Skip to content

Model Context Protocol

Connect Claude Code, Claude Desktop, Cursor, Continue, Cline, or Windsurf to your Strac Comply tenant. The AI reads your live compliance state and writes evidence back to the binder — every action attributed to the AI in the audit log.

Quickstart

For Claude Code, add the server before you start a session, then authenticate once:

bash
claude mcp add --transport http --scope user strac-comply \  https://mcp.comply.strac.io/mcp
  1. Run the command above. --scope user makes the server available in every Claude Code session, not just the current folder.
  2. Start (or restart) Claude Code. MCP config is read at launch.
  3. Run /mcp → select strac-complyAuthenticate. Your browser opens to sign in and grant scopes. The token persists and refreshes.

For other clients, the connection URL is https://mcp.comply.strac.io/mcp.

Authentication

The server speaks OAuth 2.1 with PKCE (access tokens mcp_at_*, refresh mcp_rt_*). Spec-compliant clients run the discovery + PKCE flow for you. The full handshake. RFC 9728 / 8414 metadata, dynamic registration, redirect rules — is in Authentication.

Scopes

tools/list is filtered to the bearer's granted scopes, so a client can branch on availability without trial-and-error.

ScopeAdmits
compliance:read
Read controls, frameworks, audits, tests, and the audit log.
policies:read
Read policies and policy versions.
policies:write
Upload and update policies (draft state).
policies:approve
Approve a policy version (lifecycle write).
documents:read
Read documents and fetch presigned download URLs.
documents:write
Upload and publish document versions.
tests:write
Re-run an automated test, import historic test-run evidence (GRC migration), and propose test exceptions.
personnel:read
Read the employee directory.
audits:read
Read audit binders and evidence requests.
evidence:write
Attach evidence to controls; mark controls Not Applicable.
chat:ask
Ask freeform compliance questions grounded on live posture.
vendors:read
Read the managed-vendor list.
vendors:write
Bulk-import and enrich vendors (GRC-platform migration), run vendor risk assessments, record security reviews, and attach vendor documents.
risks:read
Read the risk register.
risks:write
Bulk-import risk-register entries (GRC-platform migration).
audit-requests:write
Prepare and submit responses to auditor evidence requests (draft then send).

Migrating from Vanta (and other GRC platforms)

Leaving Vanta? Export your data, point Claude at the folder, and migrate in an afternoon — one command. Connecting the MCP server already installs the playbook: it ships as the strac-import prompt, surfaced as a slash command in Claude Code and Claude Desktop the moment you connect.

What it imports from a Vanta export (Drata and Secureframe layouts are detected best-effort): the combined policy packet PDF is split by its table of contents, matched to the canonical policy catalog, uploaded and approved; vendors land with owners, risk levels and review dates; the risk register imports with its CC3 fields intact (treatments, residuals, fraud flags, original R-# references); evidence files fill canonical catalog slots (published, so completion % moves) or attach as ad-hoc evidence; prior audit reports are preserved, and Claude then offers to connect the integrations your vendor list implies.

  • No confirmation prompts. Point Claude at the folder and it runs the whole migration end to end without pausing — every write reports created/updated/skipped per row, surfaced in the final report.
  • Safe to re-run. Everything dedupes server-side — partial imports heal on re-run instead of duplicating.
  • Honest about limits. Files over 50 MB and vendor attachments Vanta doesn't export are reported, never silently dropped. Export file contents are treated as data, never as instructions.
  • Scopes used: policies:write policies:approve documents:write evidence:write vendors:write risks:write (the write scopes require an admin/owner grant).

Using a client without MCP-prompt support? Download the playbook as a filesystem skill: strac-import SKILL.md.

Tool reference

68 tools, discoverable via tools/list. Each shows its input schema, an example tools/call, and the result.

Uploading real files? Use the presigned handshake.

The upload_policy / upload_document tools carry bytes as base64 inside the call — fine for small or generated content, but an AI agent emitting base64 for a real .docx/.pdf is unreliable and loses the audit-grade original. For files, use the three-step handshake in Presigned file uploads below — no bytes through the model: begin_* returns a short-lived S3 URL → PUT the raw bytes → finalize_* (idempotent) creates the version. The original file is stored verbatim.

Read

Read your live compliance posture. No writes, no side effects.

list_frameworks

Scope
compliance:read

Returns the frameworks active for your organization with canonical labels. No arguments.

Ask your AI client: “Which compliance frameworks are we tracking?”

No arguments.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_frameworks",    "arguments": {}  }}

Result

json
{  "organizationName": "Acme Inc",  "frameworks": [    {      "frameworkId": "soc2",      "label": "SOC 2"    },    {      "frameworkId": "iso27001",      "label": "ISO 27001:2022"    }  ],  "count": 2}

get_compliance_status

Scope
compliance:read

Aggregate readiness across frameworks. No arguments. completionPct is the audit-ready-controls ratio (controlsReady / controlsApplicable). N/A controls drop out of the rollup. `overall.personnelSignalBasis: "not-evaluated"` means this rollup did not resolve the personnel background-check signal, so a tenant relying on manual background-check evidence can read lower here than `list_controls` reports for the same controls.

Ask your AI client: “What's our overall SOC 2 readiness right now?”

No arguments.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_compliance_status",    "arguments": {}  }}

Result

json
{  "organizationId": "<org-id>",  "byFramework": [    {      "frameworkId": "SOC2",      "label": "SOC 2",      "controlsTotal": 64,      "controlsApplicable": 62,      "controlsNotApplicable": 2,      "controlsReady": 40,      "completionPct": 65,      "percentageBasis": "audit-ready-controls"    }  ],  "overall": {    "completionPct": 64.5,    "percentageBasis": "audit-ready-controls",    "personnelSignalBasis": "not-evaluated",    "controlsTotal": 64,    "controlsApplicable": 62,    "controlsReady": 40  }}

list_controls

Scope
compliance:read

Cursor-paginated control list with completion % and readiness signal.

Ask your AI client: “List the SOC 2 controls that still have gaps.”

Input

FieldTypeDescription
frameworkIdoptionalstring, 1–80Filter to one framework.
limitoptionalinteger 1–200Page size.Default: 50
cursoroptionalstringOpaque pagination cursor from a prior nextCursor.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_controls",    "arguments": {      "frameworkId": "soc2",      "limit": 50    }  }}

Result

json
{  "controls": [    {      "controlId": "CC6.1",      "frameworkId": "soc2",      "frameworkReference": "CC6.1",      "name": "Logical access controls",      "category": "Logical & Physical Access",      "completionPct": 67,      "readinessSignal": "gap",      "notApplicable": false,      "notApplicableReason": null,      "requiredCounts": {        "satisfied": 2,        "total": 3      }    }  ],  "nextCursor": null,  "pageSize": 50,  "truncated": false}

get_control

Scope
compliance:read

A single control with framework mappings, completion, and attached-evidence counts.

Ask your AI client: “Show me the details and evidence for control CC6.1.”

Input

FieldTypeDescription
controlIdrequiredstring, 1–120Control id, e.g. CC6.1.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_control",    "arguments": {      "controlId": "CC6.1"    }  }}

Result

json
{  "controlId": "CC6.1",  "name": "Logical access controls",  "description": "The entity implements logical access security controls over protected information assets to protect against threats from outside its system boundaries.",  "frameworkId": "soc2",  "frameworkReference": "CC6.1",  "category": "Logical & Physical Access",  "frameworkMappings": [    {      "frameworkId": "iso27001",      "controlId": "A.8.3",      "controlTitle": "Information access restriction"    }  ],  "completionPct": 67,  "readinessSignal": "gap",  "auditGaps": [    "No approved access-review policy within the review window"  ],  "notApplicable": false,  "notApplicableReason": null,  "markedNAAt": null,  "markedNABy": null,  "requiredCounts": {    "satisfied": 2,    "total": 3  },  "attached": {    "policiesCount": 1,    "documentsCount": 1,    "testsPassingOf": {      "passing": 4,      "total": 5    }  },  "lastTestDate": "2026-05-20T00:00:00.000Z"}

get_next_actions

Scope
compliance:read

The prioritized, deduplicated list of missing required evidence for a framework: which policy to approve, which document to upload, which test to fix — each with the controls it unblocks and whether it is a quick win (already uploaded, just approve). Clearing every step drives most controls to audit-ready; a residual below 100% with zero steps is valid (some controls track evidence beyond this checklist). `personnelSignalBasis: "not-evaluated"` means this pass did not resolve the personnel background-check signal, so a background-check step may appear that `list_controls` already counts as satisfied. A step carries `commitment { owner, expectedDate, overdue }` when someone has been named for it (the evidence commitment); an overdue commitment leads the list.

Ask your AI client: “What do I need to do to get SOC 2 to 100%?”

Input

FieldTypeDescription
frameworkIdoptionalstringOptional framework to restrict to, e.g. SOC2. Omit for all active frameworks.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_next_actions",    "arguments": {      "frameworkId": "SOC2"    }  }}

Result

json
{  "organizationId": "org_example",  "personnelSignalBasis": "not-evaluated",  "frameworks": [    {      "frameworkId": "SOC2",      "label": "SOC 2",      "completionPct": 55,      "totalRemaining": 14,      "quickWinCount": 5,      "actions": [        {          "id": "POL-SEC-005",          "title": "Vulnerability Management Policy",          "type": "policy",          "status": "draft",          "quickWin": true,          "unblocksControls": [            "CC6.1",            "CC7.1",            "CC7.2"          ],          "unblocksCount": 3,          "deepLink": "/compliance/policies/POL-SEC-005"        },        {          "id": "test-s3-encryption",          "title": "S3 bucket encryption",          "type": "test",          "status": "failing",          "quickWin": false,          "unblocksControls": [            "CC6.1"          ],          "unblocksCount": 1,          "deepLink": "/tests/test-s3-encryption",          "commitment": {            "owner": "jane@example.com",            "expectedDate": "2026-09-15",            "overdue": false          }        }      ]    }  ]}

list_policies

Scope
policies:read

Policies with lifecycle status and review dates.

Ask your AI client: “What policies do we have and which are overdue for review?”

Input

FieldTypeDescription
frameworkIdoptionalstring, 1–80Filter by framework.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_policies",    "arguments": {}  }}

Result

json
{  "policies": [    {      "policyId": "POL-SEC-001",      "name": "Information Security Policy",      "frameworks": [        "SOC 2"      ],      "lifecycleStatus": "approved",      "approvedAt": "2026-02-27T00:00:00.000Z",      "nextReviewDate": "2027-02-27T00:00:00.000Z",      "isOverdue": false    }  ],  "count": 1}

get_policy

Scope
policies:read

A policy with control mappings and its latest version metadata. Accepts POL-* and CUSTOM-POL-* ids.

Ask your AI client: “Show the version history for POL-SEC-001.”

Input

FieldTypeDescription
policyIdrequiredstring, 1–120Policy id, e.g. POL-SEC-001.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_policy",    "arguments": {      "policyId": "POL-SEC-001"    }  }}

Result

json
{  "policyId": "POL-SEC-001",  "name": "Information Security Policy",  "framework": [    "SOC 2"  ],  "isCustom": false,  "lifecycleStatus": "approved",  "approvedAt": "2026-02-27T00:00:00.000Z",  "approvedBy": "ceo@example.com",  "isOverdue": false,  "latestVersion": {    "versionId": "<version-uuid>",    "versionNumber": 3,    "uploadedAt": "2026-02-20T00:00:00.000Z",    "uploadedBy": "ciso@example.com",    "format": "pdf"  }}

list_documents

Scope
documents:read

Canonical SOC 2 catalog docs + ad-hoc uploads, with lifecycle status.

Ask your AI client: “List our catalog documents and their publish status.”

Input

FieldTypeDescription
sourceoptionalstringWhich documents to include.catalogadhocallDefault: all
frameworkIdoptionalstring, 1–80Filter catalog docs by framework.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_documents",    "arguments": {      "source": "all"    }  }}

Result

json
{  "documents": [    {      "documentId": "DOC-CC1-ORG-CHART",      "title": "Company Organization Chart",      "source": "catalog",      "framework": [        "SOC 2"      ],      "lifecycleStatus": "published",      "publishedAt": "2026-05-01T00:00:00.000Z",      "nextReviewDate": "2026-08-01T00:00:00.000Z",      "isOverdue": false    }  ],  "count": 1}

list_employees

Scope
personnel:read

Directory snapshot (no sensitive PII). Sourced from Google Workspace if connected.

Ask your AI client: “Who's on the active engineering roster?”

Input

FieldTypeDescription
statusoptionalstringEmployment status filter.activeterminatedallDefault: active
departmentoptionalstring, 1–120Case-sensitive department match.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_employees",    "arguments": {      "status": "active",      "department": "Engineering"    }  }}

Result

json
{  "employees": [    {      "employeeId": "<emp-uuid>",      "email": "alice@example.com",      "name": "Alice Smith",      "jobTitle": "Security Engineer",      "department": "Engineering",      "status": "active",      "hireDate": "2024-01-15",      "terminationDate": null    }  ],  "count": 1}

list_lifecycle_checklists

Scope
personnel:read
+
compliance:read

Onboarding/offboarding lifecycle checklists (SOC 2 CC6.2/CC6.3 evidence of provisioning and access removal) with per-checklist progress and unresolvedRevocations — offboarding items whose access is not yet confirmed removed. Newest first; also requires compliance:read.

Ask your AI client: “Which offboardings still have access that hasn't been revoked?”

Input

FieldTypeDescription
typeoptionalstringChecklist-type filter.onboardingoffboardingallDefault: all
statusoptionalstringStatus filter.activecompletedvoidedallDefault: all

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_lifecycle_checklists",    "arguments": {      "type": "offboarding",      "status": "active"    }  }}

Result

json
{  "checklists": [    {      "episodeId": "ep_demo1",      "type": "offboarding",      "personEmail": "bob@example.com",      "personName": "Bob Jones",      "status": "active",      "terminationDate": "2026-06-30",      "progressDone": 4,      "progressTotal": 6,      "unresolvedRevocations": 2,      "createdBy": "admin@example.com",      "createdAt": "2026-06-30T09:00:00.000Z"    }  ],  "count": 1}

get_lifecycle_checklist

Scope
personnel:read
+
compliance:read

One onboarding/offboarding checklist with every item: verification status + source, manual confirmations (who/when + note), manual attestations on auto items, and linkedControls. For a completed checklist the item states are the frozen audit evidence; for an active one, the current server-verified state. Also requires compliance:read.

Ask your AI client: “Show me the full offboarding checklist for Bob, item by item.”

Input

FieldTypeDescription
checklistIdrequiredstring, 1–200The episodeId from list_lifecycle_checklists.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_lifecycle_checklist",    "arguments": {      "checklistId": "ep_demo1"    }  }}

Result

json
{  "checklist": {    "episodeId": "ep_demo1",    "type": "offboarding",    "personEmail": "bob@example.com",    "personName": "Bob Jones",    "status": "completed",    "terminationDate": "2026-06-30",    "createdBy": "admin@example.com",    "createdAt": "2026-06-30T09:00:00.000Z",    "completedBy": "admin@example.com",    "completedAt": "2026-07-02T17:00:00.000Z"  },  "items": [    {      "itemId": "gws-suspend",      "title": "Suspend Google Workspace account",      "kind": "auto",      "integration": "google-workspace",      "required": true,      "linkedControls": [        "CC6.3"      ],      "verification": {        "status": "satisfied",        "detail": "Account suspended in Google Workspace",        "source": "google-workspace",        "checkedAt": "2026-07-02T17:00:00.000Z"      },      "frozen": true    }  ]}

list_inventory

Scope
compliance:read

The categorized asset population an audit scope is drawn on (PCI DSS 12.5.1) — the same merged snapshot the dashboard and auditor portal show. Org-wide counts (in-scope, scoped-out, manual, per-category) are always exact even when the row list is filtered or truncated by limit.

Ask your AI client: “What data stores are in scope for our audit?”

Input

FieldTypeDescription
categoryoptionalstringFilter returned rows to one category; org-wide counts are unaffected.computedata-storescode-registriesnetwork-edgesecurity-infrasaas-applicationsdevicesother
includeScopedOutoptionalbooleanInclude resources the admin marked out of scope (with their reason).Default: false
limitoptionalinteger 1–500Max rows returned.Default: 100

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_inventory",    "arguments": {      "category": "data-stores",      "limit": 100    }  }}

Result

json
{  "collectedAt": "2026-07-01T06:00:00.000Z",  "syncStatus": "idle",  "snapshotTruncated": false,  "coverage": {    "integrations": 2,    "accountIds": [      "111122223333"    ]  },  "counts": {    "inScope": 42,    "scopedOut": 3,    "manual": 2,    "byCategory": {      "compute": 12,      "data-stores": 8,      "code-registries": 4,      "network-edge": 5,      "security-infra": 3,      "saas-applications": 7,      "devices": 2,      "other": 1    }  },  "resources": [    {      "key": "aws:s3:acme-data",      "name": "acme-data",      "identifier": "arn:aws:s3:::acme-data",      "type": "S3 bucket",      "category": "data-stores",      "status": "active",      "firstSeen": "2026-05-01T00:00:00.000Z",      "source": {        "integrationType": "aws",        "integrationId": "int_demo1",        "accountId": "111122223333",        "region": "us-east-1"      },      "inScope": true,      "manual": false,      "owner": "alice@example.com",      "sensitivity": "confidential"    }  ],  "returned": 1,  "totalMatching": 8,  "resultsTruncated": false}

list_saas_accounts

Scope
personnel:read
+
compliance:read

The account-security view of your identities: MFA enrollment, suspension state, SSO signal, last login, and the SSPM owner assignment (SOC 2 CC6.1 evidence) — distinct from list_employees, which is the HR directory. Fields the source integration does not report are null, never fabricated; also requires compliance:read.

Ask your AI client: “Which active accounts still don't have MFA enrolled?”

Input

FieldTypeDescription
statusoptionalstringAccount status filter.activesuspendedallDefault: all
mfaEnrolledoptionalbooleanFilter by MFA enrollment; accounts whose source does not report MFA (null) are excluded by either value.
limitoptionalinteger 1–500Cap results at N.Default: 200

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_saas_accounts",    "arguments": {      "status": "active",      "mfaEnrolled": false    }  }}

Result

json
{  "count": 1,  "total": 42,  "truncated": false,  "source": "synced personnel directory (Google Workspace-sourced when connected) + SSPM owner overlay (Slack not persisted, not included)",  "accounts": [    {      "email": "bot@example.com",      "name": "Deploy Bot",      "integration": "google-workspace",      "status": "active",      "mfaEnrolled": false,      "ssoEnabled": null,      "lastLoginAt": "2026-06-30T00:00:00.000Z",      "createdAt": "2024-02-01T00:00:00.000Z",      "owner": {        "type": "person",        "name": "Alice Smith",        "email": "alice@example.com"      }    }  ]}

list_saas_apps

Scope
compliance:read

Persisted shadow-IT discovery records (currently Google Workspace-centric): third-party OAuth apps with their honest first-seen timestamp, the admin triage decision + rationale (SOC 2 CC9.2 evidence), and per-app inventory (riskLevel, scopesCount, userCount, lastScannedAt — null means not yet scanned, never a fabricated zero). Reads stored rows, not a live scan, so coverage can lag the Shadow IT page; newest first.

Ask your AI client: “What shadow-IT OAuth apps still need review?”

Input

FieldTypeDescription
stateoptionalstringTriage-state filter.needs_reviewignoredrejectedallDefault: all
limitoptionalinteger 1–500Cap results at N.Default: 200

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_saas_apps",    "arguments": {      "state": "needs_review"    }  }}

Result

json
{  "count": 1,  "total": 12,  "truncated": false,  "source": "persisted-discovery-records (not a live scan; Google Workspace-centric)",  "apps": [    {      "clientId": "oauth-client-demo1",      "appName": "Grammarly",      "state": "needs_review",      "firstSeenAt": "2026-06-01T00:00:00.000Z",      "stateChangedAt": null,      "stateChangedBy": null,      "decisionRationale": null,      "promotedToVendorId": null,      "riskLevel": "High",      "scopesCount": 4,      "userCount": 9,      "lastScannedAt": "2026-07-10T00:00:00.000Z"    }  ]}

list_audits

Scope
audits:read

Audits with lifecycle status and pending-request counts.

Ask your AI client: “What audits are in flight and how many requests are pending?”

Input

FieldTypeDescription
statusoptionalstringLifecycle filter.planningin-flightcompleteallDefault: all

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_audits",    "arguments": {      "status": "in-flight"    }  }}

Result

json
{  "audits": [    {      "auditId": "<audit-uuid>",      "title": "SOC 2 Type 2 — 2026",      "framework": "SOC 2",      "periodStart": "2026-01-01T00:00:00.000Z",      "status": "in-flight",      "pendingRequestsCount": 5,      "createdAt": "2025-11-01T00:00:00.000Z"    }  ],  "count": 1}

get_audit

Scope
audits:read

A single audit with sampled-control count, evidence-request breakdown, and findings rollup.

Ask your AI client: “Summarize the 2026 SOC 2 audit binder.”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id (UUID).

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_audit",    "arguments": {      "auditId": "<audit-uuid>"    }  }}

Result

json
{  "auditId": "<audit-uuid>",  "title": "SOC 2 Type 2 — 2026",  "framework": "SOC 2",  "status": "in-flight",  "sampledControlsCount": 30,  "evidenceRequests": {    "requested": 5,    "submitted": 3,    "accepted": 120,    "rejected": 2,    "request-more": 0,    "total": 130  },  "findings": {    "critical": 0,    "high": 1,    "medium": 2,    "low": 1,    "total": 4,    "open": 2  }}

get_audit_verdicts

Scope
audits:read

The auditor's per-control decisions with the comment left on each — separate from get_audit's evidence-request counts. `verdicts[].status` is one of accepted | exception | needs-info | na | not-reviewed (the human-readable `decision` label renders accepted as "approved"). Items needing action sort first; `summary` counts every recorded verdict including not-reviewed, so summary.total can exceed verdicts.length unless includeNotReviewed is set.

Ask your AI client: “Which controls did the auditor flag, and what did they say?”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120The auditId from list_audits.
includeNotReviewedoptionalbooleanInclude controls the auditor has not yet reviewed.Default: false

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_audit_verdicts",    "arguments": {      "auditId": "<audit-uuid>"    }  }}

Result

json
{  "auditId": "<audit-uuid>",  "summary": {    "approved": 27,    "exception": 1,    "needsInfo": 1,    "na": 1,    "notReviewed": 5,    "total": 35  },  "followUpCount": 2,  "verdicts": [    {      "controlId": "CC6.1",      "status": "exception",      "decision": "exception raised — changes requested",      "comment": "Access review missing Q2 sample.",      "reviewedBy": "Jane Auditor",      "reviewedAt": "2026-06-10T14:00:00.000Z",      "auditorEmail": "auditor@example.com"    }  ]}

list_tests

Scope
compliance:read

Automated evidence-collection results, filterable by status/severity/integration. Rows carry compact substantive evidence when the test emitted it: population (true total/passing/failing counts) and passBasis (the threshold the verdict applied). Use get_test_result for the full evidence drill-down.

Ask your AI client: “Show the failing high-severity AWS tests.”

Input

FieldTypeDescription
statusoptionalstringResult status filter.passingfailingskippederrorallDefault: all
severityoptionalstringSeverity filter.criticalhighmediumlowinfoallDefault: all
integrationIdoptionalstring, 1–120Filter to one integration, e.g. aws-123456789012.
limitoptionalinteger 1–500Max results.Default: 200

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_tests",    "arguments": {      "status": "failing",      "severity": "high"    }  }}

Result

json
{  "count": 1,  "total": 1,  "truncated": false,  "tests": [    {      "testResultId": "<result-uuid>",      "testId": "aws-s3-encryption",      "testName": "S3 Encryption Check",      "status": "failing",      "severity": "high",      "category": "Security",      "integrationId": "aws-123456789012",      "resourceId": "s3://acme-audit-logs",      "finding": "Default encryption not enabled.",      "lastRunAt": "2026-05-27T14:30:00.000Z",      "acknowledged": false,      "population": {        "total": 12,        "passing": 11,        "failing": 1,        "unit": "buckets"      },      "passBasis": "Passes when every bucket has default encryption enabled."    }  ]}

get_test_result

Scope
compliance:read

One test result with remediation steps, acknowledgement metadata, and the full substantive evidence the test emitted: population counts, observedConfiguration (config values actually read), passBasis, passingResources (the compliant population, with truncation disclosed), collectionContext (source-account attestation), and consoleScreenshot (async console capture metadata). Screenshot download URLs are omitted by default — pass includeScreenshotDownloadUrl:true only when you actually need to fetch the image (the presigned URL is a bearer-less link that lands in the conversation).

Ask your AI client: “Why did the S3 encryption test fail and how do I fix it?”

Input

FieldTypeDescription
testResultIdrequiredstring, 1–200Test result id.
includeScreenshotDownloadUrloptionalbooleanMint a short-lived (5 min) presigned download link for a ready console screenshot.Default: false

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_test_result",    "arguments": {      "testResultId": "<result-uuid>"    }  }}

Result

json
{  "testResultId": "<result-uuid>",  "testId": "aws-s3-encryption",  "testName": "S3 Encryption Check",  "status": "failing",  "severity": "high",  "finding": "S3 bucket 'acme-audit-logs' does not have default encryption enabled.",  "remediationSteps": [    "Enable SSE-S3 or SSE-KMS default encryption on the bucket."  ],  "acknowledged": false,  "acknowledgedBy": null,  "acknowledgedAt": null,  "population": {    "total": 12,    "passing": 11,    "failing": 1,    "unit": "buckets"  },  "passBasis": "Passes when every bucket has default encryption (SSE-S3 or SSE-KMS) enabled.",  "collectionContext": {    "source": "aws",    "accountId": "123456789012",    "callerArn": "arn:aws:iam::123456789012:role/StracComplyAudit"  }}

Automated tests (write)

Act on automated evidence-collection tests: re-run one after a fix (and poll it to completion), attach the reference file an auditor reviews alongside a result, or propose an accept-risk exception, which a DIFFERENT human admin must approve in the dashboard before it applies. None of these make a failing test pass.

rerun_test

Scope
tests:write

Re-runs ONE automated integration check and overwrites its result row. It does NOT make a failing test pass or change any evidence — a check failing on a real misconfiguration fails again. Owner/admin only. Returns immediately with status 'pending'; poll get_test_execution(executionId) for the outcome. Pass testId (from list_tests / get_next_actions) or testResultId.

Ask your AI client: “We enabled bucket encryption — re-run the S3 encryption test.”

Input

FieldTypeDescription
testIdoptionalstring, 1–200Test-definition id to re-run (e.g. 'gws-mfa-enabled'). One of testId / testResultId is required.
testResultIdoptionalstring, 1–200Alternatively, the result-row id (from list_tests / get_test_result); its testId is resolved server-side.
aiReasoningoptionalstring, 1–2000Optional rationale recorded on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "rerun_test",    "arguments": {      "testId": "aws-s3-encryption"    }  }}

Result

json
{  "executionId": "<execution-id>",  "testId": "aws-s3-encryption",  "integrationType": "aws",  "status": "pending",  "message": "Re-run enqueued for aws-s3-encryption. Poll get_test_execution({ executionId: \"<execution-id>\" }) until status is 'completed', then read the updated result with get_test_result."}

get_test_execution

Scope
compliance:read

Polls the status of a re-run started by rerun_test. Status flips pending → running → completed (or failed) with pass/fail counts; once 'completed', read the refreshed row with get_test_result.

Ask your AI client: “Did that test re-run finish? Check the execution status.”

Input

FieldTypeDescription
executionIdrequiredstring, 1–200The executionId returned by rerun_test.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_test_execution",    "arguments": {      "executionId": "<execution-id>"    }  }}

Result

json
{  "executionId": "<execution-id>",  "testId": "aws-s3-encryption",  "status": "completed",  "totalTests": 1,  "completedTests": 1,  "passingTests": 1,  "failingTests": 0,  "errorTests": 0,  "startedAt": "2026-05-27T14:30:00.000Z",  "completedAt": "2026-05-27T14:30:45.000Z",  "updatedAt": "2026-05-27T14:30:45.000Z"}

acknowledge_test

Scope
tests:write

PROPOSES an accept-risk on a failing test. It does NOT make the test pass or move the compliance score. It creates a PENDING exception request that a DIFFERENT human admin must approve in the dashboard before the accept-risk applies. Only accept-risk-eligible tests can be proposed (others are refused). Provide testId + resourceId (or findingId) + a written rationale.

Ask your AI client: “Draft a risk-acceptance for the failing S3 encryption finding on the legacy exports bucket — we're decommissioning it in Q3.”

Input

FieldTypeDescription
testIdrequiredstring, 1–200The failing test (from list_tests / get_next_actions). Must be accept-risk-eligible.
resourceIdoptionalstring, 1–400The flagged resource to accept (from the test finding). One of resourceId / findingId is required.
findingIdoptionalstring, 1–200Alternatively, the finding id (resolved + tenant-checked server-side).
rationalerequiredstring, 10–2000Why this risk is acceptable. Goes verbatim into the audit binder for the auditor.
affectedControlsoptionalstring[] (≤50)Optional control ids this exception touches — display-only context for the human approver.
proposedReviewByoptionalstring, 1–40Optional re-review date (YYYY-MM-DD).Default: +12 months

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "acknowledge_test",    "arguments": {      "testId": "aws-s3-encryption",      "resourceId": "s3://acme-legacy-exports",      "rationale": "Bucket holds only public marketing assets; decommission scheduled for Q3."    }  }}

Result

json
{  "requestId": "<request-id>",  "state": "pending",  "proposedReviewBy": "2027-05-27T16:00:00.000Z",  "message": "Proposed an accept-risk exception for 'aws-s3-encryption'. This has NOT changed the score — a different admin must approve it in the dashboard before it applies."}

list_test_evidence

Scope
compliance:read

Lists the reference/audit files attached to a test (uploaded via begin_test_evidence_upload). Pass the testResultId from list_tests / get_test_result. Download URLs are omitted by default — pass includeDownloadUrls:true only when you actually need to fetch a file (the presigned URL is a bearer-less link that lands in the conversation).

Ask your AI client: “What audit files are attached to the S3 encryption test?”

Input

FieldTypeDescription
testResultIdrequiredstring, 1–200The id of the test result row from list_tests / get_test_result.
includeDownloadUrlsoptionalbooleanWhen true, include a short-lived presigned download URL per evidence file.Default: false

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_test_evidence",    "arguments": {      "testResultId": "<result-id>"    }  }}

Result

json
{  "testResultId": "<result-id>",  "testId": "aws-s3-encryption",  "evidence": [    {      "evidenceId": "<evidence-id>",      "name": "Q1 access review export",      "notes": "Reviewed by security team.",      "contentType": "application/pdf",      "uploadedBy": "admin@example.com",      "createdAt": "2026-05-27T16:05:00.000Z",      "fileSize": 248192    }  ]}

begin_test_evidence_upload

Scope
evidence:write

Step 1 of the test-evidence handshake. Attaches a reference/audit file (PDF, screenshot, export; ≤25 MB, any type) to ONE automated test — the file an auditor reviews alongside the result. Pass the testResultId from list_tests / get_test_result (NOT the testId field). Returns a short-lived S3 PUT URL; upload the raw bytes with the returned `next` curl command, then call finalize_test_evidence_upload. This does NOT link evidence to a control (use attach_evidence) and does NOT change the pass/fail status or compliance score of the test.

Ask your AI client: “Attach this access-review PDF to the access review test as audit evidence.”

Input

FieldTypeDescription
testResultIdrequiredstring, 1–200The id of the test result row from list_tests / get_test_result (the testResultId field, NOT the secondary testId).
namerequiredstring, 1–256Human-readable evidence name, e.g. 'Q1 access review export'.
contentTyperequiredstring, 1–128MIME type of the file (e.g. application/pdf, image/png). Must match the Content-Type sent on the PUT.
notesoptionalstring, ≤2048Optional context for auditors.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_test_evidence_upload",    "arguments": {      "testResultId": "<result-id>",      "name": "Q1 access review export",      "contentType": "application/pdf"    }  }}

Result

json
{  "uploadId": "<upload-id>",  "uploadUrl": "https://<bucket>.s3.amazonaws.com/test-evidence/_staging/comp_demo/<evidence-id>?X-Amz-Signature=<sig>",  "uploadExpiresAt": "2026-05-27T16:10:00.000Z",  "maxBytes": 26214400,  "contentType": "application/pdf",  "next": "Upload the file bytes directly to S3, then finalize:\n  1. curl -X PUT -T <path-to-file> -H 'Content-Type: application/pdf' '<uploadUrl>'\n  2. call finalize_test_evidence_upload with this uploadId"}

finalize_test_evidence_upload

Scope
evidence:write

Step 2 of the test-evidence handshake. After you PUT the file bytes to the URL from begin_test_evidence_upload, pass the uploadId; the server size-checks the object (≤25 MB, non-empty) and registers it against the test. Idempotent — a repeated call with the same uploadId returns the same result. Returns precondition_failed if the file was not uploaded first. The returned `note` confirms the file is reference/audit material only and does NOT change the test status or compliance score.

Ask your AI client: “Finalize the test-evidence upload. Here is the uploadId.”

Input

FieldTypeDescription
uploadIdrequiredstring, 1–80The uploadId returned by begin_test_evidence_upload.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_test_evidence_upload",    "arguments": {      "uploadId": "<upload-id>"    }  }}

Result

json
{  "evidenceId": "<evidence-id>",  "name": "Q1 access review export",  "testId": "aws-s3-encryption",  "status": "uploaded",  "fileSize": 248192,  "note": "Stored as reference/audit material — does NOT change the test status or compliance score."}

Evidence & lifecycle (write)

Write evidence and drive the policy/document lifecycle. Every write is attributed in the append-only audit log.

mark_control_na

Scope
evidence:write

Marks a control N/A with a reason (the auditor sees it). Re-marking preserves the original marker.

Ask your AI client: “Mark CC6.1 not applicable. We don't issue laptops.”

Input

FieldTypeDescription
controlIdrequiredstring, 1–120Control id, e.g. CC6.1.
reasonrequiredstring, 10–500Why the control does not apply.
aiReasoningoptionalstring, 1–2000Optional rationale recorded on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "mark_control_na",    "arguments": {      "controlId": "CC6.1",      "reason": "No customer-managed laptops; access is SSO-only on managed devices."    }  }}

Result

json
{  "controlId": "CC6.1",  "naReason": "No customer-managed laptops…",  "markedNAAt": "2026-05-27T16:00:00.000Z",  "markedNABy": "admin@example.com",  "remarkedAt": null,  "remarkedBy": null}

mark_evidence_na

Scope
evidence:write

Marks a single evidence slot — a canonical catalog document, a policy, or a required "connect an automated evidence source" integration slot on one control. N/A with a customer-visible justification (the evidence twin of mark_control_na; reversible via the web UI). The slot DROPS from the required-evidence checklist of every control it belongs to (leaves both numerator and denominator), raising that per-control completion %; an integration waiver also lifts the readiness gap it was forcing. The framework headline moves only if the N/A flips a control to ready. Audit-logged and admin-notified.

Ask your AI client: “Mark the Physical Security Evidence document not applicable. We are fully remote.”

Input

FieldTypeDescription
evidenceTyperequired'document' | 'policy' | 'integration'Which kind of slot: 'document' (catalog document), 'policy', or 'integration' (an integration-coverage slot on one control).
evidenceIdrequiredstring, 1–200Catalog document id (from list_documents), policy id (from list_policies), or the integration-coverage item id from the control's required-evidence checklist.
controlIdoptionalstring, 1–200Required when evidenceType is 'integration' — the control the slot belongs to (e.g. soc2-cc1.3). Rejected for the other kinds.
reasonrequiredstring, 10–500Customer-visible justification, shown in the N/A badge and the audit binder.
aiReasoningoptionalstring, 1–2000Optional rationale recorded on the audit-log row (not user-visible).

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "mark_evidence_na",    "arguments": {      "evidenceType": "document",      "evidenceId": "DOC-CC6-PHYSICAL-SECURITY",      "reason": "Fully remote company with no offices or owned data centres; physical controls are inherited from the cloud provider."    }  }}

Result

json
{  "evidenceType": "document",  "evidenceId": "DOC-CC6-PHYSICAL-SECURITY",  "lifecycleStatus": "not_applicable",  "naReason": "Fully remote company with no offices or owned data centres…",  "markedBy": "admin@example.com",  "markedAt": "2026-06-05T16:00:00.000Z"}

Document shape shown. A policy slot instead returns { evidenceType: "policy", evidenceId, lifecycleStatus, naReason, markedNAAt, markedNABy } — re-marking preserves the original marker. An integration slot returns { evidenceType: "integration", evidenceId, controlId, lifecycleStatus, naReason, markedBy, markedAt }. Use it only when none of the candidate sources the slot lists is (or will be) part of your stack; connecting one, or uploading manual evidence against the slot, are the alternatives.

attach_evidence

Scope
evidence:write

Links existing evidence to a control. Idempotent (re-attaching is a no-op). Fans out to submitted audit binders.

Ask your AI client: “Attach policy POL-SEC-001 as evidence for CC6.1.”

Input

FieldTypeDescription
controlIdrequiredstring, 1–120Control id, e.g. CC6.1.
evidenceTyperequiredstringKind of evidence.policydocument
referenceIdrequiredstring, 1–120Policy id or document id to attach.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "attach_evidence",    "arguments": {      "controlId": "CC6.1",      "evidenceType": "policy",      "referenceId": "POL-SEC-001"    }  }}

Result

json
{  "controlId": "CC6.1",  "evidenceType": "policy",  "referenceId": "POL-SEC-001",  "attached": true,  "noop": false,  "totalControlsLinked": 4}

upload_document

Scope
documents:write

Uploads a base64 document (≤4 MB raw) that appears in list_documents immediately. No-orphan rule: controlIds is REQUIRED (≥1) — an ad-hoc document with no control links counts toward nothing.

Ask your AI client: “Upload this pen test report as a compliance document.”

Input

FieldTypeDescription
titlerequiredstring, 1–200Document title.
descriptionoptionalstring, 0–2000Optional description.
contentTyperequiredMIME enumOne of the allowed types: pdf, docx, doc, xlsx, xls, pptx, ppt, png, jpeg, gif, webp, heic, heif, tiff, bmp, markdown, plain, csv, json, eml, rtf, zip, tar, gzip, 7z, rar.
contentBase64requiredstring (base64)Base64 bytes, ≤4 MB after decode.
controlIdsrequiredstring[], 1–20control ids stored on the document (e.g. ['soc2-cc6.1']) — at least one is required. For completion %, also call attach_evidence per control.
frameworksoptionalstring[], cap 10additional framework tags stored on the document. Use the exact labels 'SOC 2', 'ISO 27001', 'NIST CSF 2.0', 'PCI DSS' (an unrecognized label hides the document in the web list).
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "upload_document",    "arguments": {      "title": "Pen Test Report 2026",      "contentType": "application/pdf",      "contentBase64": "JVBERi0xLjcK...<base64>",      "controlIds": [        "soc2-cc4.1"      ]    }  }}

Result

json
{  "documentId": "<doc-uuid>",  "title": "Pen Test Report 2026",  "contentType": "application/pdf",  "sizeBytes": 184523,  "sha256": "<hex>",  "s3Key": "mcp/<doc-uuid>/pen-test-report-2026.pdf",  "uploadedAt": "2026-05-27T16:00:00.000Z"}

upload_policy

Scope
policies:write

Creates the FIRST version of a canonical policy. Provide exactly one of `markdown` or (`fileName` + `fileContent`). Returns 409 if a version already exists (use update_policy).

Ask your AI client: “Upload the first version of policy POL-SEC-001 from this markdown.”

Input

FieldTypeDescription
policyIdrequiredstring, 1–120Canonical policy id, e.g. POL-SEC-001.
markdownoptionalstringPolicy body as markdown (sanitized). Mutually exclusive with file fields.
fileNameoptionalstring, 1–200File name with extension. Pair with fileContent.
fileContentoptionalstring (base64), ≤4 MB rawBase64 file bytes. Pair with fileName.
commentsoptionalstring, 0–2000Version changelog note.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "upload_policy",    "arguments": {      "policyId": "POL-SEC-001",      "markdown": "# Information Security Policy\n\n## Purpose\n..."    }  }}

Result

json
{  "policyId": "POL-SEC-001",  "versionId": "<version-uuid>",  "version": 1,  "uploadedAt": "2026-05-27T16:00:00.000Z",  "uploadedBy": "ciso@example.com",  "status": "draft",  "contentFormat": "md",  "fileName": null,  "sizeBytes": 4096}

update_policy

Scope
policies:write

Adds a new draft version. `basedOnVersion` must equal the current version counter; on mismatch it returns a structured `{ stale: true }` result (not an error) so you can refetch. `force: true` bypasses the check.

Ask your AI client: “Update POL-SEC-001 with these revisions (based on version 3).”

Input

FieldTypeDescription
policyIdrequiredstring, 1–120Policy id.
basedOnVersionrequiredpositive integerThe version counter you edited from (optimistic lock).
markdownoptionalstringNew body (markdown). Mutually exclusive with file fields.
fileNameoptionalstring, 1–200File name with extension. Pair with fileContent.
fileContentoptionalstring (base64), ≤4 MB rawBase64 file bytes.
commentsoptionalstring, 0–2000Version changelog note.
forceoptionalbooleanSkip the stale-version check.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "update_policy",    "arguments": {      "policyId": "POL-SEC-001",      "basedOnVersion": 3,      "markdown": "# Information Security Policy (rev 4)\n..."    }  }}

Result

json
{  "policyId": "POL-SEC-001",  "versionId": "<version-uuid>",  "version": 4,  "basedOnVersion": 3,  "uploadedAt": "2026-05-27T16:00:00.000Z",  "uploadedBy": "ciso@example.com",  "status": "draft",  "contentFormat": "md",  "fileName": null,  "sizeBytes": 4210}

compare_policy

Scope
policies:read

Deterministic (no LLM) section diff of a policy version against its canonical required sections. Defaults to the current/latest version.

Ask your AI client: “Does our current POL-SEC-001 cover all the required sections?”

Input

FieldTypeDescription
policyIdrequiredstring, 1–120Policy id.
versionIdoptionalstring, 1–120Specific version; defaults to current/latest.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "compare_policy",    "arguments": {      "policyId": "POL-SEC-001"    }  }}

Result

json
{  "policyId": "POL-SEC-001",  "policyTitle": "Information Security Policy",  "versionId": "<version-uuid>",  "version": 3,  "versionStatus": "approved",  "resolvedFormat": "pdf",  "conversionWarnings": [],  "diff": {    "missingSections": [      "Access Review Cadence"    ],    "extraSections": [],    "sectionDeltas": [      {        "section": "Acceptable Use",        "matchedTitle": "Acceptable Use",        "bodyLengthChars": 842,        "appearsToBePlaceholder": false      }    ],    "detectedHeadingStyle": "atx",    "totalPolicySections": 7  }}

approve_policy

Scope
policies:approve

Approves a version and flips the policy to published. Idempotent (re-approving is a no-op preserving the original approver).

Ask your AI client: “Approve version <version-uuid> of POL-SEC-001.”

Input

FieldTypeDescription
versionIdrequiredstring, 1–120The version id to approve.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "approve_policy",    "arguments": {      "versionId": "<version-uuid>"    }  }}

Result

json
{  "versionId": "<version-uuid>",  "policyId": "POL-SEC-001",  "version": 4,  "approvedAt": "2026-05-27T16:05:00.000Z",  "approvedBy": "ceo@example.com",  "noop": false}

create_custom_policy

Scope
policies:write

One call to mint a CUSTOM-POL-* policy, its first draft version, and its control mappings. All controlIds are validated before the write. Provide exactly one of `markdown` or (`fileName` + `fileContent`).

Ask your AI client: “Create a custom Incident Response Policy mapped to CC7.4.”

Input

FieldTypeDescription
titlerequiredstring, 1–200Policy title.
descriptionoptionalstring, 0–2000Optional description.
categoryoptionalstring, 0–80Optional category.
frameworksoptionalstring[] (≤10)Framework tags.
controlIdsoptionalstring[] (≤100)Controls to map; each must exist.
markdownoptionalstringBody as markdown. Mutually exclusive with file fields.
fileNameoptionalstring, 1–200File name with extension. Pair with fileContent.
fileContentoptionalstring (base64), ≤4 MB rawBase64 file bytes.
commentsoptionalstring, 0–2000Version changelog note.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "create_custom_policy",    "arguments": {      "title": "Incident Response Policy",      "frameworks": [        "SOC 2"      ],      "controlIds": [        "CC7.4"      ],      "markdown": "# Incident Response Policy\n..."    }  }}

Result

json
{  "policyId": "CUSTOM-POL-<uuid>",  "title": "Incident Response Policy",  "frameworks": [    "SOC 2"  ],  "controlIds": [    "CC7.4"  ],  "versionId": "<version-uuid>",  "version": 1,  "uploadedAt": "2026-05-27T16:00:00.000Z",  "uploadedBy": "ciso@example.com",  "status": "draft",  "contentFormat": "md",  "fileName": null,  "sizeBytes": 2048}

Presigned file uploads (write)

Upload real .docx/.pdf bytes straight to S3 via a begin → PUT → finalize handshake — no base64 through the model. Preferred over the inline upload_* tools for real files. The plural begin_*_uploads / finalize_*_uploads twins batch a whole folder in a handful of calls (200 URLs per begin, 20 finalizes per call) with per-row errors, dryRun previews, and inline approve/publish.

begin_policy_upload

Scope
policies:write

Step 1 of the presigned policy-upload handshake. Returns a short-lived S3 PUT URL. Upload the raw .docx/.doc/.pdf bytes directly, then call finalize_policy_upload. The original file is preserved (the dashboard renders markdown on read). Three modes: (1) `policyId` alone → first version of a canonical catalog policy; (2) `title` (+ optional custom fields) → a new CUSTOM-POL-* policy; (3) `policyId` + `basedOnVersion` → a new version of an EXISTING policy (canonical or custom) with the same optimistic-lock as update_policy. Prefer this over upload_policy / update_policy / create_custom_policy for real files where base64-through-the-model is unreliable.

Ask your AI client: “Upload this .docx as a new version of POL-SEC-001.”

Input

FieldTypeDescription
fileNamerequiredstring, ≤200File name with extension. Allowed: .docx, .doc, .pdf. Path-traversal-safe (no /, \, .., null bytes).
policyIdoptionalstring, 1–120Existing policy id — a canonical id from list_policies (e.g. POL-SEC-001) or a CUSTOM-POL-* id. Mutually exclusive with title. Add basedOnVersion to update an existing policy.
titleoptionalstring, 1–200Custom-policy title. Creates a new CUSTOM-POL-* policy. Mutually exclusive with policyId.
descriptionoptionalstring, 0–2000Custom policy: optional description.
categoryoptionalstring, 0–80Custom policy: optional category. Defaults to 'Custom'.
frameworksoptionalstring[] (≤10)Custom policy: optional framework codes, e.g. ['soc2'].
controlIdsoptionalstring[] (≤100)Custom policy: optional control codes to map; each must exist.
basedOnVersionoptionalpositive integerUpdate mode: the version number you were editing (from get_policy). Requires policyId. finalize_policy_upload returns a stale-version payload if the policy advanced since.
forceoptionalbooleanUpdate mode: skip the stale-version check. Use only after surfacing the conflict; defaulting it loses human edits silently. Requires basedOnVersion.
commentsoptionalstring, 0–2000Optional human-visible changelog comment.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_policy_upload",    "arguments": {      "policyId": "POL-SEC-001",      "fileName": "information-security-policy.docx"    }  }}

Result

json
{  "uploadId": "<upload-id>",  "uploadUrl": "https://<bucket>.s3.amazonaws.com/comp_demo/_staging/<upload-id>/information-security-policy.docx?X-Amz-Signature=<sig>",  "uploadExpiresAt": "2026-05-27T16:10:00.000Z",  "maxBytes": 52428800,  "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",  "next": "Upload the file bytes directly to S3, then finalize:\n  1. curl -X PUT -T <path-to-file> -H 'Content-Type: …' '<uploadUrl>'\n  2. call finalize_policy_upload with this uploadId"}

finalize_policy_upload

Scope
policies:write

Step 2 of the policy-upload handshake. After you PUT the file bytes to the URL from begin_policy_upload, pass the uploadId; the server reads the object (≤50 MB) and creates a draft version (first version, new custom policy, or — for an update begun with basedOnVersion — a new version of an existing policy). Idempotent. For an update, if the policy advanced since you read it, returns a structured { stale: true, latestCounter, … } payload WITHOUT consuming the upload — re-read with get_policy, or re-call with force:true to override (no re-upload needed). The draft still needs approve_policy. Returns precondition_failed if the file was not uploaded first.

Ask your AI client: “Finalize the policy upload. Here is the uploadId.”

Input

FieldTypeDescription
uploadIdrequiredstring, 1–80The uploadId returned by begin_policy_upload.
forceoptionalbooleanUpdate mode only: override a stale-version conflict returned by a prior finalize, without re-uploading. Use only after surfacing the conflict. It can silently supersede a concurrent edit.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_policy_upload",    "arguments": {      "uploadId": "<upload-id>"    }  }}

Result

json
{  "policyId": "POL-SEC-001",  "versionId": "<version-uuid>",  "version": 1,  "uploadedAt": "2026-05-27T16:05:00.000Z",  "uploadedBy": "ciso@example.com",  "status": "draft",  "contentFormat": "docx",  "fileName": "information-security-policy.docx",  "sizeBytes": 248192}

begin_policy_uploads

Scope
policies:write

Bulk twin of begin_policy_upload: mints up to 200 presigned S3 PUT URLs in one call (chunk larger sets). Each row is the same shape as begin_policy_upload (`policyId` → existing policy; `title` → new custom; add `basedOnVersion` to update). Per-row errors[] (indexed by your input) keep a partial batch visible — one malformed row never blocks the others; dryRun:true previews per-row mode + canonical validity with zero writes. PUT the bytes in parallel, then call finalize_policy_uploads.

Ask your AI client: “Upload all 12 of these policy files into Strac in one batch.”

Input

FieldTypeDescription
policiesrequiredobject[], 1–200Upload specs, one per file: `fileName` (required) plus exactly one of `policyId` (existing canonical/custom) or `title` (new custom); optional description, category, frameworks, controlIds, basedOnVersion, force, comments. A malformed row lands in errors[].
dryRunoptionalbooleantrue → preview per-row mode + canonical validity with ZERO writes; no URLs minted.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_policy_uploads",    "arguments": {      "policies": [        {          "policyId": "POL-SEC-001",          "fileName": "information-security-policy.docx"        },        {          "title": "Data Retention Policy",          "fileName": "data-retention-policy.pdf"        }      ]    }  }}

Result

json
{  "dryRun": false,  "uploads": [    {      "index": 0,      "uploadId": "<upload-id-1>",      "uploadUrl": "https://<bucket>.s3.amazonaws.com/comp_demo/_staging/<upload-id-1>/information-security-policy.docx?X-Amz-Signature=<sig>",      "uploadExpiresAt": "2026-06-05T16:10:00.000Z",      "maxBytes": 52428800,      "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",      "next": "curl -X PUT -T <path-to-file> -H 'Content-Type: …' '<uploadUrl>', then finalize"    },    {      "index": 1,      "uploadId": "<upload-id-2>",      "uploadUrl": "https://<bucket>.s3.amazonaws.com/comp_demo/_staging/<upload-id-2>/data-retention-policy.pdf?X-Amz-Signature=<sig>",      "uploadExpiresAt": "2026-06-05T16:10:00.000Z",      "maxBytes": 52428800,      "contentType": "application/pdf",      "next": "curl -X PUT -T <path-to-file> -H 'Content-Type: …' '<uploadUrl>', then finalize"    }  ],  "errors": [],  "summary": "Minted 2 upload URLs, 0 failed",  "next": "PUT the bytes to each uploadUrl (in parallel), then call finalize_policy_uploads with all the uploadIds (it registers AND approves each draft)."}

finalize_policy_uploads

Scope
policies:write
+
policies:approve

Bulk twin of finalize_policy_upload: finishes up to 20 presigned policy uploads in one call (loop larger sets), registering each draft AND approving it inline so it counts toward completion immediately — requires BOTH policies:write and policies:approve. Per-row results keyed by uploadId; a finalize failure lands in errors[], a row that finalizes but fails to approve returns approved:false WITH its draft versionId (re-run heals), and a stale update returns status 'stale' (re-read, or force via the single tool). Idempotent — re-running is safe.

Ask your AI client: “Finalize and approve the whole batch of policy uploads I just PUT.”

Input

FieldTypeDescription
uploadIdsrequiredstring[], 1–20, uniqueThe uploadIds returned by begin_policy_uploads (after the bytes were PUT). Max 20/call.
aiReasoningoptionalstring, 1–2000Optional rationale recorded on the audit-log rows.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_policy_uploads",    "arguments": {      "uploadIds": [        "<upload-id-1>",        "<upload-id-2>"      ]    }  }}

Result

json
{  "results": [    {      "uploadId": "<upload-id-1>",      "index": 0,      "status": "approved",      "policyId": "POL-SEC-001",      "versionId": "<version-uuid>",      "approved": true    },    {      "uploadId": "<upload-id-2>",      "index": 1,      "status": "approved",      "policyId": "CUSTOM-POL-<uuid>",      "versionId": "<version-uuid>",      "approved": true    }  ],  "errors": [],  "summary": "Finalized 2 (2 approved), 0 failed"}

begin_document_upload

Scope
documents:write

Step 1 of the presigned document-upload handshake. Returns a short-lived S3 PUT URL. Upload the raw bytes directly, then call finalize_document_upload. Same MIME allowlist as upload_document. Prefer this over upload_document for real files where base64-through-the-model is unreliable. Two modes: pass `title` for a new ad-hoc document (no-orphan rule: at least one of controlIds/frameworks is required), OR `documentId` (a canonical catalog slot id like DOC-CC2-SYSTEM-DESCRIPTION) to create a draft version of that catalog slot, then publish_document makes it count toward control completion.

Ask your AI client: “Upload this PDF pen test report as a document.”

Input

FieldTypeDescription
titleoptionalstring, 1–200Ad-hoc mode: human-readable document title. Mutually exclusive with documentId.
documentIdoptionalstring, 1–120Catalog mode: canonical catalog slot id (DOC-*). Creates a draft version of that slot. Publish with publish_document. Mutually exclusive with title.
fileNameoptionalstring, 1–200Catalog mode only: original file name (sanitized server-side). Defaults to "{documentId}.{ext}".
commentsoptionalstring, 0–2000Catalog mode only: human-visible changelog comment.
descriptionoptionalstring, 0–2000Ad-hoc mode only: optional description.
contentTyperequiredMIME enumMIME type of the file; must match the Content-Type sent on the PUT. Allowed: pdf, docx, doc, xlsx, xls, pptx, ppt, png, jpeg, gif, webp, heic, heif, tiff, bmp, markdown, plain, csv, json, eml, rtf, zip, tar, gzip, 7z, rar.
controlIdsoptionalstring[], cap 20Ad-hoc mode: control ids stored on the document (e.g. ['soc2-cc6.1']). At least one of controlIds/frameworks is REQUIRED on an ad-hoc upload (controlIds preferred); for completion %, also call attach_evidence per control.
frameworksoptionalstring[], cap 10Ad-hoc mode: framework tags stored on the document (e.g. ['SOC 2']). Frameworks-only is accepted for migration flows that attach controls afterward via attach_evidence.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_document_upload",    "arguments": {      "title": "Pen Test Report 2026",      "contentType": "application/pdf",      "controlIds": [        "soc2-cc4.1"      ]    }  }}

Result

json
{  "uploadId": "<upload-id>",  "uploadUrl": "https://<bucket>.s3.amazonaws.com/mcp-uploads/_staging/comp_demo/<upload-id>/pen-test-report-2026.pdf?X-Amz-Signature=<sig>",  "uploadExpiresAt": "2026-05-27T16:10:00.000Z",  "maxBytes": 52428800,  "contentType": "application/pdf",  "next": "Upload the file bytes directly to S3, then finalize:\n  1. curl -X PUT -T <path-to-file> -H 'Content-Type: application/pdf' '<uploadUrl>'\n  2. call finalize_document_upload with this uploadId"}

finalize_document_upload

Scope
documents:write

Step 2 of the document-upload handshake. After you PUT the file bytes to the URL from begin_document_upload, pass the uploadId; the server reads the object (≤50 MB) and registers it. Ad-hoc uploads (begun with title) return the documentId + S3 key + sha256 and appear in list_documents. Catalog uploads (begun with documentId) create a DRAFT version of the catalog slot and return its versionId — call publish_document with that versionId so the document counts toward control completion. Idempotent — repeating with the same uploadId returns the same result. Returns precondition_failed if the file was not uploaded first.

Ask your AI client: “Finalize the document upload. Here is the uploadId.”

Input

FieldTypeDescription
uploadIdrequiredstring, 1–80The uploadId returned by begin_document_upload.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_document_upload",    "arguments": {      "uploadId": "<upload-id>"    }  }}

Result

json
{  "documentId": "<doc-uuid>",  "title": "Pen Test Report 2026",  "contentType": "application/pdf",  "sizeBytes": 184523,  "sha256": "<hex>",  "s3Key": "mcp/<doc-uuid>/pen-test-report-2026.pdf",  "s3VersionId": "<s3-version-id>",  "uploadedAt": "2026-05-27T16:05:00.000Z"}

Ad-hoc shape shown. A catalog upload (begun with documentId) instead returns { documentId, title, versionId, versionNumber, status: "draft", next } — pass that versionId to publish_document.

begin_document_uploads

Scope
documents:write

Bulk twin of begin_document_upload: mints up to 200 presigned S3 PUT URLs in one call (chunk larger sets). Each row is the same shape as begin_document_upload (`title` → ad-hoc document; `documentId` → canonical catalog slot). Per-row errors[] (indexed by your input) keep a partial batch visible — one bad row (bad contentType, title+documentId both set, unknown slot) never blocks the others; dryRun:true previews the slot-match plan with zero writes. PUT the bytes in parallel, then call finalize_document_uploads.

Ask your AI client: “Batch-upload these 30 evidence PDFs into their catalog slots.”

Input

FieldTypeDescription
documentsrequiredobject[], 1–200Upload specs, one per file: `contentType` (required, same MIME allowlist as upload_document) plus exactly one of `title` (ad-hoc; requires at least one of controlIds/frameworks — the no-orphan rule; optional description) or `documentId` (catalog slot like 'DOC-CC2-SYSTEM-DESCRIPTION'; optional fileName/comments). A malformed row lands in errors[].
dryRunoptionalbooleantrue → preview the slot-match plan (mode + catalog-slot validity) with ZERO writes; no URLs minted.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_document_uploads",    "arguments": {      "documents": [        {          "documentId": "DOC-CC2-SYSTEM-DESCRIPTION",          "contentType": "application/pdf"        },        {          "title": "Pen Test Report 2026",          "contentType": "application/pdf",          "controlIds": [            "soc2-cc4.1"          ]        }      ]    }  }}

Result

json
{  "dryRun": false,  "uploads": [    {      "index": 0,      "uploadId": "<upload-id-1>",      "uploadUrl": "https://<bucket>.s3.amazonaws.com/mcp-uploads/_staging/comp_demo/<upload-id-1>/DOC-CC2-SYSTEM-DESCRIPTION.pdf?X-Amz-Signature=<sig>",      "uploadExpiresAt": "2026-06-05T16:10:00.000Z",      "maxBytes": 52428800,      "contentType": "application/pdf",      "next": "curl -X PUT -T <path-to-file> -H 'Content-Type: application/pdf' '<uploadUrl>', then finalize"    },    {      "index": 1,      "uploadId": "<upload-id-2>",      "uploadUrl": "https://<bucket>.s3.amazonaws.com/mcp-uploads/_staging/comp_demo/<upload-id-2>/pen-test-report-2026.pdf?X-Amz-Signature=<sig>",      "uploadExpiresAt": "2026-06-05T16:10:00.000Z",      "maxBytes": 52428800,      "contentType": "application/pdf",      "next": "curl -X PUT -T <path-to-file> -H 'Content-Type: application/pdf' '<uploadUrl>', then finalize"    }  ],  "errors": [],  "summary": "Minted 2 upload URLs, 0 failed",  "next": "PUT the bytes to each uploadUrl (in parallel), then call finalize_document_uploads with all the uploadIds."}

finalize_document_uploads

Scope
documents:write

Bulk twin of finalize_document_upload: finishes up to 20 presigned document uploads in one call (loop larger sets). Catalog uploads (begun with a documentId) are registered AND published inline so the slot counts toward completion — no separate publish_document call; ad-hoc uploads count on presence. Per-row results keyed by uploadId; a finalize failure lands in errors[], a catalog row that finalizes but fails to publish returns published:false WITH its draft versionId so a re-run heals it, and a re-run whose bytes match an archived version returns status 'superseded' (a newer version is live). Idempotent — re-running is safe.

Ask your AI client: “Finalize and publish the whole batch of document uploads I just PUT.”

Input

FieldTypeDescription
uploadIdsrequiredstring[], 1–20, uniqueThe uploadIds returned by begin_document_uploads (after the bytes were PUT). Max 20/call.
aiReasoningoptionalstring, 1–2000Optional rationale recorded on the audit-log rows.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_document_uploads",    "arguments": {      "uploadIds": [        "<upload-id-1>",        "<upload-id-2>"      ]    }  }}

Result

json
{  "results": [    {      "uploadId": "<upload-id-1>",      "index": 0,      "status": "published",      "documentId": "DOC-CC2-SYSTEM-DESCRIPTION",      "versionId": "<version-uuid>",      "published": true    },    {      "uploadId": "<upload-id-2>",      "index": 1,      "status": "finalized",      "documentId": "<doc-uuid>",      "published": false    }  ],  "errors": [],  "summary": "Finalized 2 (1 published, 1 ad-hoc), 0 failed"}

publish_document

Scope
documents:write

Publishes a draft catalog-document version (created via begin_document_upload with a documentId) so it counts toward control completion — the documents analog of approve_policy. Archives any older published version of the same slot and sets the next review date from the catalog cadence. Idempotent on already-published versions (no-op). publishedBy is derived from your identity, never from arguments.

Ask your AI client: “Publish the System Description draft I just uploaded.”

Input

FieldTypeDescription
versionIdrequiredstring, 1–120The catalog document version id from the finalize_document_upload response.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "publish_document",    "arguments": {      "versionId": "<version-uuid>"    }  }}

Result

json
{  "documentId": "DOC-CC2-SYSTEM-DESCRIPTION",  "versionId": "<version-uuid>",  "versionNumber": 1,  "publishedAt": "2026-06-05T16:05:00.000Z",  "publishedBy": "ciso@example.com",  "nextReviewDate": "2027-06-05T16:05:00.000Z",  "noop": false}

Q&A

Ask freeform questions grounded on your posture.

ask_compliance_question

Scope
chat:ask

Asks the compliance agent a question, grounded on your live posture. Read-only (no audit-log row). Echoes token usage for cost-aware follow-ups.

Ask your AI client: “What evidence do I still need for SOC 2 CC6?”

Input

FieldTypeDescription
messagerequiredstring, 1–8000Your question.
conversationHistoryoptionalarray (≤20 turns)Prior {role:"user"|"assistant", content} turns for context.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "ask_compliance_question",    "arguments": {      "message": "What evidence do I still need for SOC 2 CC6?"    }  }}

Result

json
{  "reply": "For CC6 you still need: an access-review document for Q2, and the MFA-enforcement test is failing on the AWS root account…",  "context": {    "testsPassing": 120,    "testsFailing": 18,    "policiesApproved": 12,    "integrationsConnected": 3,    "activeFrameworks": [      "soc2"    ]  },  "usage": {    "inputTokens": 1842,    "outputTokens": 240  }}

GRC migration (write)

Bulk-import a GRC-platform export (Vanta, Drata, Secureframe…) — vendors, the risk register, and historic test runs (original collection timestamps preserved, so auditors can sample the pre-migration period) — idempotent and safe to re-run, plus the read-back tools the import uses to verify itself. The strac-import prompt (installed with the server) orchestrates the full migration end to end.

import_vendors

Scope
vendors:write

Bulk-create/enrich managed vendors from a GRC-platform export (Vanta, Drata, Secureframe…) — parse the export locally and send structured rows, max 200 per call. Dedupes by name: new names are created (race-safe, re-run idempotent); existing vendors are ENRICHED only on currently-empty fields — human edits are never overwritten. Set dryRun: true first to preview created/updated/skipped without writing. Per-row errors[] (keyed by your input rows) make partial batches visible; a re-run heals them. Requires admin/owner.

Ask your AI client: “Import the vendors from my Vanta export — preview first.”

Input

FieldTypeDescription
vendorsrequiredarray, 1–200 rowsVendor rows. Only `name` is required; optional fields include website, inherentRisk, category, securityOwner, businessOwner, businessPurpose, notes, dataAccess, compliance[], review dates, reviewFrequency, status.
dryRunoptionalbooleantrue → full preview (created/updated/skipped) with ZERO writes. Run first, show the customer, then commit.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "import_vendors",    "arguments": {      "vendors": [        {          "name": "Stripe",          "website": "stripe.com",          "inherentRisk": "high",          "securityOwner": "security@example.com"        }      ],      "dryRun": true    }  }}

Result

json
{  "importBatchId": "<batch-uuid>",  "dryRun": true,  "summary": "DRY RUN — would import: 1 created, 0 enriched, 0 skipped, 0 failed",  "created": [    {      "id": "vendor-imp-<sha16>",      "name": "Stripe"    }  ],  "updated": [],  "skipped": [],  "errors": [],  "warnings": []}

import_risks

Scope
risks:write

Bulk-create risk-register entries from a GRC-platform export (e.g. Vanta's Risk Register CSV), max 200 per call. Every row needs the CC3 minimum: treatment, nextReviewDate, isFraudRisk. treatment='accept' rows record YOU as the acceptance approver (server-derived, never an argument). residual > inherent is clamped down with a warning. Dedupes by title; dryRun previews; per-row errors[]; re-runs heal. Requires admin/owner.

Ask your AI client: “Import my Vanta risk register CSV into Strac Comply.”

Input

FieldTypeDescription
risksrequiredarray, 1–200 rowsRisk rows: title, likelihood (1–5), impact (1–5), treatment (mitigate|transfer|avoid|accept), nextReviewDate (YYYY-MM-DD), isFraudRisk, plus optional category, residual pair, owner, mitigationPlan, mitigatingControls[], externalRef (e.g. Vanta "R-25").
dryRunoptionalbooleantrue → full preview with ZERO writes.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "import_risks",    "arguments": {      "risks": [        {          "title": "Unauthorized access to production",          "category": "security",          "likelihood": 3,          "impact": 4,          "treatment": "mitigate",          "mitigationPlan": "MFA + least privilege",          "residualLikelihood": 2,          "residualImpact": 3,          "nextReviewDate": "2027-06-05",          "isFraudRisk": false,          "externalRef": "R-25"        }      ]    }  }}

Result

json
{  "importBatchId": "<batch-uuid>",  "dryRun": false,  "summary": "Imported: 1 created, 0 enriched, 0 skipped, 0 failed",  "created": [    {      "id": "risk-imp-<sha16>",      "name": "Unauthorized access to production"    }  ],  "updated": [],  "skipped": [],  "errors": [],  "warnings": []}

import_test_runs

Scope
tests:write

Bulk-import HISTORIC automated-test runs from a GRC-platform export (e.g. Vanta's per-test '*-testRun.csv' files), max 100 per call. Each run carries the ORIGINAL collection timestamp (past only) — a SOC 2 Type 2 auditor samples runs across the whole observation period by date, and PCI counts quarterly scan cadence, so the source platform's timestamps are the evidence. Match source test names to Strac testIds via list_tests view:'catalog'. Source risk acceptances are preserved verbatim with the ORIGINAL approver. Imported runs appear in each test's run history (labeled by source + original date) and in audit binders. They NEVER count toward current completion %. Idempotent (dedupes on testId + executedAt); dryRun previews; per-row errors[]. Requires admin/owner.

Ask your AI client: “Import the historic test runs from my Vanta Tests export so our auditor can see the full period.”

Input

FieldTypeDescription
sourcerequired'vanta' | 'drata' | 'secureframe' | 'other'The platform the export came from.
runsrequiredarray, 1–100 rowsRun rows: testId (Strac canonical), executedAt (ISO 8601, past only), status (passing|failing|not_applicable), plus optional summary, sourceTestName, findingsTotal, findings[] (≤100, sampled), riskAcceptances[] (original approver/reason/dates, verbatim), affectedResources[].
dryRunoptionalbooleantrue → full preview with ZERO writes.
aiReasoningoptionalstring, 1–2000Optional rationale on the audit-log row.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "import_test_runs",    "arguments": {      "source": "vanta",      "runs": [        {          "testId": "aws-s3-encryption",          "executedAt": "2026-05-20T00:00:00.000Z",          "status": "failing",          "sourceTestName": "S3 buckets allow only HTTPS traffic (AWS)",          "summary": "19 of 42 S3 buckets failing HTTPS-only at export",          "findingsTotal": 19        }      ]    }  }}

Result

json
{  "importBatchId": "<batch-uuid>",  "dryRun": false,  "source": "vanta",  "summary": "Imported: 1 created, 0 enriched, 0 skipped, 0 failed",  "created": [    {      "id": "test-imp-<sha16>",      "name": "S3 buckets allow only HTTPS traffic (AWS)"    }  ],  "updated": [],  "skipped": [],  "errors": [],  "warnings": []}

list_vendors

Scope
vendors:read

List your organization's managed vendors: name, inherent risk, owners, category, review dates, compliance attestations, and import provenance. Optional status filter (active/archived/pending/inactive). Pairs with import_vendors for post-import verification.

Ask your AI client: “What vendors do we have, and who owns each one?”

Input

FieldTypeDescription
statusoptional'active' | 'archived' | 'pending' | 'inactive'Filter by vendor lifecycle status. Omit to list all.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_vendors",    "arguments": {}  }}

Result

json
{  "vendors": [    {      "id": "vendor-imp-<sha16>",      "name": "Stripe",      "website": "stripe.com",      "status": "active",      "inherentRisk": "high",      "category": "Payments",      "securityOwner": "security@example.com",      "businessOwner": null,      "businessPurpose": null,      "dataAccess": null,      "compliance": [        "SOC 2"      ],      "securityReviewStatus": "up_to_date",      "lastReviewedAt": "2025-09-23",      "nextReviewDueAt": "2026-09-23T00:00:00.000Z",      "source": "manual",      "importBatchId": "<batch-uuid>"    }  ],  "count": 1}

list_risks

Scope
risks:read

List your organization's risk register: title, category, inherent/residual scores + levels (5×5 bands), treatment, owner, review dates, fraud-risk flag, import provenance, and maintenance provenance (maintainedBy: 'strac-ai' | 'vendor-sync' | 'manual'; basis; basisKind; aiStale). A 'strac-ai' row is kept current by Strac and counts as risk-program evidence only once a person approves the register. Optional status filter (identified/mitigating/accepted/closed).

Ask your AI client: “Show me our open risks and their residual levels.”

Input

FieldTypeDescription
statusoptional'identified' | 'mitigating' | 'accepted' | 'closed'Filter by risk status. Omit to list all.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_risks",    "arguments": {      "status": "mitigating"    }  }}

Result

json
{  "risks": [    {      "id": "risk-imp-<sha16>",      "title": "Unauthorized access to production",      "category": "security",      "likelihood": 3,      "impact": 4,      "inherentRiskScore": 12,      "inherentRiskLevel": "high",      "residualRiskScore": 6,      "residualRiskLevel": "medium",      "treatment": "mitigate",      "status": "mitigating",      "owner": "security@example.com",      "isFraudRisk": false,      "nextReviewDate": "2027-06-05",      "acceptanceApprover": null,      "externalRef": "R-25",      "importBatchId": "<batch-uuid>"    }  ],  "count": 1}

Vendor lifecycle (write)

Run a vendor's deterministic risk assessment (single or bulk fan-out, with a dryRun preview), record a completed CC6.1/CC9.2 security review, and file the documents a vendor hands you (SOC 2 report, PCI AOC, DPA, pen-test summary) against their record — the vendor-lifecycle actions from the in-app Vendors page, exposed to agents. Assessments reuse the same engine as the Assess button; a recorded review stamps the vendor up to date and is marked agent-recorded in the binder. For vendor documents, prefer the presigned begin/finalize pair (≤50 MB, bytes straight to S3) over the inline base64 tool.

run_vendor_risk_assessment

Scope
vendors:write

Run the deterministic risk assessment for one managed vendor (the same engine as the in-app Assess button). Returns inherent + residual risk bands (low/medium/high/critical), the 1–25 scores, top drivers, evidence confidence, and a plain-English rationale. The engine auto-loads the vendor's own evidence (documents, reviews, issues, integrations). You don't pass evidence in. Set dryRun: true to preview the band without writing; otherwise it records a point-in-time assessment and updates the vendor's displayed risk. Resolve vendorId with list_vendors. Requires admin/owner.

Ask your AI client: “Assess Stripe's vendor risk.”

Input

FieldTypeDescription
vendorIdrequiredstringThe managed vendor to assess (resolve with list_vendors).
dryRunoptionalbooleantrue → compute and return the band with ZERO writes.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "run_vendor_risk_assessment",    "arguments": {      "vendorId": "vendor-imp-<sha16>"    }  }}

Result

json
{  "dryRun": false,  "persisted": true,  "vendorId": "vendor-imp-<sha16>",  "vendorName": "Stripe",  "assessmentId": "risk-assessment-<uuid>",  "inherent": {    "band": "high",    "score": 16,    "impact": 4,    "likelihood": 4  },  "residual": {    "band": "medium",    "score": 9,    "impact": 3,    "likelihood": 3  },  "evidenceConfidence": "medium",  "mirroredInherentRisk": "medium",  "rationale": "Residual risk is medium (impact 3 × likelihood 3 = 9/25)…"}

run_vendor_risk_assessments

Scope
vendors:write

Run the deterministic risk assessment for MULTIPLE managed vendors in one call (the fan-out version of run_vendor_risk_assessment). Pass up to 25 vendorIds; chunk larger sets across calls. Returns a per-vendor result plus a per-vendor errors[] — a bad id never fails the others. Set dryRun: true to preview every band with ZERO writes. There is no bulk security-review. Review findings are vendor-specific, so use record_vendor_security_review per vendor. Requires admin/owner.

Ask your AI client: “Assess all my high-risk vendors — preview first.”

Input

FieldTypeDescription
vendorIdsrequiredarray, 1–25Managed vendor ids to assess (resolve with list_vendors). Duplicates are assessed once.
dryRunoptionalbooleantrue → compute every band with ZERO writes.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "run_vendor_risk_assessments",    "arguments": {      "vendorIds": [        "vendor-imp-<sha16-a>",        "vendor-imp-<sha16-b>"      ],      "dryRun": true    }  }}

Result

json
{  "dryRun": true,  "summary": "DRY RUN — would assess: 2 ok, 0 failed of 2",  "assessed": [    {      "vendorId": "vendor-imp-<sha16-a>",      "vendorName": "Stripe",      "inherentBand": "high",      "residualBand": "medium",      "residualScore": 9,      "evidenceConfidence": "medium",      "assessmentId": null    }  ],  "errors": []}

record_vendor_security_review

Scope
vendors:write

Record a COMPLETED security review for one managed vendor (the CC6.1/CC9.2 attestation auditors expect). Marks the vendor's security review up to date and computes the next review due date. summaryOfFindings is required (10–2000 chars). This is the audit evidence of what was reviewed, since files can't be uploaded over MCP; optionally pin existing vendor documents with reviewedDocIds. The review actor is taken from your token; reviewer is an optional display-only name. Recording the same vendor + outcome + findings twice is idempotent. Requires admin/owner.

Ask your AI client: “Record a passed security review for Stripe. I reviewed their SOC 2 and DPA.”

Input

FieldTypeDescription
vendorIdrequiredstringThe managed vendor being reviewed (resolve with list_vendors).
outcomerequired'passed' | 'passed_with_exceptions' | 'failed'The review verdict.
summaryOfFindingsrequiredstring, 10–2000What was checked and found (the CC9.2 evidence of record).
reviewedDocIdsoptionalarray, ≤50Ids of this vendor's existing documents the reviewer attests to having reviewed. Ids not belonging to this vendor are dropped.
revieweroptionalstringDisplay-only name of the human reviewer, if different from the caller. Does not change the audit actor.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "record_vendor_security_review",    "arguments": {      "vendorId": "vendor-imp-<sha16>",      "outcome": "passed",      "summaryOfFindings": "Reviewed SOC 2 report and DPA; no exceptions found."    }  }}

Result

json
{  "reviewId": "review-mcp-<sha16>",  "vendorId": "vendor-imp-<sha16>",  "status": "complete",  "outcome": "passed",  "completedAt": "2026-07-14T18:56:43.620Z",  "reviewer": "you@example.com",  "recordedBy": "you@example.com",  "evidenceChannel": "summary",  "reviewedEvidenceIds": [],  "droppedDocIds": [],  "idempotentReplay": false,  "vendorSecurityReviewStatus": "up_to_date",  "securityReviewDate": "2026-07-14T18:56:43.620Z",  "nextReviewDueAt": "2027-07-14T00:00:00.000Z"}

attach_vendor_document

Scope
vendors:write

Upload a document FROM a vendor (their SOC 2 report, DPA, pen-test summary, security questionnaire…) and attach it to one managed vendor — the third-party evidence auditors expect for CC6.1/CC9.2. Accepts inline base64 content, max 4 MB after decode; for a PCI DSS AOC use docType 'other' with a descriptive name. PREFER begin_vendor_document_upload + finalize_vendor_document_upload for any real file — the presigned handshake streams the bytes straight to S3 (≤50 MB, no base64-through-the-model). Resolve vendorId with list_vendors. Requires admin/owner.

Ask your AI client: “Attach this small DPA PDF to Example CRM's vendor record.”

Input

FieldTypeDescription
vendorIdrequiredstring, 1–200The managed vendor to attach the document to (resolve with list_vendors).
docTyperequireddocType enumOne of soc2_report, iso27001_cert, dpa, msa, sla, security_questionnaire, privacy_policy, security_whitepaper, pentest_summary, subprocessor_list, other. For a PCI DSS Attestation of Compliance use 'other' with a descriptive name.
namerequiredstring, 1–200Human-readable document name, e.g. 'Example CRM SOC 2 Type II 2026'.
contentTyperequiredMIME enumMIME type of the file (pdf, docx, doc, xlsx, xls, pptx, png, jpeg, gif, webp, plain, csv, json, zip). Other types are rejected.
contentBase64requiredstring (base64)Base64-encoded file content. Max 4 MB after decode (~5.3 MB base64).
expiresAtoptionalstring, 1–40Optional expiry date (ISO 8601), e.g. the SOC 2 report period end or the attestation expiry.
notesoptionalstring, 0–2000Optional free-text notes.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "attach_vendor_document",    "arguments": {      "vendorId": "vendor-demo1",      "docType": "dpa",      "name": "Example CRM DPA 2026",      "contentType": "application/pdf",      "contentBase64": "<base64-bytes>"    }  }}

Result

json
{  "documentId": "doc-<uuid>",  "vendorId": "vendor-demo1",  "docType": "dpa",  "name": "Example CRM DPA 2026",  "status": "current",  "s3Key": "vendor-documents/comp_demo/vendor-demo1/<uuid>-Example_CRM_DPA_2026.pdf",  "fileSize": 248192,  "fileType": "application/pdf",  "sha256": "<sha256>",  "uploadedAt": "2026-07-16T18:56:43.620Z",  "uploadedBy": "you@example.com"}

begin_vendor_document_upload

Scope
vendors:write

Step 1 of the presigned vendor-document handshake — the PREFERRED way to attach a real file FROM a vendor (SOC 2 report, PCI AOC, DPA, pen-test summary…) to one managed vendor. Returns a short-lived S3 PUT URL. Upload the raw bytes with curl -T, then call finalize_vendor_document_upload with the returned uploadId. The bytes stream straight to S3 (≤50 MB), never base64-through-the-model. All routing metadata (vendorId, docType, name, expiresAt, notes) is pinned here; finalize takes only the uploadId. Resolve vendorId with list_vendors. Requires admin/owner.

Ask your AI client: “Upload Example CRM's SOC 2 Type II report and attach it to their vendor record.”

Input

FieldTypeDescription
vendorIdrequiredstring, 1–200The managed vendor to attach the document to (resolve with list_vendors). Validated at begin and re-validated at finalize.
docTyperequireddocType enumOne of soc2_report, iso27001_cert, dpa, msa, sla, security_questionnaire, privacy_policy, security_whitepaper, pentest_summary, subprocessor_list, other. For a PCI DSS Attestation of Compliance use 'other' with a descriptive name.
namerequiredstring, 1–200Human-readable document name, e.g. 'Example CRM SOC 2 Type II 2026'.
contentTyperequiredMIME enumMIME type of the file; must match the Content-Type sent on the PUT (pdf, docx, doc, xlsx, xls, pptx, png, jpeg, gif, webp, plain, csv, json, zip).
expiresAtoptionalstring, 1–40Optional expiry date (ISO 8601), e.g. the SOC 2 report period end or the attestation expiry.
notesoptionalstring, 0–2000Optional free-text notes.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_vendor_document_upload",    "arguments": {      "vendorId": "vendor-demo1",      "docType": "soc2_report",      "name": "Example CRM SOC 2 Type II 2026",      "contentType": "application/pdf",      "expiresAt": "2027-03-31"    }  }}

Result

json
{  "uploadId": "<upload-id>",  "uploadUrl": "https://<bucket>.s3.amazonaws.com/vendor-documents/comp_demo/vendor-demo1/_staging/<upload-id>/Example_CRM_SOC_2_Type_II_2026.pdf?X-Amz-Signature=<sig>",  "uploadExpiresAt": "2026-07-16T19:10:00.000Z",  "maxBytes": 52428800,  "contentType": "application/pdf",  "next": "Upload the file bytes directly to S3, then finalize:\n  1. curl -X PUT -T <path-to-file> -H 'Content-Type: …' '<uploadUrl>'\n  2. call finalize_vendor_document_upload with this uploadId"}

finalize_vendor_document_upload

Scope
vendors:write

Step 2 of the vendor-document handshake. After you PUT the file bytes to the URL from begin_vendor_document_upload, pass the uploadId; the server reads the uploaded object (≤50 MB), version-pinned-copies it to the vendor's final key, and stores it as the vendor's current evidence on its Documents tab. Idempotent — a repeated call with the same uploadId returns the same result. Returns precondition_failed if the file was not uploaded first. Requires admin/owner.

Ask your AI client: “Finalize the vendor document upload. Here is the uploadId.”

Input

FieldTypeDescription
uploadIdrequiredstring, 1–80The uploadId returned by begin_vendor_document_upload.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "finalize_vendor_document_upload",    "arguments": {      "uploadId": "<upload-id>"    }  }}

Result

json
{  "documentId": "doc-<uuid>",  "vendorId": "vendor-demo1",  "docType": "soc2_report",  "name": "Example CRM SOC 2 Type II 2026",  "status": "current",  "s3Key": "vendor-documents/comp_demo/vendor-demo1/<uuid>-Example_CRM_SOC_2_Type_II_2026.pdf",  "fileSize": 4823040,  "fileType": "application/pdf",  "sha256": "<sha256>",  "uploadedAt": "2026-07-16T18:56:43.620Z",  "uploadedBy": "you@example.com",  "s3VersionId": "<s3-version-id>"}

Integration handoff

See what is already connected, then drive a browser-based OAuth handoff to connect GWS / AWS / Slack and poll to completion.

list_integrations

Scope
compliance:read

The tenant's connected integrations, so a client never re-suggests one that exists. `isLive` means a real connection (anything except mid-setup pending or removed/disconnected); `mcpConnectable`/`mcpAlias` mark types connect_integration can wire up over MCP. Echoes the bound organizationId/organizationName so a multi-tenant client can confirm which org the token resolves to.

Ask your AI client: “Which integrations do we already have connected?”

No arguments.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_integrations",    "arguments": {}  }}

Result

json
{  "organizationId": "comp_demo",  "organizationName": "Acme Inc",  "integrations": [    {      "type": "google-workspace",      "label": "Google Workspace",      "status": "active",      "isLive": true,      "mcpConnectable": true,      "mcpAlias": "gws",      "connectedAt": "2026-03-01T00:00:00.000Z"    }  ],  "count": 1,  "liveTypes": [    "google-workspace"  ],  "connectableViaMcp": [    "gws",    "aws",    "slack"  ],  "note": "Skip any type with isLive:true. It is already connected. Suggest connect_integration only for connectableViaMcp types not already live; point other tools to comply.strac.io/integrations."}

connect_integration

Scope
compliance:read

Returns a connect URL the user opens in a browser to authorize GWS / AWS / Slack. Poll get_connection_status until it flips. 30-minute window.

Ask your AI client: “Connect our Google Workspace.”

Input

FieldTypeDescription
integrationTyperequiredstringWhich integration to connect.gwsawsslack

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "connect_integration",    "arguments": {      "integrationType": "gws"    }  }}

Result

json
{  "connectionId": "<conn-uuid>",  "connectUrl": "https://comply.strac.io/integrations?connect=gws&mcp_connection_id=<conn-uuid>",  "integrationType": "gws",  "integrationLabel": "Google Workspace",  "expiresAt": "2026-05-27T16:30:00.000Z",  "state": "pending",  "pollWith": "get_connection_status({ connectionId: \"<conn-uuid>\" })"}

get_connection_status

Scope
compliance:read

Polls a pending connection. Returns connected (with integrationId), pending, timeout, or not_found.

Ask your AI client: “Did the Google Workspace connection finish?”

Input

FieldTypeDescription
connectionIdrequiredstring, 1–120The connectionId from connect_integration.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_connection_status",    "arguments": {      "connectionId": "<conn-uuid>"    }  }}

Result

json
{  "connectionId": "<conn-uuid>",  "state": "connected",  "integrationType": "gws",  "integrationId": "gws-acme",  "createdAt": "2026-05-27T16:00:00.000Z",  "expiresAt": "2026-05-27T16:30:00.000Z",  "message": "Google Workspace connected."}

Auditor replies

Respond to an auditor's evidence requests entirely over MCP (no dashboard required). List/read requests, then prepare a DRAFT reply from existing binder evidence, a newly uploaded file, or a text note, and submit it to send. Prepare and submit are separate steps so the auditor is never emailed by accident.

list_audit_requests

Scope
audits:read

List the evidence requests an auditor has raised on an audit (what they're waiting on), with response/draft counts and who created each.

Ask your AI client: “What is the auditor waiting on for the 2026 SOC 2 audit?”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id from list_audits.
statusoptionalstringRequest status filter.requestedsubmittedacceptedrejectednarequest-moreallDefault: all

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "list_audit_requests",    "arguments": {      "auditId": "<audit-uuid>",      "status": "requested"    }  }}

Result

json
{  "auditId": "<audit-uuid>",  "requests": [    {      "requestId": "<request-uuid>",      "controlId": "cc1.1",      "title": "Provide Q1 access review",      "status": "requested",      "evidenceType": "manual",      "createdByKind": "auditor",      "dueDate": null,      "responseCount": 0,      "draftCount": 0,      "auditorAttachmentCount": 1,      "createdAt": "2026-05-22T00:00:00.000Z"    }  ],  "count": 1}

get_audit_request

Scope
audits:read

One auditor request in full, plus attachableEvidenceRefs — the binder evidence already mapped to its control (the valid evidenceRefIds for prepare_audit_request_response).

Ask your AI client: “What evidence can I attach to answer this auditor request?”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id.
requestIdrequiredstring, 1–200Request id from list_audit_requests.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_audit_request",    "arguments": {      "auditId": "<audit-uuid>",      "requestId": "<request-uuid>"    }  }}

Result

json
{  "auditId": "<audit-uuid>",  "requestId": "<request-uuid>",  "controlId": "cc1.1",  "title": "Provide Q1 access review",  "status": "requested",  "attachableEvidenceRefs": [    {      "refId": "<ref-hash>",      "type": "policy",      "sourceId": "POL-SEC-001",      "versionId": "v3",      "title": "Access Control Policy",      "capturedAt": "2026-04-01T00:00:00.000Z"    }  ],  "responses": [],  "auditorAttachments": []}

get_audit_request_attachment_download_url

Scope
audits:read

Short-lived (1h) download URL for one of the auditor's own attachments on a request (attachmentId from get_audit_request).

Ask your AI client: “Download the example the auditor attached to this request.”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id.
requestIdrequiredstring, 1–200Request id.
attachmentIdrequiredstring, 1–120Attachment id from get_audit_request.auditorAttachments.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "get_audit_request_attachment_download_url",    "arguments": {      "auditId": "<audit-uuid>",      "requestId": "<request-uuid>",      "attachmentId": "<attachment-uuid>"    }  }}

Result

json
{  "downloadUrl": "https://s3.amazonaws.com/...signed...",  "fileName": "sample-access-review.xlsx",  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",  "expiresInSeconds": 3600}

begin_audit_response_upload

Scope
audit-requests:write

Begin a presigned upload for a NEW file to attach to an auditor request: returns { uploadId, uploadUrl }. PUT the bytes to uploadUrl, then pass uploadId to prepare_audit_request_response. For evidence already in the binder, use evidenceRefIds instead.

Ask your AI client: “I have a fresh access-review PDF to attach to this request.”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id.
requestIdrequiredstring, 1–200Request id.
fileNamerequiredstring, 1–255Original filename.
fileSizerequirednumber (≤ 50MB)File size in bytes.
contentTyperequiredstringMIME type (PDF/Office/CSV/PNG/JPEG/ZIP/text).

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "begin_audit_response_upload",    "arguments": {      "auditId": "<audit-uuid>",      "requestId": "<request-uuid>",      "fileName": "q1-access-review.pdf",      "fileSize": 524288,      "contentType": "application/pdf"    }  }}

Result

json
{  "uploadId": "<upload-uuid>",  "uploadUrl": "https://s3.amazonaws.com/...signed-put...",  "expiresInSeconds": 600,  "next": "PUT the file bytes to uploadUrl, then call prepare_audit_request_response with uploadId=\"<upload-uuid>\"."}

prepare_audit_request_response

Scope
audit-requests:write

Prepare a DRAFT response to an auditor request — does NOT send. Answer with existing binder evidence (evidenceRefIds from get_audit_request), a newly uploaded file (uploadId), and/or a text note. At least one is required. Idempotent. Submit it with submit_audit_request_response (or approve in the dashboard).

Ask your AI client: “Draft a reply to this request using the access policy already in our binder.”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id.
requestIdrequiredstring, 1–200Request id.
messageoptionalstring, 1–4000Optional note to the auditor.
evidenceRefIdsoptionalstring[] (≤ 50)refIds from get_audit_request.attachableEvidenceRefs.
uploadIdoptionalstring, 1–120An uploadId from begin_audit_response_upload (after the PUT).
idempotencyKeyoptionalstring, 1–120Usually omit — idempotency is content-derived.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "prepare_audit_request_response",    "arguments": {      "auditId": "<audit-uuid>",      "requestId": "<request-uuid>",      "message": "Q1 access review attached.",      "evidenceRefIds": [        "<ref-hash>"      ]    }  }}

Result

json
{  "deliveryState": "draft_prepared",  "auditorNotified": false,  "awaitingHumanApproval": true,  "requestStatusAfter": "requested",  "responseId": "d-<sha16>",  "evidenceRefsAttached": 1,  "fileAttached": false,  "idempotentReplay": false,  "humanNextStep": "A teammate approves this in Strac → Audits → this audit → Requests → Submit to auditor. The auditor is NOT notified until then.",  "dashboardApprovalUrl": "/compliance/audits/<audit-uuid>?tab=requests"}

submit_audit_request_response

Scope
audit-requests:write

SEND a prepared draft response to the auditor (the deliberate approve step). Flips the draft to submitted, sets the request status to submitted, and notifies the auditor. Irreversible. Idempotent — re-submitting is a no-op.

Ask your AI client: “Send the draft reply to the auditor.”

Input

FieldTypeDescription
auditIdrequiredstring, 1–120Audit id.
requestIdrequiredstring, 1–200Request id.
responseIdrequiredstring, 1–120The draft responseId from prepare_audit_request_response.

tools/call request

json
{  "jsonrpc": "2.0",  "id": 1,  "method": "tools/call",  "params": {    "name": "submit_audit_request_response",    "arguments": {      "auditId": "<audit-uuid>",      "requestId": "<request-uuid>",      "responseId": "d-<sha16>"    }  }}

Result

json
{  "deliveryState": "submitted",  "auditorNotified": true,  "alreadySubmitted": false,  "requestStatusAfter": "submitted",  "responseId": "d-<sha16>",  "message": "Submitted to the auditor. The request is now marked submitted and the auditor was notified."}

Tool error envelope

tools/call error results carry a discriminator on _meta["strac.io/errorCode"] so clients branch on shape, not free text. Always present on errors; the message is capped at 500 chars.

jsonerror result
{  "jsonrpc": "2.0",  "id": 1,  "result": {    "content": [{ "type": "text", "text": "test result 'abc-123' not found" }],    "_meta": { "strac.io/errorCode": "not_found" },    "isError": true  }}
errorCodeRecover by
not_foundTry a different id; check the corresponding list_* first.
invalid_argumentArgs passed Zod but failed semantic validation. Consult the tool input schema.
precondition_failedResource exists but is in a state that blocks the action. Mutate state first.
unauthorizedBearer scope is fine but a tenant-internal role gate rejected. Use a higher-role bearer.
unknownHandler signaled a client-facing error without a category. Inspect the message.
internalServer-side failure. Retry; if persistent, contact support. The message is generic by design.

Tenant-isolation invariants

Safe to build against

Identity is server-derived from the bearer — body-supplied userId/createdBy are rejected by strict-mode Zod. Every read partitions by your organization; cross-tenant access returns not_found, never a leak. Every write fans out to an append-only audit log. Evidence attached to an audit is version-pinned at attach time.

See also