Add risks

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-11-23 20:58:23 +01:00
parent 0efecd232e
commit fa97a8e9c6
9 changed files with 666 additions and 6 deletions

View File

@@ -192,3 +192,103 @@ func (r *Resolver) AddPeopleTool(ctx context.Context, req *mcp.CallToolRequest,
People: types.NewPeople(people),
}, nil
}
func (r *Resolver) ListRisksTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListRisksInput) (*mcp.CallToolResult, types.ListRisksOutput, error) {
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionListRisks)
prb := r.ProboService(ctx, input.OrganizationID)
pageOrderBy := page.OrderBy[coredata.RiskOrderField]{
Field: coredata.RiskOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.RiskOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
var riskFilter = coredata.NewRiskFilter(nil, nil)
if input.Filter != nil {
riskFilter = coredata.NewRiskFilter(input.Filter.Query, &input.Filter.SnapshotID)
}
page, err := prb.Risks.ListForOrganizationID(ctx, input.OrganizationID, cursor, riskFilter)
if err != nil {
panic(fmt.Errorf("cannot list organization risks: %w", err))
}
return nil, types.NewListRisksOutput(page), nil
}
func (r *Resolver) GetRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetRiskInput) (*mcp.CallToolResult, types.GetRiskOutput, error) {
r.MustBeAuthorized(ctx, input.ID, authz.ActionGet)
prb := r.ProboService(ctx, input.ID)
risk, err := prb.Risks.Get(ctx, input.ID)
if err != nil {
return nil, types.GetRiskOutput{}, fmt.Errorf("failed to get risk: %w", err)
}
return nil, types.GetRiskOutput{
Risk: types.NewRisk(risk),
}, nil
}
func (r *Resolver) AddRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddRiskInput) (*mcp.CallToolResult, types.AddRiskOutput, error) {
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionCreateRisk)
svc := r.ProboService(ctx, input.OrganizationID)
risk, err := svc.Risks.Create(
ctx,
probo.CreateRiskRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: input.Description,
Category: input.Category,
Treatment: input.Treatment,
InherentLikelihood: input.InherentLikelihood,
InherentImpact: input.InherentImpact,
ResidualLikelihood: input.ResidualLikelihood,
ResidualImpact: input.ResidualImpact,
},
)
if err != nil {
return nil, types.AddRiskOutput{}, fmt.Errorf("failed to create risk: %w", err)
}
return nil, types.AddRiskOutput{
Risk: types.NewRisk(risk),
}, nil
}
func (r *Resolver) UpdateRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateRiskInput) (*mcp.CallToolResult, types.UpdateRiskOutput, error) {
r.MustBeAuthorized(ctx, input.ID, authz.ActionUpdateRisk)
svc := r.ProboService(ctx, input.ID)
risk, err := svc.Risks.Update(
ctx,
probo.UpdateRiskRequest{
ID: input.ID,
Name: input.Name,
Description: UnwrapOmittable(input.Description),
Category: input.Category,
Treatment: input.Treatment,
OwnerID: UnwrapOmittable(input.OwnerID),
InherentLikelihood: input.InherentLikelihood,
InherentImpact: input.InherentImpact,
ResidualLikelihood: input.ResidualLikelihood,
ResidualImpact: input.ResidualImpact,
},
)
if err != nil {
return nil, types.UpdateRiskOutput{}, fmt.Errorf("failed to update risk: %w", err)
}
return nil, types.UpdateRiskOutput{
Risk: types.NewRisk(risk),
}, nil
}

View File

@@ -17,6 +17,10 @@ type ResolverInterface interface {
UpdateVendorTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateVendorInput) (*mcp.CallToolResult, types.UpdateVendorOutput, error)
GetPeopleTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetPeopleInput) (*mcp.CallToolResult, types.GetPeopleOutput, error)
AddPeopleTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddPeopleInput) (*mcp.CallToolResult, types.AddPeopleOutput, error)
ListRisksTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListRisksInput) (*mcp.CallToolResult, types.ListRisksOutput, error)
GetRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetRiskInput) (*mcp.CallToolResult, types.GetRiskOutput, error)
AddRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddRiskInput) (*mcp.CallToolResult, types.AddRiskOutput, error)
UpdateRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateRiskInput) (*mcp.CallToolResult, types.UpdateRiskOutput, error)
}
// New creates a new MCP server instance with all handlers registered.
@@ -106,4 +110,44 @@ func registerToolHandlers(server *mcp.Server, resolver ResolverInterface) {
},
resolver.AddPeopleTool,
)
mcp.AddTool(
server,
&mcp.Tool{
Name: "listRisks",
Description: "List all risks for the organization",
InputSchema: types.ListRisksToolInputSchema,
OutputSchema: types.ListRisksToolOutputSchema,
},
resolver.ListRisksTool,
)
mcp.AddTool(
server,
&mcp.Tool{
Name: "getRisk",
Description: "Get a risk by ID",
InputSchema: types.GetRiskToolInputSchema,
OutputSchema: types.GetRiskToolOutputSchema,
},
resolver.GetRiskTool,
)
mcp.AddTool(
server,
&mcp.Tool{
Name: "addRisk",
Description: "Add a new risk to the organization",
InputSchema: types.AddRiskToolInputSchema,
OutputSchema: types.AddRiskToolOutputSchema,
},
resolver.AddRiskTool,
)
mcp.AddTool(
server,
&mcp.Tool{
Name: "updateRisk",
Description: "Update an existing risk",
InputSchema: types.UpdateRiskToolInputSchema,
OutputSchema: types.UpdateRiskToolOutputSchema,
},
resolver.UpdateRiskTool,
)
}

View File

@@ -397,6 +397,280 @@ components:
people:
$ref: "#/components/schemas/People"
RiskOrderField:
type: string
enum:
- CREATED_AT
- UPDATED_AT
- NAME
- CATEGORY
- TREATMENT
- INHERENT_RISK_SCORE
- RESIDUAL_RISK_SCORE
- OWNER_FULL_NAME
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.RiskOrderField
RiskOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/RiskOrderField"
description: Risk order field
direction:
$ref: "#/components/schemas/OrderDirection"
description: Risk order direction
RiskTreatment:
type: string
enum:
- MITIGATED
- ACCEPTED
- AVOIDED
- TRANSFERRED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.RiskTreatment
Risk:
type: object
required:
- id
- organization_id
- name
- category
- treatment
- inherent_likelihood
- inherent_impact
- inherent_risk_score
- residual_likelihood
- residual_impact
- residual_risk_score
- note
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Risk ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
snapshot_id:
type:
- string
- "null"
description: Snapshot ID
name:
type: string
description: Risk name
description:
type:
- string
- "null"
description: Risk description
category:
type: string
description: Risk category
treatment:
$ref: "#/components/schemas/RiskTreatment"
description: Risk treatment
inherent_likelihood:
type: integer
description: Inherent likelihood
inherent_impact:
type: integer
description: Inherent impact
inherent_risk_score:
type: integer
description: Inherent risk score
residual_likelihood:
type: integer
description: Residual likelihood
residual_impact:
type: integer
description: Residual impact
residual_risk_score:
type: integer
description: Residual risk score
note:
type: string
description: Risk note
owner_id:
type:
- string
- "null"
description: Owner ID
created_at:
type: string
format: date-time
description: Creation timestamp
updated_at:
type: string
format: date-time
description: Update timestamp
ListRisksInput:
type: object
required:
- organization_id
properties:
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
order_by:
$ref: "#/components/schemas/RiskOrderBy"
description: Risk order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
filter:
type: object
properties:
query:
type: string
description: Search query
snapshot_id:
$ref: "#/components/schemas/GID"
description: Snapshot ID
ListRisksOutput:
type: object
required:
- risks
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
risks:
type: array
items:
$ref: "#/components/schemas/Risk"
GetRiskInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Risk ID
GetRiskOutput:
type: object
required:
- risk
properties:
risk:
$ref: "#/components/schemas/Risk"
AddRiskInput:
type: object
required:
- organization_id
- name
- category
- treatment
- inherent_likelihood
- inherent_impact
properties:
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
name:
type: string
description: Risk name
description:
type: string
description: Risk description
category:
type: string
description: Risk category
owner_id:
$ref: "#/components/schemas/GID"
description: Owner ID
treatment:
$ref: "#/components/schemas/RiskTreatment"
description: Risk treatment
inherent_likelihood:
type: integer
description: Inherent likelihood
inherent_impact:
type: integer
description: Inherent impact
residual_likelihood:
type: integer
description: Residual likelihood
residual_impact:
type: integer
description: Residual impact
note:
type: string
description: Risk note
AddRiskOutput:
type: object
required:
- risk
properties:
risk:
$ref: "#/components/schemas/Risk"
UpdateRiskInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Risk ID
name:
type: string
description: Risk name
description:
type: ["string", "null"]
description: Risk description
go.probo.inc/mcpgen/omittable: true
category:
type: string
description: Risk category
owner_id:
anyOf:
- type: string
$ref: "#/components/schemas/GID"
- type: "null"
description: Owner ID
go.probo.inc/mcpgen/omittable: true
treatment:
$ref: "#/components/schemas/RiskTreatment"
description: Risk treatment
inherent_likelihood:
type: integer
description: Inherent likelihood
inherent_impact:
type: integer
description: Inherent impact
residual_likelihood:
type: integer
description: Residual likelihood
residual_impact:
type: integer
description: Residual impact
note:
type: string
description: Risk note
UpdateRiskOutput:
type: object
required:
- risk
properties:
risk:
$ref: "#/components/schemas/Risk"
tools:
- name: listOrganizations
description: List all organizations the user has access to
@@ -447,3 +721,31 @@ tools:
$ref: "#/components/schemas/AddPeopleInput"
outputSchema:
$ref: "#/components/schemas/AddPeopleOutput"
- name: listRisks
description: List all risks for the organization
readonly: true
inputSchema:
$ref: "#/components/schemas/ListRisksInput"
outputSchema:
$ref: "#/components/schemas/ListRisksOutput"
- name: getRisk
description: Get a risk by ID
readonly: true
inputSchema:
$ref: "#/components/schemas/GetRiskInput"
outputSchema:
$ref: "#/components/schemas/GetRiskOutput"
- name: addRisk
description: Add a new risk to the organization
readonly: false
inputSchema:
$ref: "#/components/schemas/AddRiskInput"
outputSchema:
$ref: "#/components/schemas/AddRiskOutput"
- name: updateRisk
description: Update an existing risk
readonly: false
inputSchema:
$ref: "#/components/schemas/UpdateRiskInput"
outputSchema:
$ref: "#/components/schemas/UpdateRiskOutput"

View File

@@ -21,7 +21,7 @@ import (
)
func NewCursor[O page.OrderField](
first *int64,
first *int,
after *page.CursorKey,
orderBy page.OrderBy[O],
) *page.Cursor[O] {

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2025 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 NewRisk(r *coredata.Risk) *Risk {
return &Risk{
ID: r.ID,
Name: r.Name,
Description: r.Description,
Category: r.Category,
Treatment: r.Treatment,
InherentLikelihood: r.InherentLikelihood,
InherentImpact: r.InherentImpact,
}
}
func NewListRisksOutput(riskPage *page.Page[*coredata.Risk, coredata.RiskOrderField]) ListRisksOutput {
risks := make([]*Risk, 0, len(riskPage.Data))
for _, v := range riskPage.Data {
risks = append(risks, NewRisk(v))
}
var nextCursor *page.CursorKey
if len(riskPage.Data) > 0 {
cursorKey := riskPage.Data[len(riskPage.Data)-1].CursorKey(riskPage.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListRisksOutput{
NextCursor: nextCursor,
Risks: risks,
}
}

View File

@@ -14,16 +14,24 @@ import (
var (
AddPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","full_name","primary_email_address","kind"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"type":"string","description":"Contract end date","format":"date-time"},"contract_start_date":{"type":"string","description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"type":"string","description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"}}}`)
AddPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
AddRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name","category","treatment","inherent_likelihood","inherent_impact"],"properties":{"category":{"type":"string","description":"Risk category"},"description":{"type":"string","description":"Risk description"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]}}}`)
AddRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"description":"Owner ID"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
AddVendorToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"type":"string","description":"Vendor description"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}`)
AddVendorToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendor"],"properties":{"vendor":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
GetPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
GetPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
GetRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`)
GetRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"description":"Owner ID"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
ListOrganizationsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organization_id":{"type":"string","format":"string"}}}`)
ListOrganizationsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organizations":{"type":"array","items":{"type":"object","required":["id","name","description","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Organization description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Organization name"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
ListPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"exclude_contract_ended":{"type":"boolean","description":"Exclude people with ended contracts"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["asc","desc"]},"field":{"type":"string","enum":["CREATED_AT","FULL_NAME","KIND"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
ListPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"next_cursor":{"type":"string","format":"string"},"people":{"type":"array","items":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
ListRisksToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["asc","desc"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME","CATEGORY","TREATMENT","INHERENT_RISK_SCORE","RESIDUAL_RISK_SCORE","OWNER_FULL_NAME"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
ListRisksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risks"],"properties":{"next_cursor":{"type":"string","format":"string"},"risks":{"type":"array","items":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"description":"Owner ID"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
ListVendorsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["asc","desc"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`)
ListVendorsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendors"],"properties":{"next_cursor":{"type":"string","format":"string"},"vendors":{"type":"array","items":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`)
UpdateRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"category":{"type":"string","description":"Risk category"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]}}}`)
UpdateRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"description":"Owner ID"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
UpdateVendorToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"description":{"type":"string","description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"}}}`)
UpdateVendorToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["vendor"],"properties":{"vendor":{"type":"object","required":["id","name","organization_id","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Vendor description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`)
)
@@ -53,6 +61,37 @@ type AddPeopleOutput struct {
People *People `json:"people"`
}
// AddRiskInput represents the schema
type AddRiskInput struct {
// Risk category
Category string `json:"category"`
// Risk description
Description *string `json:"description,omitempty"`
// Inherent impact
InherentImpact int `json:"inherent_impact"`
// Inherent likelihood
InherentLikelihood int `json:"inherent_likelihood"`
// Risk name
Name string `json:"name"`
// Risk note
Note *string `json:"note,omitempty"`
// Organization ID
OrganizationID gid.GID `json:"organization_id"`
// Owner ID
OwnerID *gid.GID `json:"owner_id,omitempty"`
// Residual impact
ResidualImpact *int `json:"residual_impact,omitempty"`
// Residual likelihood
ResidualLikelihood *int `json:"residual_likelihood,omitempty"`
// Risk treatment
Treatment coredata.RiskTreatment `json:"treatment"`
}
// AddRiskOutput represents the schema
type AddRiskOutput struct {
Risk *Risk `json:"risk"`
}
// AddVendorInput represents the schema
type AddVendorInput struct {
// Creation timestamp
@@ -83,6 +122,17 @@ type GetPeopleOutput struct {
People *People `json:"people"`
}
// GetRiskInput represents the schema
type GetRiskInput struct {
// Risk ID
ID gid.GID `json:"id"`
}
// GetRiskOutput represents the schema
type GetRiskOutput struct {
Risk *Risk `json:"risk"`
}
// ListOrganizationsInput represents the schema
type ListOrganizationsInput struct {
// Organization ID
@@ -104,7 +154,7 @@ type ListPeopleInput struct {
// Organization ID
OrganizationID gid.GID `json:"organization_id"`
// Page size
Size *int64 `json:"size,omitempty"`
Size *int `json:"size,omitempty"`
}
// ListPeopleOutput represents the schema
@@ -114,6 +164,26 @@ type ListPeopleOutput struct {
People []*People `json:"people"`
}
// ListRisksInput represents the schema
type ListRisksInput struct {
// Page cursor
Cursor *page.CursorKey `json:"cursor,omitempty"`
Filter *ListRisksInputFilter `json:"filter,omitempty"`
// Risk order by
OrderBy *RiskOrderBy `json:"order_by,omitempty"`
// Organization ID
OrganizationID gid.GID `json:"organization_id"`
// Page size
Size *int `json:"size,omitempty"`
}
// ListRisksOutput represents the schema
type ListRisksOutput struct {
// Next cursor
NextCursor *page.CursorKey `json:"next_cursor,omitempty"`
Risks []*Risk `json:"risks"`
}
// ListVendorsInput represents the schema
type ListVendorsInput struct {
// Page cursor
@@ -124,7 +194,7 @@ type ListVendorsInput struct {
// Organization ID
OrganizationID gid.GID `json:"organization_id"`
// Page size
Size *int64 `json:"size,omitempty"`
Size *int `json:"size,omitempty"`
}
// ListVendorsOutput represents the schema
@@ -182,6 +252,83 @@ type PeopleOrderBy struct {
Field coredata.PeopleOrderField `json:"field"`
}
// Risk represents the schema
type Risk struct {
// Risk category
Category string `json:"category"`
// Creation timestamp
CreatedAt time.Time `json:"created_at"`
// Risk description
Description *string `json:"description,omitempty"`
// Risk ID
ID gid.GID `json:"id"`
// Inherent impact
InherentImpact int `json:"inherent_impact"`
// Inherent likelihood
InherentLikelihood int `json:"inherent_likelihood"`
// Inherent risk score
InherentRiskScore int `json:"inherent_risk_score"`
// Risk name
Name string `json:"name"`
// Risk note
Note string `json:"note"`
// Organization ID
OrganizationID gid.GID `json:"organization_id"`
// Owner ID
OwnerID *string `json:"owner_id,omitempty"`
// Residual impact
ResidualImpact int `json:"residual_impact"`
// Residual likelihood
ResidualLikelihood int `json:"residual_likelihood"`
// Residual risk score
ResidualRiskScore int `json:"residual_risk_score"`
// Snapshot ID
SnapshotID *string `json:"snapshot_id,omitempty"`
// Risk treatment
Treatment coredata.RiskTreatment `json:"treatment"`
// Update timestamp
UpdatedAt time.Time `json:"updated_at"`
}
// RiskOrderBy represents the schema
type RiskOrderBy struct {
// Risk order direction
Direction page.OrderDirection `json:"direction"`
// Risk order field
Field coredata.RiskOrderField `json:"field"`
}
// UpdateRiskInput represents the schema
type UpdateRiskInput struct {
// Risk category
Category *string `json:"category,omitempty"`
// Risk description
Description mcp.Omittable[*string] `json:"description,omitempty"`
// Risk ID
ID gid.GID `json:"id"`
// Inherent impact
InherentImpact *int `json:"inherent_impact,omitempty"`
// Inherent likelihood
InherentLikelihood *int `json:"inherent_likelihood,omitempty"`
// Risk name
Name *string `json:"name,omitempty"`
// Risk note
Note *string `json:"note,omitempty"`
// Owner ID
OwnerID mcp.Omittable[*gid.GID] `json:"owner_id,omitempty"`
// Residual impact
ResidualImpact *int `json:"residual_impact,omitempty"`
// Residual likelihood
ResidualLikelihood *int `json:"residual_likelihood,omitempty"`
// Risk treatment
Treatment *coredata.RiskTreatment `json:"treatment,omitempty"`
}
// UpdateRiskOutput represents the schema
type UpdateRiskOutput struct {
Risk *Risk `json:"risk"`
}
// UpdateVendorInput represents the schema
type UpdateVendorInput struct {
// Vendor description
@@ -227,6 +374,14 @@ type ListPeopleInputFilter struct {
ExcludeContractEnded *bool `json:"exclude_contract_ended,omitempty"`
}
// ListRisksInputFilter represents the schema
type ListRisksInputFilter struct {
// Search query
Query *string `json:"query,omitempty"`
// Snapshot ID
SnapshotID *gid.GID `json:"snapshot_id,omitempty"`
}
// ListVendorsInputFilter represents the schema
type ListVendorsInputFilter struct {
// Snapshot ID

View File

@@ -7,6 +7,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/modelcontextprotocol/go-sdk/mcp"
"go.gearno.de/kit/log"
mcpgenmcp "go.probo.inc/mcpgen/mcp"
"go.probo.inc/probo/pkg/auth"
"go.probo.inc/probo/pkg/authz"
"go.probo.inc/probo/pkg/gid"
@@ -65,3 +66,11 @@ func NewMux(logger *log.Logger, proboSvc *probo.Service, authSvc *auth.Service,
return r
}
func UnwrapOmittable[T any](field mcpgenmcp.Omittable[T]) *T {
if !field.IsSet() {
return nil
}
value, _ := field.Value()
return &value
}