Add access review MCP tools
Add MCP tool definitions and resolvers for access review campaigns, sources, entries, decisions, and flags. Wire accessreview.Service into the MCP Resolver. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
|
"go.probo.inc/probo/pkg/accessreview"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/iam"
|
"go.probo.inc/probo/pkg/iam"
|
||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
@@ -32,6 +33,7 @@ import (
|
|||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
proboSvc *probo.Service
|
proboSvc *probo.Service
|
||||||
iamSvc *iam.Service
|
iamSvc *iam.Service
|
||||||
|
accessReview *accessreview.Service
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
package mcp_v1
|
package mcp_v1
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
@@ -26,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
"go.probo.inc/probo/pkg/accessreview"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/iam"
|
"go.probo.inc/probo/pkg/iam"
|
||||||
@@ -1404,9 +1391,9 @@ func (r *Resolver) AddAuditTool(ctx context.Context, req *mcp.CallToolRequest, i
|
|||||||
func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAuditInput) (*mcp.CallToolResult, types.UpdateAuditOutput, error) {
|
func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAuditInput) (*mcp.CallToolResult, types.UpdateAuditOutput, error) {
|
||||||
r.MustAuthorize(ctx, input.ID, probo.ActionAuditUpdate)
|
r.MustAuthorize(ctx, input.ID, probo.ActionAuditUpdate)
|
||||||
|
|
||||||
prb := r.ProboService(ctx, input.ID)
|
svc := r.ProboService(ctx, input.ID)
|
||||||
|
|
||||||
audit, err := prb.Audits.Update(
|
audit, err := svc.Audits.Update(
|
||||||
ctx,
|
ctx,
|
||||||
&probo.UpdateAuditRequest{
|
&probo.UpdateAuditRequest{
|
||||||
ID: input.ID,
|
ID: input.ID,
|
||||||
@@ -1423,7 +1410,7 @@ func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest
|
|||||||
|
|
||||||
var report *coredata.Report
|
var report *coredata.Report
|
||||||
if audit.ReportID != nil {
|
if audit.ReportID != nil {
|
||||||
report, err = prb.Reports.Get(ctx, *audit.ReportID)
|
report, err = svc.Reports.Get(ctx, *audit.ReportID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
return nil, types.UpdateAuditOutput{}, fmt.Errorf("cannot get audit report: %w", err)
|
||||||
}
|
}
|
||||||
@@ -3180,6 +3167,528 @@ func (r *Resolver) ListFindingAuditsTool(ctx context.Context, req *mcp.CallToolR
|
|||||||
return nil, types.NewListFindingAuditsOutput(auditPage), nil
|
return nil, types.NewListFindingAuditsOutput(auditPage), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAccessReviewCampaignsTool handles the listAccessReviewCampaigns tool
|
||||||
|
// List access review campaigns for an organization
|
||||||
|
func (r *Resolver) ListAccessReviewCampaignsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessReviewCampaignsInput) (*mcp.CallToolResult, types.ListAccessReviewCampaignsOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignList)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{
|
||||||
|
Field: coredata.AccessReviewCampaignOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
if input.OrderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{
|
||||||
|
Field: input.OrderBy.Field,
|
||||||
|
Direction: input.OrderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||||
|
|
||||||
|
p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, input.OrganizationID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list access review campaigns: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.NewListAccessReviewCampaignsOutput(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessEntriesTool handles the listAccessEntries tool
|
||||||
|
// List access entries for a campaign with optional filters
|
||||||
|
func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessEntriesInput) (*mcp.CallToolResult, types.ListAccessEntriesOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessEntryList)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{
|
||||||
|
Field: coredata.AccessEntryOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
if input.OrderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{
|
||||||
|
Field: input.OrderBy.Field,
|
||||||
|
Direction: input.OrderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||||
|
|
||||||
|
var filter *coredata.AccessEntryFilter
|
||||||
|
if input.Filter != nil {
|
||||||
|
filter = &coredata.AccessEntryFilter{
|
||||||
|
Decision: input.Filter.Decision,
|
||||||
|
Flag: input.Filter.Flag,
|
||||||
|
IncrementalTag: input.Filter.IncrementalTag,
|
||||||
|
IsAdmin: input.Filter.IsAdmin,
|
||||||
|
AuthMethod: input.Filter.AuthMethod,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField]
|
||||||
|
|
||||||
|
if input.AccessSourceID != nil {
|
||||||
|
var err error
|
||||||
|
p, err = r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(
|
||||||
|
ctx,
|
||||||
|
input.CampaignID,
|
||||||
|
*input.AccessSourceID,
|
||||||
|
cursor,
|
||||||
|
filter,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list access entries: %w", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
p, err = r.accessReview.Entries(scope).ListForCampaignID(ctx, input.CampaignID, cursor, filter)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list access entries: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.NewListAccessEntriesOutput(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessReviewCampaignStatisticsTool handles the getAccessReviewCampaignStatistics tool
|
||||||
|
// Get statistics for an access review campaign
|
||||||
|
func (r *Resolver) GetAccessReviewCampaignStatisticsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAccessReviewCampaignStatisticsInput) (*mcp.CallToolResult, types.GetAccessReviewCampaignStatisticsOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignGet)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
stats, err := r.accessReview.Entries(scope).Statistics(ctx, input.CampaignID)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot get campaign statistics: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.GetAccessReviewCampaignStatisticsOutput{
|
||||||
|
Statistics: types.NewAccessEntryStatistics(stats),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordAccessEntryDecisionTool handles the recordAccessEntryDecision tool
|
||||||
|
// Record a decision on an access entry
|
||||||
|
func (r *Resolver) RecordAccessEntryDecisionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessEntryDecisionInput) (*mcp.CallToolResult, types.RecordAccessEntryDecisionOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.AccessEntryID, probo.ActionAccessEntryDecide)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.AccessEntryID)
|
||||||
|
|
||||||
|
identity := authn.IdentityFromContext(ctx)
|
||||||
|
if identity == nil {
|
||||||
|
return nil, types.RecordAccessEntryDecisionOutput{}, fmt.Errorf("no identity in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
decisionReq := accessreview.RecordAccessEntryDecisionRequest{
|
||||||
|
EntryID: input.AccessEntryID,
|
||||||
|
Decision: input.Decision,
|
||||||
|
DecisionNote: input.DecisionNote,
|
||||||
|
}
|
||||||
|
|
||||||
|
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessEntryID)
|
||||||
|
if err == nil {
|
||||||
|
profile, err := r.iamSvc.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
decisionReq.DecidedByID = &profile.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := r.accessReview.Entries(scope).RecordDecision(ctx, decisionReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.RecordAccessEntryDecisionOutput{}, fmt.Errorf("cannot record decision: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.RecordAccessEntryDecisionOutput{
|
||||||
|
AccessEntry: types.NewAccessEntry(entry),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordAccessEntryDecisionsTool handles the recordAccessEntryDecisions tool
|
||||||
|
// Record decisions on multiple access entries in a single batch
|
||||||
|
func (r *Resolver) RecordAccessEntryDecisionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessEntryDecisionsInput) (*mcp.CallToolResult, types.RecordAccessEntryDecisionsOutput, error) {
|
||||||
|
if len(input.Decisions) == 0 {
|
||||||
|
return nil, types.RecordAccessEntryDecisionsOutput{
|
||||||
|
AccessEntries: []*types.AccessEntry{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxBatchSize = 100
|
||||||
|
if len(input.Decisions) > maxBatchSize {
|
||||||
|
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize each entry individually to prevent cross-org bypass.
|
||||||
|
for _, d := range input.Decisions {
|
||||||
|
r.MustAuthorize(ctx, d.AccessEntryID, probo.ActionAccessEntryDecide)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.Decisions[0].AccessEntryID)
|
||||||
|
|
||||||
|
identity := authn.IdentityFromContext(ctx)
|
||||||
|
if identity == nil {
|
||||||
|
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("no identity in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache profile lookups per organization so we resolve the correct
|
||||||
|
// decidedByID for each entry even when a batch spans multiple orgs.
|
||||||
|
profileCache := make(map[gid.GID]*gid.GID)
|
||||||
|
|
||||||
|
decisions := make([]accessreview.RecordAccessEntryDecisionRequest, len(input.Decisions))
|
||||||
|
for i, d := range input.Decisions {
|
||||||
|
var decidedByID *gid.GID
|
||||||
|
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessEntryID)
|
||||||
|
if err == nil {
|
||||||
|
if cached, ok := profileCache[organizationID]; ok {
|
||||||
|
decidedByID = cached
|
||||||
|
} else {
|
||||||
|
profile, err := r.iamSvc.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
decidedByID = &profile.ID
|
||||||
|
}
|
||||||
|
profileCache[organizationID] = decidedByID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decisions[i] = accessreview.RecordAccessEntryDecisionRequest{
|
||||||
|
EntryID: d.AccessEntryID,
|
||||||
|
Decision: d.Decision,
|
||||||
|
DecisionNote: d.DecisionNote,
|
||||||
|
DecidedByID: decidedByID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := r.accessReview.Entries(scope).RecordDecisions(ctx, decisions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessEntries := make([]*types.AccessEntry, len(entries))
|
||||||
|
for i, e := range entries {
|
||||||
|
accessEntries[i] = types.NewAccessEntry(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.RecordAccessEntryDecisionsOutput{
|
||||||
|
AccessEntries: accessEntries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseAccessReviewCampaignTool handles the closeAccessReviewCampaign tool
|
||||||
|
// Close an access review campaign
|
||||||
|
func (r *Resolver) CloseAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CloseAccessReviewCampaignInput) (*mcp.CallToolResult, types.CloseAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignClose)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).Close(ctx, input.CampaignID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.CloseAccessReviewCampaignOutput{}, fmt.Errorf("cannot close campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.CloseAccessReviewCampaignOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccessSourcesTool handles the listAccessSources tool
|
||||||
|
// List access sources for an organization
|
||||||
|
func (r *Resolver) ListAccessSourcesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessSourcesInput) (*mcp.CallToolResult, types.ListAccessSourcesOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionAccessSourceList)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{
|
||||||
|
Field: coredata.AccessSourceOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
if input.OrderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{
|
||||||
|
Field: input.OrderBy.Field,
|
||||||
|
Direction: input.OrderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||||
|
|
||||||
|
p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, input.OrganizationID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list access sources: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.NewListAccessSourcesOutput(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessSourceTool handles the createAccessSource tool
|
||||||
|
// Create a new access source for an organization
|
||||||
|
func (r *Resolver) CreateAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateAccessSourceInput) (*mcp.CallToolResult, types.CreateAccessSourceOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionAccessSourceCreate)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
|
||||||
|
|
||||||
|
source, err := r.accessReview.Sources(scope).Create(ctx, accessreview.CreateAccessSourceRequest{
|
||||||
|
OrganizationID: input.OrganizationID,
|
||||||
|
ConnectorID: input.ConnectorID,
|
||||||
|
Name: input.Name,
|
||||||
|
Category: coredata.AccessSourceCategorySaaS,
|
||||||
|
CsvData: input.CsvData,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.CreateAccessSourceOutput{}, fmt.Errorf("cannot create access source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.CreateAccessSourceOutput{
|
||||||
|
AccessSource: types.NewAccessSource(source),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessSourceTool handles the updateAccessSource tool
|
||||||
|
// Update an existing access source
|
||||||
|
func (r *Resolver) UpdateAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAccessSourceInput) (*mcp.CallToolResult, types.UpdateAccessSourceOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.AccessSourceID)
|
||||||
|
|
||||||
|
updateReq := accessreview.UpdateAccessSourceRequest{
|
||||||
|
AccessSourceID: input.AccessSourceID,
|
||||||
|
Name: input.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawConnectorID := UnwrapOmittable(input.ConnectorID); rawConnectorID != nil {
|
||||||
|
if *rawConnectorID != nil {
|
||||||
|
id, err := gid.ParseGID(**rawConnectorID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.UpdateAccessSourceOutput{}, fmt.Errorf("cannot parse connector_id: %w", err)
|
||||||
|
}
|
||||||
|
idPtr := &id
|
||||||
|
updateReq.ConnectorID = &idPtr
|
||||||
|
} else {
|
||||||
|
var nilGID *gid.GID
|
||||||
|
updateReq.ConnectorID = &nilGID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawCsvData := UnwrapOmittable(input.CsvData); rawCsvData != nil {
|
||||||
|
updateReq.CsvData = rawCsvData
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := r.accessReview.Sources(scope).Update(ctx, updateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.UpdateAccessSourceOutput{}, fmt.Errorf("cannot update access source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.UpdateAccessSourceOutput{
|
||||||
|
AccessSource: types.NewAccessSource(source),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessSourceTool handles the deleteAccessSource tool
|
||||||
|
// Delete an access source
|
||||||
|
func (r *Resolver) DeleteAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAccessSourceInput) (*mcp.CallToolResult, types.DeleteAccessSourceOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.AccessSourceID, probo.ActionAccessSourceDelete)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.AccessSourceID)
|
||||||
|
|
||||||
|
if err := r.accessReview.Sources(scope).Delete(ctx, input.AccessSourceID); err != nil {
|
||||||
|
return nil, types.DeleteAccessSourceOutput{}, fmt.Errorf("cannot delete access source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.DeleteAccessSourceOutput{
|
||||||
|
DeletedAccessSourceID: input.AccessSourceID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccessReviewCampaignTool handles the createAccessReviewCampaign tool
|
||||||
|
// Create a new access review campaign for an organization
|
||||||
|
func (r *Resolver) CreateAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateAccessReviewCampaignInput) (*mcp.CallToolResult, types.CreateAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignCreate)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
|
||||||
|
|
||||||
|
var description string
|
||||||
|
if input.Description != nil {
|
||||||
|
description = *input.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).Create(ctx, accessreview.CreateAccessReviewCampaignRequest{
|
||||||
|
OrganizationID: input.OrganizationID,
|
||||||
|
Name: input.Name,
|
||||||
|
Description: description,
|
||||||
|
FrameworkControls: input.FrameworkControls,
|
||||||
|
AccessSourceIDs: input.AccessSourceIds,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.CreateAccessReviewCampaignOutput{}, fmt.Errorf("cannot create access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.CreateAccessReviewCampaignOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccessReviewCampaignTool handles the updateAccessReviewCampaign tool
|
||||||
|
// Update an existing access review campaign
|
||||||
|
func (r *Resolver) UpdateAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAccessReviewCampaignInput) (*mcp.CallToolResult, types.UpdateAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignUpdate)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
updateReq := accessreview.UpdateAccessReviewCampaignRequest{
|
||||||
|
CampaignID: input.CampaignID,
|
||||||
|
Name: input.Name,
|
||||||
|
Description: input.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawControls := UnwrapOmittable(input.FrameworkControls); rawControls != nil {
|
||||||
|
if *rawControls != nil {
|
||||||
|
controls := make([]string, 0, len(**rawControls))
|
||||||
|
for _, v := range **rawControls {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
controls = append(controls, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateReq.FrameworkControls = &controls
|
||||||
|
} else {
|
||||||
|
empty := []string{}
|
||||||
|
updateReq.FrameworkControls = &empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).Update(ctx, updateReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.UpdateAccessReviewCampaignOutput{}, fmt.Errorf("cannot update access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.UpdateAccessReviewCampaignOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccessReviewCampaignTool handles the deleteAccessReviewCampaign tool
|
||||||
|
// Delete an access review campaign
|
||||||
|
func (r *Resolver) DeleteAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAccessReviewCampaignInput) (*mcp.CallToolResult, types.DeleteAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignDelete)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
if err := r.accessReview.Campaigns(scope).Delete(ctx, input.CampaignID); err != nil {
|
||||||
|
return nil, types.DeleteAccessReviewCampaignOutput{}, fmt.Errorf("cannot delete access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.DeleteAccessReviewCampaignOutput{
|
||||||
|
DeletedCampaignID: input.CampaignID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAccessReviewCampaignTool handles the startAccessReviewCampaign tool
|
||||||
|
// Start an access review campaign
|
||||||
|
func (r *Resolver) StartAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.StartAccessReviewCampaignInput) (*mcp.CallToolResult, types.StartAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignStart)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).Start(ctx, input.CampaignID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.StartAccessReviewCampaignOutput{}, fmt.Errorf("cannot start access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.StartAccessReviewCampaignOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelAccessReviewCampaignTool handles the cancelAccessReviewCampaign tool
|
||||||
|
// Cancel an in-progress access review campaign
|
||||||
|
func (r *Resolver) CancelAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CancelAccessReviewCampaignInput) (*mcp.CallToolResult, types.CancelAccessReviewCampaignOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignCancel)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).Cancel(ctx, input.CampaignID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.CancelAccessReviewCampaignOutput{}, fmt.Errorf("cannot cancel access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.CancelAccessReviewCampaignOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccessReviewCampaignScopeSourceTool handles the addAccessReviewCampaignScopeSource tool
|
||||||
|
// Add an access source to an access review campaign's scope
|
||||||
|
func (r *Resolver) AddAccessReviewCampaignScopeSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddAccessReviewCampaignScopeSourceInput) (*mcp.CallToolResult, types.AddAccessReviewCampaignScopeSourceOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignAddScopeSource)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).AddScopeSource(ctx, accessreview.AddCampaignScopeSourceRequest{
|
||||||
|
CampaignID: input.CampaignID,
|
||||||
|
AccessSourceID: input.AccessSourceID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.AddAccessReviewCampaignScopeSourceOutput{}, fmt.Errorf("cannot add scope source to access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.AddAccessReviewCampaignScopeSourceOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAccessReviewCampaignScopeSourceTool handles the removeAccessReviewCampaignScopeSource tool
|
||||||
|
// Remove an access source from an access review campaign's scope
|
||||||
|
func (r *Resolver) RemoveAccessReviewCampaignScopeSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RemoveAccessReviewCampaignScopeSourceInput) (*mcp.CallToolResult, types.RemoveAccessReviewCampaignScopeSourceOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignRemoveScopeSource)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.CampaignID)
|
||||||
|
|
||||||
|
campaign, err := r.accessReview.Campaigns(scope).RemoveScopeSource(ctx, accessreview.RemoveCampaignScopeSourceRequest{
|
||||||
|
CampaignID: input.CampaignID,
|
||||||
|
AccessSourceID: input.AccessSourceID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.RemoveAccessReviewCampaignScopeSourceOutput{}, fmt.Errorf("cannot remove scope source from access review campaign: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.RemoveAccessReviewCampaignScopeSourceOutput{
|
||||||
|
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagAccessEntryTool handles the flagAccessEntry tool
|
||||||
|
// Flag an access entry during review
|
||||||
|
func (r *Resolver) FlagAccessEntryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.FlagAccessEntryInput) (*mcp.CallToolResult, types.FlagAccessEntryOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.AccessEntryID, probo.ActionAccessEntryFlag)
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.AccessEntryID)
|
||||||
|
|
||||||
|
entry, err := r.accessReview.Entries(scope).FlagEntry(ctx, accessreview.FlagAccessEntryRequest{
|
||||||
|
EntryID: input.AccessEntryID,
|
||||||
|
Flags: input.Flags,
|
||||||
|
FlagReasons: input.FlagReasons,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.FlagAccessEntryOutput{}, fmt.Errorf("cannot flag access entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.FlagAccessEntryOutput{
|
||||||
|
AccessEntry: types.NewAccessEntry(entry),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) GetAuditReportUrlTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAuditReportUrlInput) (*mcp.CallToolResult, types.GetAuditReportUrlOutput, error) {
|
||||||
|
r.MustAuthorize(ctx, input.ID, probo.ActionReportGetReportUrl)
|
||||||
|
|
||||||
|
prb := r.ProboService(ctx, input.ID)
|
||||||
|
|
||||||
|
url, err := prb.Audits.GenerateReportURL(ctx, input.ID, 15*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.GetAuditReportUrlOutput{}, fmt.Errorf("cannot generate audit report URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, types.GetAuditReportUrlOutput{
|
||||||
|
URL: *url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) ArchiveDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ArchiveDocumentInput) (*mcp.CallToolResult, types.ArchiveDocumentOutput, error) {
|
func (r *Resolver) ArchiveDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ArchiveDocumentInput) (*mcp.CallToolResult, types.ArchiveDocumentOutput, error) {
|
||||||
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentArchive)
|
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentArchive)
|
||||||
|
|
||||||
@@ -3250,18 +3759,16 @@ func (r *Resolver) UpdateOrganizationContextTool(ctx context.Context, req *mcp.C
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) GetAuditReportUrlTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAuditReportUrlInput) (*mcp.CallToolResult, types.GetAuditReportUrlOutput, error) {
|
func (r *Resolver) GetAuditLogEntryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAuditLogEntryInput) (*mcp.CallToolResult, types.GetAuditLogEntryOutput, error) {
|
||||||
r.MustAuthorize(ctx, input.ID, probo.ActionReportGetReportUrl)
|
r.MustAuthorize(ctx, input.ID, iam.ActionAuditLogEntryGet)
|
||||||
|
|
||||||
prb := r.ProboService(ctx, input.ID)
|
entry, err := r.iamSvc.OrganizationService.GetAuditLogEntry(ctx, input.ID)
|
||||||
|
|
||||||
url, err := prb.Audits.GenerateReportURL(ctx, input.ID, 15*time.Minute)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.GetAuditReportUrlOutput{}, fmt.Errorf("cannot generate audit report URL: %w", err)
|
panic(fmt.Errorf("cannot get audit log entry: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, types.GetAuditReportUrlOutput{
|
return nil, types.GetAuditLogEntryOutput{
|
||||||
URL: *url,
|
AuditLogEntry: types.NewAuditLogEntry(entry),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3299,19 +3806,6 @@ func (r *Resolver) ListAuditLogEntriesTool(ctx context.Context, req *mcp.CallToo
|
|||||||
return nil, types.NewListAuditLogEntriesOutput(p), nil
|
return nil, types.NewListAuditLogEntriesOutput(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) GetAuditLogEntryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAuditLogEntryInput) (*mcp.CallToolResult, types.GetAuditLogEntryOutput, error) {
|
|
||||||
r.MustAuthorize(ctx, input.ID, iam.ActionAuditLogEntryGet)
|
|
||||||
|
|
||||||
entry, err := r.iamSvc.OrganizationService.GetAuditLogEntry(ctx, input.ID)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("cannot get audit log entry: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, types.GetAuditLogEntryOutput{
|
|
||||||
AuditLogEntry: types.NewAuditLogEntry(entry),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) RequestDocumentVersionApprovalTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RequestDocumentVersionApprovalInput) (*mcp.CallToolResult, types.RequestDocumentVersionApprovalOutput, error) {
|
func (r *Resolver) RequestDocumentVersionApprovalTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RequestDocumentVersionApprovalInput) (*mcp.CallToolResult, types.RequestDocumentVersionApprovalOutput, error) {
|
||||||
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval)
|
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval)
|
||||||
|
|
||||||
|
|||||||
@@ -6412,6 +6412,843 @@ components:
|
|||||||
$ref: "#/components/schemas/GID"
|
$ref: "#/components/schemas/GID"
|
||||||
description: Deleted applicability statement ID
|
description: Deleted applicability statement ID
|
||||||
|
|
||||||
|
AccessReviewCampaignStatus:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- DRAFT
|
||||||
|
- IN_PROGRESS
|
||||||
|
- PENDING_ACTIONS
|
||||||
|
- FAILED
|
||||||
|
- COMPLETED
|
||||||
|
- CANCELLED
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatus
|
||||||
|
|
||||||
|
AccessEntryDecision:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- PENDING
|
||||||
|
- APPROVED
|
||||||
|
- REVOKE
|
||||||
|
- DEFER
|
||||||
|
- ESCALATE
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryDecision
|
||||||
|
|
||||||
|
AccessEntryFlag:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- NONE
|
||||||
|
- ORPHANED
|
||||||
|
- INACTIVE
|
||||||
|
- EXCESSIVE
|
||||||
|
- ROLE_MISMATCH
|
||||||
|
- NEW
|
||||||
|
- DORMANT
|
||||||
|
- TERMINATED_USER
|
||||||
|
- CONTRACTOR_EXPIRED
|
||||||
|
- SOD_CONFLICT
|
||||||
|
- PRIVILEGED_ACCESS
|
||||||
|
- ROLE_CREEP
|
||||||
|
- NO_BUSINESS_JUSTIFICATION
|
||||||
|
- OUT_OF_DEPARTMENT
|
||||||
|
- SHARED_ACCOUNT
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryFlag
|
||||||
|
|
||||||
|
AccessEntryIncrementalTag:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- NEW
|
||||||
|
- REMOVED
|
||||||
|
- UNCHANGED
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTag
|
||||||
|
|
||||||
|
AccessEntryAuthMethod:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- SSO
|
||||||
|
- PASSWORD
|
||||||
|
- API_KEY
|
||||||
|
- SERVICE_ACCOUNT
|
||||||
|
- UNKNOWN
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethod
|
||||||
|
|
||||||
|
AccessEntryAccountType:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- USER
|
||||||
|
- SERVICE_ACCOUNT
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryAccountType
|
||||||
|
|
||||||
|
MFAStatus:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- ENABLED
|
||||||
|
- DISABLED
|
||||||
|
- UNKNOWN
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.MFAStatus
|
||||||
|
|
||||||
|
AccessReviewCampaignOrderField:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- CREATED_AT
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewCampaignOrderField
|
||||||
|
|
||||||
|
AccessReviewCampaignOrderBy:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- field
|
||||||
|
- direction
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaignOrderField"
|
||||||
|
description: Order field
|
||||||
|
direction:
|
||||||
|
$ref: "#/components/schemas/OrderDirection"
|
||||||
|
description: Order direction
|
||||||
|
|
||||||
|
AccessEntryOrderField:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- CREATED_AT
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryOrderField
|
||||||
|
|
||||||
|
AccessEntryOrderBy:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- field
|
||||||
|
- direction
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
$ref: "#/components/schemas/AccessEntryOrderField"
|
||||||
|
description: Order field
|
||||||
|
direction:
|
||||||
|
$ref: "#/components/schemas/OrderDirection"
|
||||||
|
description: Order direction
|
||||||
|
|
||||||
|
AccessReviewCampaign:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- organization_id
|
||||||
|
- name
|
||||||
|
- status
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Campaign name
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Campaign description
|
||||||
|
status:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaignStatus"
|
||||||
|
description: Campaign status
|
||||||
|
started_at:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
format: date-time
|
||||||
|
description: Campaign start time
|
||||||
|
completed_at:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
format: date-time
|
||||||
|
description: Campaign completion time
|
||||||
|
framework_controls:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Framework controls
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Creation timestamp
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Update timestamp
|
||||||
|
|
||||||
|
AccessEntry:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- campaign_id
|
||||||
|
- access_source_id
|
||||||
|
- email
|
||||||
|
- full_name
|
||||||
|
- role
|
||||||
|
- job_title
|
||||||
|
- is_admin
|
||||||
|
- mfa_status
|
||||||
|
- auth_method
|
||||||
|
- account_type
|
||||||
|
- external_id
|
||||||
|
- incremental_tag
|
||||||
|
- flags
|
||||||
|
- decision
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Entry ID
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
description: User email
|
||||||
|
full_name:
|
||||||
|
type: string
|
||||||
|
description: User full name
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
description: User role in the system
|
||||||
|
job_title:
|
||||||
|
type: string
|
||||||
|
description: User job title
|
||||||
|
is_admin:
|
||||||
|
type: boolean
|
||||||
|
description: Whether the user has admin privileges
|
||||||
|
mfa_status:
|
||||||
|
$ref: "#/components/schemas/MFAStatus"
|
||||||
|
description: MFA status
|
||||||
|
auth_method:
|
||||||
|
$ref: "#/components/schemas/AccessEntryAuthMethod"
|
||||||
|
description: Authentication method
|
||||||
|
account_type:
|
||||||
|
$ref: "#/components/schemas/AccessEntryAccountType"
|
||||||
|
description: Account type (user or service account)
|
||||||
|
last_login:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
format: date-time
|
||||||
|
description: Last login time
|
||||||
|
account_created_at:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
format: date-time
|
||||||
|
description: Account creation time
|
||||||
|
external_id:
|
||||||
|
type: string
|
||||||
|
description: External ID in the source system
|
||||||
|
incremental_tag:
|
||||||
|
$ref: "#/components/schemas/AccessEntryIncrementalTag"
|
||||||
|
description: Change tag compared to previous campaign
|
||||||
|
flags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessEntryFlag"
|
||||||
|
description: Risk flags
|
||||||
|
flag_reasons:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Reasons for the flags
|
||||||
|
decision:
|
||||||
|
$ref: "#/components/schemas/AccessEntryDecision"
|
||||||
|
description: Review decision
|
||||||
|
decision_note:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
description: Decision justification
|
||||||
|
decided_by:
|
||||||
|
anyOf:
|
||||||
|
- $ref: "#/components/schemas/GID"
|
||||||
|
- type: "null"
|
||||||
|
description: Profile ID of the decision maker
|
||||||
|
decided_at:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
format: date-time
|
||||||
|
description: Decision timestamp
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Creation timestamp
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Update timestamp
|
||||||
|
|
||||||
|
AccessEntryStatistics:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- total_count
|
||||||
|
- decision_counts
|
||||||
|
- flag_counts
|
||||||
|
- incremental_tag_counts
|
||||||
|
properties:
|
||||||
|
total_count:
|
||||||
|
type: integer
|
||||||
|
description: Total number of entries
|
||||||
|
decision_counts:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
description: Count of entries per decision status
|
||||||
|
flag_counts:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
description: Count of entries per flag type
|
||||||
|
incremental_tag_counts:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
description: Count of entries per incremental tag
|
||||||
|
|
||||||
|
ListAccessReviewCampaignsInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- organization_id
|
||||||
|
properties:
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
order_by:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaignOrderBy"
|
||||||
|
description: Order by
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
description: Page size
|
||||||
|
cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Page cursor
|
||||||
|
|
||||||
|
ListAccessReviewCampaignsOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaigns
|
||||||
|
properties:
|
||||||
|
next_cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Next cursor
|
||||||
|
campaigns:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
ListAccessEntriesInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Filter by access source ID
|
||||||
|
order_by:
|
||||||
|
$ref: "#/components/schemas/AccessEntryOrderBy"
|
||||||
|
description: Order by
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
description: Page size
|
||||||
|
cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Page cursor
|
||||||
|
filter:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
decision:
|
||||||
|
$ref: "#/components/schemas/AccessEntryDecision"
|
||||||
|
description: Filter by decision status
|
||||||
|
flag:
|
||||||
|
$ref: "#/components/schemas/AccessEntryFlag"
|
||||||
|
description: Filter by flag
|
||||||
|
incremental_tag:
|
||||||
|
$ref: "#/components/schemas/AccessEntryIncrementalTag"
|
||||||
|
description: Filter by incremental tag
|
||||||
|
is_admin:
|
||||||
|
type: boolean
|
||||||
|
description: Filter by admin status
|
||||||
|
auth_method:
|
||||||
|
$ref: "#/components/schemas/AccessEntryAuthMethod"
|
||||||
|
description: Filter by auth method
|
||||||
|
account_type:
|
||||||
|
$ref: "#/components/schemas/AccessEntryAccountType"
|
||||||
|
description: Filter by account type
|
||||||
|
|
||||||
|
ListAccessEntriesOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- entries
|
||||||
|
properties:
|
||||||
|
next_cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Next cursor
|
||||||
|
entries:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessEntry"
|
||||||
|
|
||||||
|
GetAccessReviewCampaignStatisticsInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
|
||||||
|
GetAccessReviewCampaignStatisticsOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- statistics
|
||||||
|
properties:
|
||||||
|
statistics:
|
||||||
|
$ref: "#/components/schemas/AccessEntryStatistics"
|
||||||
|
|
||||||
|
RecordAccessEntryDecisionMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entry_id
|
||||||
|
- decision
|
||||||
|
properties:
|
||||||
|
access_entry_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access entry ID
|
||||||
|
decision:
|
||||||
|
$ref: "#/components/schemas/AccessEntryDecision"
|
||||||
|
description: Decision (APPROVED, REVOKE, DEFER, ESCALATE)
|
||||||
|
decision_note:
|
||||||
|
type: string
|
||||||
|
description: Decision justification (required for non-APPROVED decisions)
|
||||||
|
|
||||||
|
RecordAccessEntryDecisionMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entry
|
||||||
|
properties:
|
||||||
|
access_entry:
|
||||||
|
$ref: "#/components/schemas/AccessEntry"
|
||||||
|
|
||||||
|
RecordAccessEntryDecisionsMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- decisions
|
||||||
|
properties:
|
||||||
|
decisions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entry_id
|
||||||
|
- decision
|
||||||
|
properties:
|
||||||
|
access_entry_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access entry ID
|
||||||
|
decision:
|
||||||
|
$ref: "#/components/schemas/AccessEntryDecision"
|
||||||
|
description: Decision (APPROVED, REVOKE, DEFER, ESCALATE)
|
||||||
|
decision_note:
|
||||||
|
type: string
|
||||||
|
description: Decision justification (required for non-APPROVED decisions)
|
||||||
|
|
||||||
|
RecordAccessEntryDecisionsMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entries
|
||||||
|
properties:
|
||||||
|
access_entries:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessEntry"
|
||||||
|
|
||||||
|
FlagAccessEntryMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entry_id
|
||||||
|
- flags
|
||||||
|
properties:
|
||||||
|
access_entry_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access entry ID
|
||||||
|
flags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessEntryFlag"
|
||||||
|
description: Flags to set (ORPHANED, INACTIVE, EXCESSIVE, ROLE_MISMATCH, NEW, DORMANT, TERMINATED_USER, CONTRACTOR_EXPIRED, SOD_CONFLICT, PRIVILEGED_ACCESS, ROLE_CREEP, NO_BUSINESS_JUSTIFICATION, OUT_OF_DEPARTMENT, SHARED_ACCOUNT)
|
||||||
|
flag_reasons:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Reasons for flagging
|
||||||
|
|
||||||
|
FlagAccessEntryMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_entry
|
||||||
|
properties:
|
||||||
|
access_entry:
|
||||||
|
$ref: "#/components/schemas/AccessEntry"
|
||||||
|
|
||||||
|
CloseAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
|
||||||
|
CloseAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
AccessSourceCategory:
|
||||||
|
type: string
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessSourceCategory
|
||||||
|
enum:
|
||||||
|
- SAAS
|
||||||
|
- CLOUD_INFRA
|
||||||
|
- SOURCE_CODE
|
||||||
|
- OTHER
|
||||||
|
|
||||||
|
AccessSourceOrderField:
|
||||||
|
type: string
|
||||||
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessSourceOrderField
|
||||||
|
enum:
|
||||||
|
- CREATED_AT
|
||||||
|
|
||||||
|
AccessSourceOrderBy:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- field
|
||||||
|
- direction
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
$ref: "#/components/schemas/AccessSourceOrderField"
|
||||||
|
description: Order field
|
||||||
|
direction:
|
||||||
|
$ref: "#/components/schemas/OrderDirection"
|
||||||
|
description: Order direction
|
||||||
|
|
||||||
|
AccessSource:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- organization_id
|
||||||
|
- name
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
connector_id:
|
||||||
|
anyOf:
|
||||||
|
- $ref: "#/components/schemas/GID"
|
||||||
|
- type: "null"
|
||||||
|
description: Connector ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Access source name
|
||||||
|
csv_data:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
description: CSV data for manual sources
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Creation timestamp
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Update timestamp
|
||||||
|
|
||||||
|
ListAccessSourcesInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- organization_id
|
||||||
|
properties:
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
order_by:
|
||||||
|
$ref: "#/components/schemas/AccessSourceOrderBy"
|
||||||
|
description: Order by
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
description: Page size
|
||||||
|
cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Page cursor
|
||||||
|
|
||||||
|
ListAccessSourcesOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_sources
|
||||||
|
properties:
|
||||||
|
next_cursor:
|
||||||
|
$ref: "#/components/schemas/CursorKey"
|
||||||
|
description: Next cursor
|
||||||
|
access_sources:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/AccessSource"
|
||||||
|
|
||||||
|
CreateAccessSourceMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- organization_id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
connector_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Connector ID (optional)
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Access source name
|
||||||
|
csv_data:
|
||||||
|
type: string
|
||||||
|
description: CSV data for manual sources (optional)
|
||||||
|
|
||||||
|
CreateAccessSourceMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_source
|
||||||
|
properties:
|
||||||
|
access_source:
|
||||||
|
$ref: "#/components/schemas/AccessSource"
|
||||||
|
|
||||||
|
UpdateAccessSourceMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_source_id
|
||||||
|
properties:
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: New name
|
||||||
|
connector_id:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
go.probo.inc/mcpgen/omittable: true
|
||||||
|
description: Connector ID (set to null to remove)
|
||||||
|
csv_data:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
go.probo.inc/mcpgen/omittable: true
|
||||||
|
description: CSV data for manual sources (set to null to remove)
|
||||||
|
|
||||||
|
UpdateAccessSourceMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_source
|
||||||
|
properties:
|
||||||
|
access_source:
|
||||||
|
$ref: "#/components/schemas/AccessSource"
|
||||||
|
|
||||||
|
DeleteAccessSourceMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- access_source_id
|
||||||
|
properties:
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID
|
||||||
|
|
||||||
|
DeleteAccessSourceMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- deleted_access_source_id
|
||||||
|
properties:
|
||||||
|
deleted_access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Deleted access source ID
|
||||||
|
|
||||||
|
CreateAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- organization_id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
organization_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Organization ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Campaign name
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Campaign description
|
||||||
|
framework_controls:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Framework control references
|
||||||
|
access_source_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source IDs to include in scope
|
||||||
|
|
||||||
|
CreateAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
UpdateAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: New campaign name
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: New campaign description
|
||||||
|
framework_controls:
|
||||||
|
type:
|
||||||
|
- array
|
||||||
|
- "null"
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
go.probo.inc/mcpgen/omittable: true
|
||||||
|
description: Framework control references (set to null to clear)
|
||||||
|
|
||||||
|
UpdateAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
DeleteAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
|
||||||
|
DeleteAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- deleted_campaign_id
|
||||||
|
properties:
|
||||||
|
deleted_campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Deleted campaign ID
|
||||||
|
|
||||||
|
StartAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
|
||||||
|
StartAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
CancelAccessReviewCampaignMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
|
||||||
|
CancelAccessReviewCampaignMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
AddAccessReviewCampaignScopeSourceMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
- access_source_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID to add to scope
|
||||||
|
|
||||||
|
AddAccessReviewCampaignScopeSourceMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
|
RemoveAccessReviewCampaignScopeSourceMCPInput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign_id
|
||||||
|
- access_source_id
|
||||||
|
properties:
|
||||||
|
campaign_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Campaign ID
|
||||||
|
access_source_id:
|
||||||
|
$ref: "#/components/schemas/GID"
|
||||||
|
description: Access source ID to remove from scope
|
||||||
|
|
||||||
|
RemoveAccessReviewCampaignScopeSourceMCPOutput:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- campaign
|
||||||
|
properties:
|
||||||
|
campaign:
|
||||||
|
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||||
|
|
||||||
OrganizationContext:
|
OrganizationContext:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -7732,6 +8569,156 @@ tools:
|
|||||||
$ref: "#/components/schemas/DeleteApplicabilityStatementInput"
|
$ref: "#/components/schemas/DeleteApplicabilityStatementInput"
|
||||||
outputSchema:
|
outputSchema:
|
||||||
$ref: "#/components/schemas/DeleteApplicabilityStatementOutput"
|
$ref: "#/components/schemas/DeleteApplicabilityStatementOutput"
|
||||||
|
- name: listAccessReviewCampaigns
|
||||||
|
description: List access review campaigns for an organization
|
||||||
|
hints:
|
||||||
|
readonly: true
|
||||||
|
idempotent: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessReviewCampaignsInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessReviewCampaignsOutput"
|
||||||
|
- name: listAccessEntries
|
||||||
|
description: List access entries for a campaign with optional filters (decision, flag, incremental_tag, is_admin, auth_method, account_type)
|
||||||
|
hints:
|
||||||
|
readonly: true
|
||||||
|
idempotent: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessEntriesInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessEntriesOutput"
|
||||||
|
- name: getAccessReviewCampaignStatistics
|
||||||
|
description: Get statistics for an access review campaign including counts by decision, flag, and incremental tag
|
||||||
|
hints:
|
||||||
|
readonly: true
|
||||||
|
idempotent: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/GetAccessReviewCampaignStatisticsInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/GetAccessReviewCampaignStatisticsOutput"
|
||||||
|
- name: recordAccessEntryDecision
|
||||||
|
description: Record a decision on an access entry (APPROVED, REVOKE, DEFER, or ESCALATE). Non-APPROVED decisions require a decision_note.
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/RecordAccessEntryDecisionMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/RecordAccessEntryDecisionMCPOutput"
|
||||||
|
- name: recordAccessEntryDecisions
|
||||||
|
description: Record decisions on multiple access entries in a single batch. Non-APPROVED decisions require a decision_note.
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/RecordAccessEntryDecisionsMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/RecordAccessEntryDecisionsMCPOutput"
|
||||||
|
- name: flagAccessEntry
|
||||||
|
description: Flag an access entry with one or more flags during review (ORPHANED, INACTIVE, EXCESSIVE, ROLE_MISMATCH, NEW, etc.). Optionally provide reasons.
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/FlagAccessEntryMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/FlagAccessEntryMCPOutput"
|
||||||
|
- name: closeAccessReviewCampaign
|
||||||
|
description: Close an access review campaign. All entries must have been decided (no PENDING entries).
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/CloseAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/CloseAccessReviewCampaignMCPOutput"
|
||||||
|
- name: listAccessSources
|
||||||
|
description: List access sources for an organization
|
||||||
|
hints:
|
||||||
|
readonly: true
|
||||||
|
idempotent: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessSourcesInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/ListAccessSourcesOutput"
|
||||||
|
- name: createAccessSource
|
||||||
|
description: Create a new access source for an organization
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/CreateAccessSourceMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/CreateAccessSourceMCPOutput"
|
||||||
|
- name: updateAccessSource
|
||||||
|
description: Update an existing access source
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/UpdateAccessSourceMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/UpdateAccessSourceMCPOutput"
|
||||||
|
- name: deleteAccessSource
|
||||||
|
description: Delete an access source
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
destructive: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/DeleteAccessSourceMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/DeleteAccessSourceMCPOutput"
|
||||||
|
- name: createAccessReviewCampaign
|
||||||
|
description: Create a new access review campaign for an organization
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/CreateAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/CreateAccessReviewCampaignMCPOutput"
|
||||||
|
- name: updateAccessReviewCampaign
|
||||||
|
description: Update an existing access review campaign
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/UpdateAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/UpdateAccessReviewCampaignMCPOutput"
|
||||||
|
- name: deleteAccessReviewCampaign
|
||||||
|
description: Delete an access review campaign
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
destructive: true
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/DeleteAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/DeleteAccessReviewCampaignMCPOutput"
|
||||||
|
- name: startAccessReviewCampaign
|
||||||
|
description: Start an access review campaign. Triggers data fetching from all configured scope sources.
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/StartAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/StartAccessReviewCampaignMCPOutput"
|
||||||
|
- name: cancelAccessReviewCampaign
|
||||||
|
description: Cancel an in-progress access review campaign
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/CancelAccessReviewCampaignMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/CancelAccessReviewCampaignMCPOutput"
|
||||||
|
- name: addAccessReviewCampaignScopeSource
|
||||||
|
description: Add an access source to an access review campaign's scope
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/AddAccessReviewCampaignScopeSourceMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/AddAccessReviewCampaignScopeSourceMCPOutput"
|
||||||
|
- name: removeAccessReviewCampaignScopeSource
|
||||||
|
description: Remove an access source from an access review campaign's scope
|
||||||
|
hints:
|
||||||
|
readonly: false
|
||||||
|
inputSchema:
|
||||||
|
$ref: "#/components/schemas/RemoveAccessReviewCampaignScopeSourceMCPInput"
|
||||||
|
outputSchema:
|
||||||
|
$ref: "#/components/schemas/RemoveAccessReviewCampaignScopeSourceMCPOutput"
|
||||||
- name: getOrganizationContext
|
- name: getOrganizationContext
|
||||||
description: Get the organization context containing structured sections about the company
|
description: Get the organization context containing structured sections about the company
|
||||||
hints:
|
hints:
|
||||||
|
|||||||
161
pkg/server/api/mcp/v1/types/access_review.go
Normal file
161
pkg/server/api/mcp/v1/types/access_review.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAccessSource(s *coredata.AccessSource) *AccessSource {
|
||||||
|
return &AccessSource{
|
||||||
|
ID: s.ID,
|
||||||
|
OrganizationID: s.OrganizationID,
|
||||||
|
ConnectorID: s.ConnectorID,
|
||||||
|
Name: s.Name,
|
||||||
|
CsvData: s.CsvData,
|
||||||
|
CreatedAt: s.CreatedAt,
|
||||||
|
UpdatedAt: s.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListAccessSourcesOutput(
|
||||||
|
p *page.Page[*coredata.AccessSource, coredata.AccessSourceOrderField],
|
||||||
|
) ListAccessSourcesOutput {
|
||||||
|
sources := make([]*AccessSource, 0, len(p.Data))
|
||||||
|
for _, s := range p.Data {
|
||||||
|
sources = append(sources, NewAccessSource(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextCursor *page.CursorKey
|
||||||
|
if len(p.Data) > 0 {
|
||||||
|
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
|
||||||
|
nextCursor = &cursorKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListAccessSourcesOutput{
|
||||||
|
NextCursor: nextCursor,
|
||||||
|
AccessSources: sources,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccessReviewCampaign(c *coredata.AccessReviewCampaign) *AccessReviewCampaign {
|
||||||
|
return &AccessReviewCampaign{
|
||||||
|
ID: c.ID,
|
||||||
|
OrganizationID: c.OrganizationID,
|
||||||
|
Name: c.Name,
|
||||||
|
Description: &c.Description,
|
||||||
|
Status: c.Status,
|
||||||
|
StartedAt: c.StartedAt,
|
||||||
|
CompletedAt: c.CompletedAt,
|
||||||
|
FrameworkControls: c.FrameworkControls,
|
||||||
|
CreatedAt: c.CreatedAt,
|
||||||
|
UpdatedAt: c.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccessEntry(e *coredata.AccessEntry) *AccessEntry {
|
||||||
|
entry := &AccessEntry{
|
||||||
|
ID: e.ID,
|
||||||
|
CampaignID: e.AccessReviewCampaignID,
|
||||||
|
AccessSourceID: e.AccessSourceID,
|
||||||
|
Email: e.Email,
|
||||||
|
FullName: e.FullName,
|
||||||
|
Role: e.Role,
|
||||||
|
JobTitle: e.JobTitle,
|
||||||
|
IsAdmin: e.IsAdmin,
|
||||||
|
MfaStatus: e.MFAStatus,
|
||||||
|
AuthMethod: e.AuthMethod,
|
||||||
|
AccountType: e.AccountType,
|
||||||
|
LastLogin: e.LastLogin,
|
||||||
|
AccountCreatedAt: e.AccountCreatedAt,
|
||||||
|
ExternalID: e.ExternalID,
|
||||||
|
IncrementalTag: e.IncrementalTag,
|
||||||
|
Flags: e.Flags,
|
||||||
|
FlagReasons: e.FlagReasons,
|
||||||
|
Decision: e.Decision,
|
||||||
|
DecisionNote: e.DecisionNote,
|
||||||
|
DecidedBy: e.DecidedBy,
|
||||||
|
DecidedAt: e.DecidedAt,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListAccessReviewCampaignsOutput(
|
||||||
|
p *page.Page[*coredata.AccessReviewCampaign, coredata.AccessReviewCampaignOrderField],
|
||||||
|
) ListAccessReviewCampaignsOutput {
|
||||||
|
campaigns := make([]*AccessReviewCampaign, 0, len(p.Data))
|
||||||
|
for _, c := range p.Data {
|
||||||
|
campaigns = append(campaigns, NewAccessReviewCampaign(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextCursor *page.CursorKey
|
||||||
|
if len(p.Data) > 0 {
|
||||||
|
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
|
||||||
|
nextCursor = &cursorKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListAccessReviewCampaignsOutput{
|
||||||
|
NextCursor: nextCursor,
|
||||||
|
Campaigns: campaigns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListAccessEntriesOutput(
|
||||||
|
p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField],
|
||||||
|
) ListAccessEntriesOutput {
|
||||||
|
entries := make([]*AccessEntry, 0, len(p.Data))
|
||||||
|
for _, e := range p.Data {
|
||||||
|
entries = append(entries, NewAccessEntry(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextCursor *page.CursorKey
|
||||||
|
if len(p.Data) > 0 {
|
||||||
|
cursorKey := p.Data[len(p.Data)-1].CursorKey(p.Cursor.OrderBy.Field)
|
||||||
|
nextCursor = &cursorKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListAccessEntriesOutput{
|
||||||
|
NextCursor: nextCursor,
|
||||||
|
Entries: entries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccessEntryStatistics(s *coredata.AccessEntryStatistics) *AccessEntryStatistics {
|
||||||
|
decisionCounts := make(map[string]any, len(s.DecisionCounts))
|
||||||
|
for k, v := range s.DecisionCounts {
|
||||||
|
decisionCounts[string(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
flagCounts := make(map[string]any, len(s.FlagCounts))
|
||||||
|
for k, v := range s.FlagCounts {
|
||||||
|
flagCounts[string(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementalTagCounts := make(map[string]any, len(s.IncrementalTagCounts))
|
||||||
|
for k, v := range s.IncrementalTagCounts {
|
||||||
|
incrementalTagCounts[string(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AccessEntryStatistics{
|
||||||
|
TotalCount: s.TotalCount,
|
||||||
|
DecisionCounts: decisionCounts,
|
||||||
|
FlagCounts: flagCounts,
|
||||||
|
IncrementalTagCounts: incrementalTagCounts,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
mcpgenmcp "go.probo.inc/mcpgen/mcp"
|
mcpgenmcp "go.probo.inc/mcpgen/mcp"
|
||||||
|
"go.probo.inc/probo/pkg/accessreview"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/iam"
|
"go.probo.inc/probo/pkg/iam"
|
||||||
"go.probo.inc/probo/pkg/probo"
|
"go.probo.inc/probo/pkg/probo"
|
||||||
@@ -34,7 +35,7 @@ func (r *Resolver) ProboService(ctx context.Context, objectID gid.GID) *probo.Te
|
|||||||
return r.proboSvc.WithTenant(objectID.TenantID())
|
return r.proboSvc.WithTenant(objectID.TenantID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, tokenSecret string) *chi.Mux {
|
func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, accessReviewSvc *accessreview.Service, tokenSecret string) *chi.Mux {
|
||||||
logger = logger.Named("mcp.v1")
|
logger = logger.Named("mcp.v1")
|
||||||
|
|
||||||
logger.Info("initializing MCP server")
|
logger.Info("initializing MCP server")
|
||||||
@@ -43,6 +44,7 @@ func NewMux(logger *log.Logger, proboSvc *probo.Service, iamSvc *iam.Service, to
|
|||||||
resolver := &Resolver{
|
resolver := &Resolver{
|
||||||
proboSvc: proboSvc,
|
proboSvc: proboSvc,
|
||||||
iamSvc: iamSvc,
|
iamSvc: iamSvc,
|
||||||
|
accessReview: accessReviewSvc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user