Add approval quorum and decision read tools

Expose document version approval quorums and decisions
through MCP, CLI, and n8n. This lets users inspect who
approved or rejected a document version, including the
rejection comment, without relying solely on the audit
log.

MCP tools: listDocumentVersionApprovalQuorums,
getDocumentVersionApprovalQuorum,
listDocumentVersionApprovalDecisions,
getDocumentVersionApprovalDecision.

CLI commands: document list-approval-quorums,
view-approval-quorum, list-approval-decisions,
view-approval-decision.

n8n operations: Get/Get Many Approval Quorums and
Approval Decisions on the Document resource.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-20 10:10:39 +02:00
parent 685e9d2e69
commit 5b8918bd5a
14 changed files with 1528 additions and 0 deletions

View File

@@ -4048,3 +4048,91 @@ func (r *Resolver) ListWebhookEventsTool(ctx context.Context, req *mcp.CallToolR
return nil, types.NewListWebhookEventsOutput(page), nil
}
func (r *Resolver) ListDocumentVersionApprovalQuorumsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionApprovalQuorumsInput) (*mcp.CallToolResult, types.ListDocumentVersionApprovalQuorumsOutput, error) {
r.MustAuthorize(ctx, input.DocumentVersionID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.DocumentVersionID)
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
Field: coredata.DocumentVersionApprovalQuorumOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalQuorumOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
p, err := svc.DocumentApprovals.ListQuorums(ctx, input.DocumentVersionID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list approval quorums: %w", err))
}
return nil, types.NewListDocumentVersionApprovalQuorumsOutput(p), nil
}
func (r *Resolver) GetDocumentVersionApprovalQuorumTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentVersionApprovalQuorumInput) (*mcp.CallToolResult, types.GetDocumentVersionApprovalQuorumOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.ID)
quorum, err := svc.DocumentApprovals.GetQuorum(ctx, input.ID)
if err != nil {
panic(fmt.Errorf("cannot get approval quorum: %w", err))
}
return nil, types.GetDocumentVersionApprovalQuorumOutput{
ApprovalQuorum: types.NewDocumentVersionApprovalQuorum(quorum),
}, nil
}
func (r *Resolver) ListDocumentVersionApprovalDecisionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListDocumentVersionApprovalDecisionsInput) (*mcp.CallToolResult, types.ListDocumentVersionApprovalDecisionsOutput, error) {
r.MustAuthorize(ctx, input.QuorumID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.QuorumID)
pageOrderBy := page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
Field: coredata.DocumentVersionApprovalDecisionOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if input.OrderBy != nil {
pageOrderBy = page.OrderBy[coredata.DocumentVersionApprovalDecisionOrderField]{
Field: input.OrderBy.Field,
Direction: input.OrderBy.Direction,
}
}
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
var states []coredata.DocumentVersionApprovalDecisionState
if input.Filter != nil {
states = input.Filter.States
}
filter := coredata.NewDocumentVersionApprovalDecisionFilter(states)
p, err := svc.DocumentApprovals.ListDecisions(ctx, input.QuorumID, cursor, filter)
if err != nil {
panic(fmt.Errorf("cannot list approval decisions: %w", err))
}
return nil, types.NewListDocumentVersionApprovalDecisionsOutput(p), nil
}
func (r *Resolver) GetDocumentVersionApprovalDecisionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentVersionApprovalDecisionInput) (*mcp.CallToolResult, types.GetDocumentVersionApprovalDecisionOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionDocumentVersionApprovalList)
svc := r.ProboService(ctx, input.ID)
decision, err := svc.DocumentApprovals.GetDecision(ctx, input.ID)
if err != nil {
panic(fmt.Errorf("cannot get approval decision: %w", err))
}
return nil, types.GetDocumentVersionApprovalDecisionOutput{
ApprovalDecision: types.NewDocumentVersionApprovalDecision(decision),
}, nil
}

View File

@@ -82,6 +82,32 @@ components:
$ref: "#/components/schemas/OrderDirection"
description: Vendor order direction
DocumentVersionApprovalQuorumOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumOrderField"
description: Order field
direction:
$ref: "#/components/schemas/OrderDirection"
description: Order direction
DocumentVersionApprovalDecisionOrderBy:
type: object
required:
- field
- direction
properties:
field:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionOrderField"
description: Order field
direction:
$ref: "#/components/schemas/OrderDirection"
description: Order direction
ProfileOrderBy:
type: object
required:
@@ -5164,6 +5190,36 @@ components:
- PUBLISHED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionStatus
DocumentVersionApprovalQuorumStatus:
type: string
enum:
- PENDING
- APPROVED
- REJECTED
- VOIDED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumStatus
DocumentVersionApprovalQuorumOrderField:
type: string
enum:
- CREATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalQuorumOrderField
DocumentVersionApprovalDecisionState:
type: string
enum:
- PENDING
- APPROVED
- REJECTED
- VOIDED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionState
DocumentVersionApprovalDecisionOrderField:
type: string
enum:
- CREATED_AT
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.DocumentVersionApprovalDecisionOrderField
DocumentStatus:
type: string
enum:
@@ -5362,6 +5418,83 @@ components:
format: date-time
description: Update timestamp
DocumentVersionApprovalQuorum:
type: object
required:
- id
- organization_id
- version_id
- status
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
version_id:
$ref: "#/components/schemas/GID"
description: Document version ID
status:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumStatus"
description: Approval quorum status
created_at:
type: string
format: date-time
description: Creation timestamp
updated_at:
type: string
format: date-time
description: Update timestamp
DocumentVersionApprovalDecision:
type: object
required:
- id
- organization_id
- quorum_id
- approver_id
- state
- created_at
- updated_at
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval decision ID
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
quorum_id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
approver_id:
$ref: "#/components/schemas/GID"
description: Approver profile ID
state:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionState"
description: Decision state
comment:
type:
- string
- "null"
description: Approval or rejection comment
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
DocumentVersionSignature:
type: object
required:
@@ -5876,6 +6009,110 @@ components:
document_version:
$ref: "#/components/schemas/DocumentVersion"
ListDocumentVersionApprovalQuorumsInput:
type: object
required:
- document_version_id
properties:
document_version_id:
$ref: "#/components/schemas/GID"
description: Document version ID
order_by:
$ref: "#/components/schemas/DocumentVersionApprovalQuorumOrderBy"
description: Order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
ListDocumentVersionApprovalQuorumsOutput:
type: object
required:
- approval_quorums
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
approval_quorums:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalQuorum"
GetDocumentVersionApprovalQuorumInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
GetDocumentVersionApprovalQuorumOutput:
type: object
required:
- approval_quorum
properties:
approval_quorum:
$ref: "#/components/schemas/DocumentVersionApprovalQuorum"
ListDocumentVersionApprovalDecisionsInput:
type: object
required:
- quorum_id
properties:
quorum_id:
$ref: "#/components/schemas/GID"
description: Approval quorum ID
order_by:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionOrderBy"
description: Order by
size:
type: integer
description: Page size
cursor:
$ref: "#/components/schemas/CursorKey"
description: Page cursor
filter:
type: object
properties:
states:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalDecisionState"
description: Filter by decision states
ListDocumentVersionApprovalDecisionsOutput:
type: object
required:
- approval_decisions
properties:
next_cursor:
$ref: "#/components/schemas/CursorKey"
description: Next cursor
approval_decisions:
type: array
items:
$ref: "#/components/schemas/DocumentVersionApprovalDecision"
GetDocumentVersionApprovalDecisionInput:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/GID"
description: Approval decision ID
GetDocumentVersionApprovalDecisionOutput:
type: object
required:
- approval_decision
properties:
approval_decision:
$ref: "#/components/schemas/DocumentVersionApprovalDecision"
SendSigningNotificationsInput:
type: object
required:
@@ -9175,3 +9412,39 @@ tools:
$ref: "#/components/schemas/ListWebhookEventsInput"
outputSchema:
$ref: "#/components/schemas/ListWebhookEventsOutput"
- name: listDocumentVersionApprovalQuorums
description: List approval quorums for a document version
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalQuorumsInput"
outputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalQuorumsOutput"
- name: getDocumentVersionApprovalQuorum
description: Get a document version approval quorum by ID
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalQuorumInput"
outputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalQuorumOutput"
- name: listDocumentVersionApprovalDecisions
description: List approval decisions for an approval quorum
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalDecisionsInput"
outputSchema:
$ref: "#/components/schemas/ListDocumentVersionApprovalDecisionsOutput"
- name: getDocumentVersionApprovalDecision
description: Get a document version approval decision by ID
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalDecisionInput"
outputSchema:
$ref: "#/components/schemas/GetDocumentVersionApprovalDecisionOutput"

View File

@@ -190,3 +190,64 @@ func NewListDocumentVersionSignaturesOutput(signaturePage *page.Page[*coredata.D
DocumentVersionSignatures: signatures,
}
}
func NewDocumentVersionApprovalQuorum(q *coredata.DocumentVersionApprovalQuorum) *DocumentVersionApprovalQuorum {
return &DocumentVersionApprovalQuorum{
ID: q.ID,
OrganizationID: q.OrganizationID,
VersionID: q.VersionID,
Status: q.Status,
CreatedAt: q.CreatedAt,
UpdatedAt: q.UpdatedAt,
}
}
func NewListDocumentVersionApprovalQuorumsOutput(quorumPage *page.Page[*coredata.DocumentVersionApprovalQuorum, coredata.DocumentVersionApprovalQuorumOrderField]) ListDocumentVersionApprovalQuorumsOutput {
quorums := make([]*DocumentVersionApprovalQuorum, 0, len(quorumPage.Data))
for _, q := range quorumPage.Data {
quorums = append(quorums, NewDocumentVersionApprovalQuorum(q))
}
var nextCursor *page.CursorKey
if len(quorumPage.Data) > 0 {
cursorKey := quorumPage.Data[len(quorumPage.Data)-1].CursorKey(quorumPage.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListDocumentVersionApprovalQuorumsOutput{
NextCursor: nextCursor,
ApprovalQuorums: quorums,
}
}
func NewDocumentVersionApprovalDecision(d *coredata.DocumentVersionApprovalDecision) *DocumentVersionApprovalDecision {
return &DocumentVersionApprovalDecision{
ID: d.ID,
OrganizationID: d.OrganizationID,
QuorumID: d.QuorumID,
ApproverID: d.ApproverID,
State: d.State,
Comment: d.Comment,
DecidedAt: d.DecidedAt,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
}
}
func NewListDocumentVersionApprovalDecisionsOutput(decisionPage *page.Page[*coredata.DocumentVersionApprovalDecision, coredata.DocumentVersionApprovalDecisionOrderField]) ListDocumentVersionApprovalDecisionsOutput {
decisions := make([]*DocumentVersionApprovalDecision, 0, len(decisionPage.Data))
for _, d := range decisionPage.Data {
decisions = append(decisions, NewDocumentVersionApprovalDecision(d))
}
var nextCursor *page.CursorKey
if len(decisionPage.Data) > 0 {
cursorKey := decisionPage.Data[len(decisionPage.Data)-1].CursorKey(decisionPage.Cursor.OrderBy.Field)
nextCursor = &cursorKey
}
return ListDocumentVersionApprovalDecisionsOutput{
NextCursor: nextCursor,
ApprovalDecisions: decisions,
}
}