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"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
@@ -30,9 +31,10 @@ import (
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
proboSvc *probo.Service
|
||||
iamSvc *iam.Service
|
||||
logger *log.Logger
|
||||
proboSvc *probo.Service
|
||||
iamSvc *iam.Service
|
||||
accessReview *accessreview.Service
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func markdownToProseMirrorJSON(markdown string) (string, error) {
|
||||
|
||||
@@ -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
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
@@ -26,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"go.probo.inc/probo/pkg/accessreview"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"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) {
|
||||
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,
|
||||
&probo.UpdateAuditRequest{
|
||||
ID: input.ID,
|
||||
@@ -1423,7 +1410,7 @@ func (r *Resolver) UpdateAuditTool(ctx context.Context, req *mcp.CallToolRequest
|
||||
|
||||
var report *coredata.Report
|
||||
if audit.ReportID != nil {
|
||||
report, err = prb.Reports.Get(ctx, *audit.ReportID)
|
||||
report, err = svc.Reports.Get(ctx, *audit.ReportID)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentArchive)
|
||||
|
||||
@@ -3250,18 +3759,16 @@ func (r *Resolver) UpdateOrganizationContextTool(ctx context.Context, req *mcp.C
|
||||
}, 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)
|
||||
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)
|
||||
|
||||
prb := r.ProboService(ctx, input.ID)
|
||||
|
||||
url, err := prb.Audits.GenerateReportURL(ctx, input.ID, 15*time.Minute)
|
||||
entry, err := r.iamSvc.OrganizationService.GetAuditLogEntry(ctx, input.ID)
|
||||
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{
|
||||
URL: *url,
|
||||
return nil, types.GetAuditLogEntryOutput{
|
||||
AuditLogEntry: types.NewAuditLogEntry(entry),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -3299,19 +3806,6 @@ func (r *Resolver) ListAuditLogEntriesTool(ctx context.Context, req *mcp.CallToo
|
||||
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) {
|
||||
r.MustAuthorize(ctx, input.DocumentID, probo.ActionDocumentVersionRequestApproval)
|
||||
|
||||
|
||||
@@ -6412,6 +6412,843 @@ components:
|
||||
$ref: "#/components/schemas/GID"
|
||||
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:
|
||||
type: object
|
||||
required:
|
||||
@@ -7732,6 +8569,156 @@ tools:
|
||||
$ref: "#/components/schemas/DeleteApplicabilityStatementInput"
|
||||
outputSchema:
|
||||
$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
|
||||
description: Get the organization context containing structured sections about the company
|
||||
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"
|
||||
"go.gearno.de/kit/log"
|
||||
mcpgenmcp "go.probo.inc/mcpgen/mcp"
|
||||
"go.probo.inc/probo/pkg/accessreview"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
@@ -34,16 +35,17 @@ func (r *Resolver) ProboService(ctx context.Context, objectID gid.GID) *probo.Te
|
||||
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.Info("initializing MCP server")
|
||||
// server.AddReceivingMiddleware(mcputils.LoggingMiddleware(logger))
|
||||
|
||||
resolver := &Resolver{
|
||||
proboSvc: proboSvc,
|
||||
iamSvc: iamSvc,
|
||||
logger: logger,
|
||||
proboSvc: proboSvc,
|
||||
iamSvc: iamSvc,
|
||||
accessReview: accessReviewSvc,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
mcpServer := server.New(resolver)
|
||||
|
||||
Reference in New Issue
Block a user