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:
claude mcp add --transport http --scope user strac-comply \ https://mcp.comply.strac.io/mcp- Run the command above.
--scope usermakes the server available in every Claude Code session, not just the current folder. - Start (or restart) Claude Code. MCP config is read at launch.
- Run
/mcp→ selectstrac-comply→ Authenticate. 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.
| Scope | Admits |
|---|---|
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:writepolicies:approvedocuments:writeevidence:writevendors:writerisks: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.
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
ScopeReturns 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
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_frameworks", "arguments": {} }}Result
{ "organizationName": "Acme Inc", "frameworks": [ { "frameworkId": "soc2", "label": "SOC 2" }, { "frameworkId": "iso27001", "label": "ISO 27001:2022" } ], "count": 2}get_compliance_status
ScopeAggregate 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
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_compliance_status", "arguments": {} }}Result
{ "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
ScopeCursor-paginated control list with completion % and readiness signal.
Ask your AI client: “List the SOC 2 controls that still have gaps.”
Input
| Field | Type | Description |
|---|---|---|
| frameworkIdoptional | string, 1–80 | Filter to one framework. |
| limitoptional | integer 1–200 | Page size.Default: 50 |
| cursoroptional | string | Opaque pagination cursor from a prior nextCursor. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_controls", "arguments": { "frameworkId": "soc2", "limit": 50 } }}Result
{ "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
ScopeA 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
| Field | Type | Description |
|---|---|---|
| controlIdrequired | string, 1–120 | Control id, e.g. CC6.1. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_control", "arguments": { "controlId": "CC6.1" } }}Result
{ "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
ScopeThe 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
| Field | Type | Description |
|---|---|---|
| frameworkIdoptional | string | Optional framework to restrict to, e.g. SOC2. Omit for all active frameworks. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_next_actions", "arguments": { "frameworkId": "SOC2" } }}Result
{ "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
ScopePolicies with lifecycle status and review dates.
Ask your AI client: “What policies do we have and which are overdue for review?”
Input
| Field | Type | Description |
|---|---|---|
| frameworkIdoptional | string, 1–80 | Filter by framework. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_policies", "arguments": {} }}Result
{ "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
ScopeA 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
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | Policy id, e.g. POL-SEC-001. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_policy", "arguments": { "policyId": "POL-SEC-001" } }}Result
{ "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
ScopeCanonical SOC 2 catalog docs + ad-hoc uploads, with lifecycle status.
Ask your AI client: “List our catalog documents and their publish status.”
Input
| Field | Type | Description |
|---|---|---|
| sourceoptional | string | Which documents to include.catalogadhocallDefault: all |
| frameworkIdoptional | string, 1–80 | Filter catalog docs by framework. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_documents", "arguments": { "source": "all" } }}Result
{ "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
ScopeDirectory snapshot (no sensitive PII). Sourced from Google Workspace if connected.
Ask your AI client: “Who's on the active engineering roster?”
Input
| Field | Type | Description |
|---|---|---|
| statusoptional | string | Employment status filter.activeterminatedallDefault: active |
| departmentoptional | string, 1–120 | Case-sensitive department match. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_employees", "arguments": { "status": "active", "department": "Engineering" } }}Result
{ "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
ScopeOnboarding/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
| Field | Type | Description |
|---|---|---|
| typeoptional | string | Checklist-type filter.onboardingoffboardingallDefault: all |
| statusoptional | string | Status filter.activecompletedvoidedallDefault: all |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_lifecycle_checklists", "arguments": { "type": "offboarding", "status": "active" } }}Result
{ "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
ScopeOne 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
| Field | Type | Description |
|---|---|---|
| checklistIdrequired | string, 1–200 | The episodeId from list_lifecycle_checklists. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_lifecycle_checklist", "arguments": { "checklistId": "ep_demo1" } }}Result
{ "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
ScopeThe 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
| Field | Type | Description |
|---|---|---|
| categoryoptional | string | Filter returned rows to one category; org-wide counts are unaffected.computedata-storescode-registriesnetwork-edgesecurity-infrasaas-applicationsdevicesother |
| includeScopedOutoptional | boolean | Include resources the admin marked out of scope (with their reason).Default: false |
| limitoptional | integer 1–500 | Max rows returned.Default: 100 |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_inventory", "arguments": { "category": "data-stores", "limit": 100 } }}Result
{ "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
ScopeThe 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
| Field | Type | Description |
|---|---|---|
| statusoptional | string | Account status filter.activesuspendedallDefault: all |
| mfaEnrolledoptional | boolean | Filter by MFA enrollment; accounts whose source does not report MFA (null) are excluded by either value. |
| limitoptional | integer 1–500 | Cap results at N.Default: 200 |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_saas_accounts", "arguments": { "status": "active", "mfaEnrolled": false } }}Result
{ "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
ScopePersisted 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
| Field | Type | Description |
|---|---|---|
| stateoptional | string | Triage-state filter.needs_reviewignoredrejectedallDefault: all |
| limitoptional | integer 1–500 | Cap results at N.Default: 200 |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_saas_apps", "arguments": { "state": "needs_review" } }}Result
{ "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
ScopeAudits with lifecycle status and pending-request counts.
Ask your AI client: “What audits are in flight and how many requests are pending?”
Input
| Field | Type | Description |
|---|---|---|
| statusoptional | string | Lifecycle filter.planningin-flightcompleteallDefault: all |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_audits", "arguments": { "status": "in-flight" } }}Result
{ "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
ScopeA single audit with sampled-control count, evidence-request breakdown, and findings rollup.
Ask your AI client: “Summarize the 2026 SOC 2 audit binder.”
Input
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id (UUID). |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_audit", "arguments": { "auditId": "<audit-uuid>" } }}Result
{ "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
ScopeThe 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | The auditId from list_audits. |
| includeNotReviewedoptional | boolean | Include controls the auditor has not yet reviewed.Default: false |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_audit_verdicts", "arguments": { "auditId": "<audit-uuid>" } }}Result
{ "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
ScopeAutomated 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
| Field | Type | Description |
|---|---|---|
| statusoptional | string | Result status filter.passingfailingskippederrorallDefault: all |
| severityoptional | string | Severity filter.criticalhighmediumlowinfoallDefault: all |
| integrationIdoptional | string, 1–120 | Filter to one integration, e.g. aws-123456789012. |
| limitoptional | integer 1–500 | Max results.Default: 200 |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_tests", "arguments": { "status": "failing", "severity": "high" } }}Result
{ "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
ScopeOne 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
| Field | Type | Description |
|---|---|---|
| testResultIdrequired | string, 1–200 | Test result id. |
| includeScreenshotDownloadUrloptional | boolean | Mint a short-lived (5 min) presigned download link for a ready console screenshot.Default: false |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_test_result", "arguments": { "testResultId": "<result-uuid>" } }}Result
{ "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
ScopeRe-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
| Field | Type | Description |
|---|---|---|
| testIdoptional | string, 1–200 | Test-definition id to re-run (e.g. 'gws-mfa-enabled'). One of testId / testResultId is required. |
| testResultIdoptional | string, 1–200 | Alternatively, the result-row id (from list_tests / get_test_result); its testId is resolved server-side. |
| aiReasoningoptional | string, 1–2000 | Optional rationale recorded on the audit-log row. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "rerun_test", "arguments": { "testId": "aws-s3-encryption" } }}Result
{ "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
ScopePolls 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
| Field | Type | Description |
|---|---|---|
| executionIdrequired | string, 1–200 | The executionId returned by rerun_test. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_test_execution", "arguments": { "executionId": "<execution-id>" } }}Result
{ "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
ScopePROPOSES 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
| Field | Type | Description |
|---|---|---|
| testIdrequired | string, 1–200 | The failing test (from list_tests / get_next_actions). Must be accept-risk-eligible. |
| resourceIdoptional | string, 1–400 | The flagged resource to accept (from the test finding). One of resourceId / findingId is required. |
| findingIdoptional | string, 1–200 | Alternatively, the finding id (resolved + tenant-checked server-side). |
| rationalerequired | string, 10–2000 | Why this risk is acceptable. Goes verbatim into the audit binder for the auditor. |
| affectedControlsoptional | string[] (≤50) | Optional control ids this exception touches — display-only context for the human approver. |
| proposedReviewByoptional | string, 1–40 | Optional re-review date (YYYY-MM-DD).Default: +12 months |
tools/call request
{ "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
{ "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
ScopeLists 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
| Field | Type | Description |
|---|---|---|
| testResultIdrequired | string, 1–200 | The id of the test result row from list_tests / get_test_result. |
| includeDownloadUrlsoptional | boolean | When true, include a short-lived presigned download URL per evidence file.Default: false |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_test_evidence", "arguments": { "testResultId": "<result-id>" } }}Result
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| testResultIdrequired | string, 1–200 | The id of the test result row from list_tests / get_test_result (the testResultId field, NOT the secondary testId). |
| namerequired | string, 1–256 | Human-readable evidence name, e.g. 'Q1 access review export'. |
| contentTyperequired | string, 1–128 | MIME type of the file (e.g. application/pdf, image/png). Must match the Content-Type sent on the PUT. |
| notesoptional | string, ≤2048 | Optional context for auditors. |
tools/call request
{ "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
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| uploadIdrequired | string, 1–80 | The uploadId returned by begin_test_evidence_upload. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_test_evidence_upload", "arguments": { "uploadId": "<upload-id>" } }}Result
{ "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
ScopeMarks 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
| Field | Type | Description |
|---|---|---|
| controlIdrequired | string, 1–120 | Control id, e.g. CC6.1. |
| reasonrequired | string, 10–500 | Why the control does not apply. |
| aiReasoningoptional | string, 1–2000 | Optional rationale recorded on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeMarks 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
| Field | Type | Description |
|---|---|---|
| evidenceTyperequired | 'document' | 'policy' | 'integration' | Which kind of slot: 'document' (catalog document), 'policy', or 'integration' (an integration-coverage slot on one control). |
| evidenceIdrequired | string, 1–200 | Catalog document id (from list_documents), policy id (from list_policies), or the integration-coverage item id from the control's required-evidence checklist. |
| controlIdoptional | string, 1–200 | Required when evidenceType is 'integration' — the control the slot belongs to (e.g. soc2-cc1.3). Rejected for the other kinds. |
| reasonrequired | string, 10–500 | Customer-visible justification, shown in the N/A badge and the audit binder. |
| aiReasoningoptional | string, 1–2000 | Optional rationale recorded on the audit-log row (not user-visible). |
tools/call request
{ "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
{ "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
ScopeLinks 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
| Field | Type | Description |
|---|---|---|
| controlIdrequired | string, 1–120 | Control id, e.g. CC6.1. |
| evidenceTyperequired | string | Kind of evidence.policydocument |
| referenceIdrequired | string, 1–120 | Policy id or document id to attach. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "attach_evidence", "arguments": { "controlId": "CC6.1", "evidenceType": "policy", "referenceId": "POL-SEC-001" } }}Result
{ "controlId": "CC6.1", "evidenceType": "policy", "referenceId": "POL-SEC-001", "attached": true, "noop": false, "totalControlsLinked": 4}upload_document
ScopeUploads 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
| Field | Type | Description |
|---|---|---|
| titlerequired | string, 1–200 | Document title. |
| descriptionoptional | string, 0–2000 | Optional description. |
| contentTyperequired | MIME enum | One 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. |
| contentBase64required | string (base64) | Base64 bytes, ≤4 MB after decode. |
| controlIdsrequired | string[], 1–20 | control ids stored on the document (e.g. ['soc2-cc6.1']) — at least one is required. For completion %, also call attach_evidence per control. |
| frameworksoptional | string[], cap 10 | additional 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). |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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"}add_document_link
ScopeSatisfies a canonical catalog document slot (DOC-*) with a stored https URL + title instead of an uploaded file — for artifacts that live outside Strac and can't be downloaded (a shared Drive file, an internal wiki page). Counts toward completion exactly like an uploaded file but renders as the distinct 'linked' state and is never shown on public surfaces; a later real-file upload supersedes it (file wins), and re-linking the same url is a no-op.
Ask your AI client: “Our incident report lives in Google Drive — link it into the DOC-CC7-INCIDENT-REPORT slot.”
Input
| Field | Type | Description |
|---|---|---|
| documentIdrequired | string, 1–120 | The canonical catalog slot id (e.g. 'DOC-CC7-INCIDENT-REPORT'). |
| urlrequired | string (https), 1–2048 | The external artifact URL (https only), stored verbatim. May be an internal link, never exposed publicly. |
| titleoptional | string, 1–300 | Human title for the linked artifact. Defaults to the canonical slot title. |
| sourceoptional | string, 1–60 | Coarse provenance label — 'vanta' | 'google-drive' | 'pagerduty' | 'confluence' | 'web'. Drives the 'held externally' copy.Default: web |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "add_document_link", "arguments": { "documentId": "DOC-CC7-INCIDENT-REPORT", "url": "https://drive.google.com/file/d/<file-id>/view", "title": "Incident Report. March 2026", "source": "google-drive" } }}Result
{ "documentId": "DOC-CC7-INCIDENT-REPORT", "versionId": "<version-uuid>", "versionNumber": 1, "publishedAt": "2026-06-05T16:00:00.000Z", "publishedBy": "ciso@example.com", "nextReviewDate": "2027-06-05T16:00:00.000Z", "noop": false, "evidenceKind": "external-link", "externalUrl": "https://drive.google.com/file/d/<file-id>/view", "externalTitle": "Incident Report. March 2026", "externalSource": "google-drive"}If the slot is already satisfied by an uploaded file (or already linked to this url), returns { noop: true, reason, documentId, versionId, evidenceKind } without writing.
upload_policy
ScopeCreates 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
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | Canonical policy id, e.g. POL-SEC-001. |
| markdownoptional | string | Policy body as markdown (sanitized). Mutually exclusive with file fields. |
| fileNameoptional | string, 1–200 | File name with extension. Pair with fileContent. |
| fileContentoptional | string (base64), ≤4 MB raw | Base64 file bytes. Pair with fileName. |
| commentsoptional | string, 0–2000 | Version changelog note. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeAdds 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
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | Policy id. |
| basedOnVersionrequired | positive integer | The version counter you edited from (optimistic lock). |
| markdownoptional | string | New body (markdown). Mutually exclusive with file fields. |
| fileNameoptional | string, 1–200 | File name with extension. Pair with fileContent. |
| fileContentoptional | string (base64), ≤4 MB raw | Base64 file bytes. |
| commentsoptional | string, 0–2000 | Version changelog note. |
| forceoptional | boolean | Skip the stale-version check. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeDeterministic (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
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | Policy id. |
| versionIdoptional | string, 1–120 | Specific version; defaults to current/latest. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "compare_policy", "arguments": { "policyId": "POL-SEC-001" } }}Result
{ "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
ScopeApproves 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
| Field | Type | Description |
|---|---|---|
| versionIdrequired | string, 1–120 | The version id to approve. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "approve_policy", "arguments": { "versionId": "<version-uuid>" } }}Result
{ "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
ScopeOne 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
| Field | Type | Description |
|---|---|---|
| titlerequired | string, 1–200 | Policy title. |
| descriptionoptional | string, 0–2000 | Optional description. |
| categoryoptional | string, 0–80 | Optional category. |
| frameworksoptional | string[] (≤10) | Framework tags. |
| controlIdsoptional | string[] (≤100) | Controls to map; each must exist. |
| markdownoptional | string | Body as markdown. Mutually exclusive with file fields. |
| fileNameoptional | string, 1–200 | File name with extension. Pair with fileContent. |
| fileContentoptional | string (base64), ≤4 MB raw | Base64 file bytes. |
| commentsoptional | string, 0–2000 | Version changelog note. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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}Evidence-coverage links
One uploaded document satisfying multiple policy slots. suggest_policy_coverage (read, advisory) starts an async scan that proposes links, get_policy_coverage polls it, and set_policy_coverage (write) applies a confirmed one. The covered slot inherits the source policy's approval + review clock.
suggest_policy_coverage
ScopeADVISORY ONLY — present suggestions to the human; only after they confirm, call set_policy_coverage. Starts a BACKGROUND coverage scan of ONE uploaded source document — a canonical policy (default) or a canonical document (slotType:'document', which additionally requires documents:read) — to find OTHER canonical slots the same document plausibly satisfies (cross-type included). Returns a jobId + status immediately; poll get_policy_coverage until 'done'. Re-running for the same source is safe and deduped (same jobId, no duplicate scan); a scan that already finished returns its suggestions inline with status 'done'. Writes nothing.
Ask your AI client: “Which other policy slots does our Information Security Policy cover?”
Input
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | The source slot whose uploaded document to read — canonical policy, CUSTOM-POL id, or (with slotType) a DOC-* catalog id. |
| versionIdoptional | string, 1–120 | Specific version to read; defaults to the source's head/latest. |
| slotTypeoptional | 'policy' | 'document' | Kind of the SOURCE slot. 'document' additionally requires the documents:read scope.Default: policy |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "suggest_policy_coverage", "arguments": { "policyId": "POL-SEC-001" } }}Result
{ "jobId": "cov-1a2b3c4d5e6f", "status": "pending", "sourceId": "POL-SEC-001", "versionId": "<version-uuid>", "note": "ENQUEUED — the coverage scan runs in the background. Poll get_policy_coverage with this jobId until status is no longer 'pending'."}If a prior scan of the same source already finished, the result instead comes back inline with status 'done', classifierStatus, and the suggestions array (same shape as get_policy_coverage).
get_policy_coverage
ScopePolls the async coverage scan started by suggest_policy_coverage, using its jobId. While the scan runs, status is 'pending' — poll again shortly; when 'done', read `classifierStatus` ('ok' = ran, empty suggestions = no additional slots; 'error' = render as "suggestions unavailable") and the `suggestions` array; status 'error' means the job itself failed (see `errorReason`). Writes nothing, after the human confirms a slot, call set_policy_coverage.
Ask your AI client: “Check whether that coverage scan on our InfoSec policy finished.”
Input
| Field | Type | Description |
|---|---|---|
| jobIdrequired | string, 1–64 | The jobId returned by suggest_policy_coverage (e.g. 'cov-1a2b3c…'). |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_policy_coverage", "arguments": { "jobId": "cov-1a2b3c4d5e6f" } }}Result
{ "jobId": "cov-1a2b3c4d5e6f", "status": "done", "sourceId": "POL-SEC-001", "versionId": "<version-uuid>", "classifierStatus": "ok", "suggestions": [ { "id": "POL-ISO-002", "type": "policy", "confidence": "high", "rationale": "Section 4 (Access Provisioning) squarely covers the Access Control slot.", "matchedSections": [ "4. Access Provisioning" ] } ], "note": "ADVISORY ONLY. These are AI suggestions, not links. After the human confirms a slot, call set_policy_coverage."}set_policy_coverage
ScopeLink one evidence slot (policy OR catalog document) to be COVERED BY another evidence item (policy or document). Use when a single uploaded document (e.g. a combined Information Security Policy) legitimately satisfies more than one canonical slot, instead of uploading the same file twice. Cross-type is allowed (a document slot covered by a policy, or vice versa). The covered slot inherits the source's approval and review clock. Pass coveredBy: null to unlink. Requires a written rationale that appears in the audit binder. Links are depth-1: a covered item cannot itself cover others. Document slots/sources must be catalog documents and require the documents:write scope.
Ask your AI client: “Our InfoSec policy already covers POL-ISO-002 — link it instead of uploading again.”
Input
| Field | Type | Description |
|---|---|---|
| policyIdrequired | string, 1–120 | The slot being covered — a policy id, or a catalog document id when slotType is "document". |
| slotTypeoptional | 'policy' | 'document' | Evidence type of the slot. Defaults to 'policy'. 'document' requires documents:write. |
| coveredByrequired | string | null | Source policy/document id whose evidence satisfies this slot, or null to unlink. |
| coveredByTypeoptional | 'policy' | 'document' | Source evidence type ('policy' or 'document'). Defaults to 'policy'. |
| rationaleoptional | string, 10–500 | Customer-visible justification. Required when setting; shown to auditors. |
| aiReasoningoptional | string, 1–2000 | Optional internal AI reasoning on the audit-log row. |
| suggestionConfidenceoptional | 'high' | 'medium' | 'low' | When applying a suggest_policy_coverage suggestion, its confidence for this slot. Recorded on the audit row only. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "set_policy_coverage", "arguments": { "policyId": "POL-ISO-002", "coveredBy": "POL-SEC-001", "rationale": "Section 4 of the InfoSec policy defines access provisioning end to end." } }}Result
{ "policyId": "POL-ISO-002", "coveredBy": "POL-SEC-001", "coveredByType": "policy", "noop": false, "affectedControls": [ "soc2-cc6.1" ], "note": "Coverage link applied — the slot now inherits the source policy's approval and review clock."}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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| fileNamerequired | string, ≤200 | File name with extension. Allowed: .docx, .doc, .pdf. Path-traversal-safe (no /, \, .., null bytes). |
| policyIdoptional | string, 1–120 | Existing 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. |
| titleoptional | string, 1–200 | Custom-policy title. Creates a new CUSTOM-POL-* policy. Mutually exclusive with policyId. |
| descriptionoptional | string, 0–2000 | Custom policy: optional description. |
| categoryoptional | string, 0–80 | Custom policy: optional category. Defaults to 'Custom'. |
| frameworksoptional | string[] (≤10) | Custom policy: optional framework codes, e.g. ['soc2']. |
| controlIdsoptional | string[] (≤100) | Custom policy: optional control codes to map; each must exist. |
| basedOnVersionoptional | positive integer | Update 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. |
| forceoptional | boolean | Update mode: skip the stale-version check. Use only after surfacing the conflict; defaulting it loses human edits silently. Requires basedOnVersion. |
| commentsoptional | string, 0–2000 | Optional human-visible changelog comment. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "begin_policy_upload", "arguments": { "policyId": "POL-SEC-001", "fileName": "information-security-policy.docx" } }}Result
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| uploadIdrequired | string, 1–80 | The uploadId returned by begin_policy_upload. |
| forceoptional | boolean | Update 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
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_policy_upload", "arguments": { "uploadId": "<upload-id>" } }}Result
{ "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
ScopeBulk 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
| Field | Type | Description |
|---|---|---|
| policiesrequired | object[], 1–200 | Upload 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[]. |
| dryRunoptional | boolean | true → preview per-row mode + canonical validity with ZERO writes; no URLs minted. |
tools/call request
{ "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
{ "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
ScopeBulk 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
| Field | Type | Description |
|---|---|---|
| uploadIdsrequired | string[], 1–20, unique | The uploadIds returned by begin_policy_uploads (after the bytes were PUT). Max 20/call. |
| aiReasoningoptional | string, 1–2000 | Optional rationale recorded on the audit-log rows. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_policy_uploads", "arguments": { "uploadIds": [ "<upload-id-1>", "<upload-id-2>" ] } }}Result
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| titleoptional | string, 1–200 | Ad-hoc mode: human-readable document title. Mutually exclusive with documentId. |
| documentIdoptional | string, 1–120 | Catalog mode: canonical catalog slot id (DOC-*). Creates a draft version of that slot. Publish with publish_document. Mutually exclusive with title. |
| fileNameoptional | string, 1–200 | Catalog mode only: original file name (sanitized server-side). Defaults to "{documentId}.{ext}". |
| commentsoptional | string, 0–2000 | Catalog mode only: human-visible changelog comment. |
| descriptionoptional | string, 0–2000 | Ad-hoc mode only: optional description. |
| contentTyperequired | MIME enum | MIME 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. |
| controlIdsoptional | string[], cap 20 | Ad-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. |
| frameworksoptional | string[], cap 10 | Ad-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. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| uploadIdrequired | string, 1–80 | The uploadId returned by begin_document_upload. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_document_upload", "arguments": { "uploadId": "<upload-id>" } }}Result
{ "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
ScopeBulk 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
| Field | Type | Description |
|---|---|---|
| documentsrequired | object[], 1–200 | Upload 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[]. |
| dryRunoptional | boolean | true → preview the slot-match plan (mode + catalog-slot validity) with ZERO writes; no URLs minted. |
tools/call request
{ "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
{ "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
ScopeBulk 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
| Field | Type | Description |
|---|---|---|
| uploadIdsrequired | string[], 1–20, unique | The uploadIds returned by begin_document_uploads (after the bytes were PUT). Max 20/call. |
| aiReasoningoptional | string, 1–2000 | Optional rationale recorded on the audit-log rows. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_document_uploads", "arguments": { "uploadIds": [ "<upload-id-1>", "<upload-id-2>" ] } }}Result
{ "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
ScopePublishes 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
| Field | Type | Description |
|---|---|---|
| versionIdrequired | string, 1–120 | The catalog document version id from the finalize_document_upload response. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "publish_document", "arguments": { "versionId": "<version-uuid>" } }}Result
{ "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
ScopeAsks 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
| Field | Type | Description |
|---|---|---|
| messagerequired | string, 1–8000 | Your question. |
| conversationHistoryoptional | array (≤20 turns) | Prior {role:"user"|"assistant", content} turns for context. |
tools/call request
{ "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
{ "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
ScopeBulk-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
| Field | Type | Description |
|---|---|---|
| vendorsrequired | array, 1–200 rows | Vendor rows. Only `name` is required; optional fields include website, inherentRisk, category, securityOwner, businessOwner, businessPurpose, notes, dataAccess, compliance[], review dates, reviewFrequency, status. |
| dryRunoptional | boolean | true → full preview (created/updated/skipped) with ZERO writes. Run first, show the customer, then commit. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeBulk-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
| Field | Type | Description |
|---|---|---|
| risksrequired | array, 1–200 rows | Risk 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"). |
| dryRunoptional | boolean | true → full preview with ZERO writes. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeBulk-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
| Field | Type | Description |
|---|---|---|
| sourcerequired | 'vanta' | 'drata' | 'secureframe' | 'other' | The platform the export came from. |
| runsrequired | array, 1–100 rows | Run 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[]. |
| dryRunoptional | boolean | true → full preview with ZERO writes. |
| aiReasoningoptional | string, 1–2000 | Optional rationale on the audit-log row. |
tools/call request
{ "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
{ "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
ScopeList 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
| Field | Type | Description |
|---|---|---|
| statusoptional | 'active' | 'archived' | 'pending' | 'inactive' | Filter by vendor lifecycle status. Omit to list all. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_vendors", "arguments": {} }}Result
{ "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
ScopeList 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
| Field | Type | Description |
|---|---|---|
| statusoptional | 'identified' | 'mitigating' | 'accepted' | 'closed' | Filter by risk status. Omit to list all. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_risks", "arguments": { "status": "mitigating" } }}Result
{ "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
ScopeRun 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
| Field | Type | Description |
|---|---|---|
| vendorIdrequired | string | The managed vendor to assess (resolve with list_vendors). |
| dryRunoptional | boolean | true → compute and return the band with ZERO writes. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "run_vendor_risk_assessment", "arguments": { "vendorId": "vendor-imp-<sha16>" } }}Result
{ "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
ScopeRun 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
| Field | Type | Description |
|---|---|---|
| vendorIdsrequired | array, 1–25 | Managed vendor ids to assess (resolve with list_vendors). Duplicates are assessed once. |
| dryRunoptional | boolean | true → compute every band with ZERO writes. |
tools/call request
{ "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
{ "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
ScopeRecord 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
| Field | Type | Description |
|---|---|---|
| vendorIdrequired | string | The managed vendor being reviewed (resolve with list_vendors). |
| outcomerequired | 'passed' | 'passed_with_exceptions' | 'failed' | The review verdict. |
| summaryOfFindingsrequired | string, 10–2000 | What was checked and found (the CC9.2 evidence of record). |
| reviewedDocIdsoptional | array, ≤50 | Ids of this vendor's existing documents the reviewer attests to having reviewed. Ids not belonging to this vendor are dropped. |
| revieweroptional | string | Display-only name of the human reviewer, if different from the caller. Does not change the audit actor. |
tools/call request
{ "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
{ "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
ScopeUpload 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
| Field | Type | Description |
|---|---|---|
| vendorIdrequired | string, 1–200 | The managed vendor to attach the document to (resolve with list_vendors). |
| docTyperequired | docType enum | One 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. |
| namerequired | string, 1–200 | Human-readable document name, e.g. 'Example CRM SOC 2 Type II 2026'. |
| contentTyperequired | MIME enum | MIME type of the file (pdf, docx, doc, xlsx, xls, pptx, png, jpeg, gif, webp, plain, csv, json, zip). Other types are rejected. |
| contentBase64required | string (base64) | Base64-encoded file content. Max 4 MB after decode (~5.3 MB base64). |
| expiresAtoptional | string, 1–40 | Optional expiry date (ISO 8601), e.g. the SOC 2 report period end or the attestation expiry. |
| notesoptional | string, 0–2000 | Optional free-text notes. |
tools/call request
{ "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
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| vendorIdrequired | string, 1–200 | The managed vendor to attach the document to (resolve with list_vendors). Validated at begin and re-validated at finalize. |
| docTyperequired | docType enum | One 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. |
| namerequired | string, 1–200 | Human-readable document name, e.g. 'Example CRM SOC 2 Type II 2026'. |
| contentTyperequired | MIME enum | MIME 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). |
| expiresAtoptional | string, 1–40 | Optional expiry date (ISO 8601), e.g. the SOC 2 report period end or the attestation expiry. |
| notesoptional | string, 0–2000 | Optional free-text notes. |
tools/call request
{ "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
{ "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
ScopeStep 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
| Field | Type | Description |
|---|---|---|
| uploadIdrequired | string, 1–80 | The uploadId returned by begin_vendor_document_upload. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "finalize_vendor_document_upload", "arguments": { "uploadId": "<upload-id>" } }}Result
{ "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
ScopeThe 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
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_integrations", "arguments": {} }}Result
{ "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
ScopeReturns 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
| Field | Type | Description |
|---|---|---|
| integrationTyperequired | string | Which integration to connect.gwsawsslack |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "connect_integration", "arguments": { "integrationType": "gws" } }}Result
{ "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
ScopePolls a pending connection. Returns connected (with integrationId), pending, timeout, or not_found.
Ask your AI client: “Did the Google Workspace connection finish?”
Input
| Field | Type | Description |
|---|---|---|
| connectionIdrequired | string, 1–120 | The connectionId from connect_integration. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_connection_status", "arguments": { "connectionId": "<conn-uuid>" } }}Result
{ "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
ScopeList 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id from list_audits. |
| statusoptional | string | Request status filter.requestedsubmittedacceptedrejectednarequest-moreallDefault: all |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_audit_requests", "arguments": { "auditId": "<audit-uuid>", "status": "requested" } }}Result
{ "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
ScopeOne 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id. |
| requestIdrequired | string, 1–200 | Request id from list_audit_requests. |
tools/call request
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_audit_request", "arguments": { "auditId": "<audit-uuid>", "requestId": "<request-uuid>" } }}Result
{ "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
ScopeShort-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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id. |
| requestIdrequired | string, 1–200 | Request id. |
| attachmentIdrequired | string, 1–120 | Attachment id from get_audit_request.auditorAttachments. |
tools/call request
{ "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
{ "downloadUrl": "https://s3.amazonaws.com/...signed...", "fileName": "sample-access-review.xlsx", "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "expiresInSeconds": 3600}begin_audit_response_upload
ScopeBegin 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id. |
| requestIdrequired | string, 1–200 | Request id. |
| fileNamerequired | string, 1–255 | Original filename. |
| fileSizerequired | number (≤ 50MB) | File size in bytes. |
| contentTyperequired | string | MIME type (PDF/Office/CSV/PNG/JPEG/ZIP/text). |
tools/call request
{ "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
{ "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
ScopePrepare 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id. |
| requestIdrequired | string, 1–200 | Request id. |
| messageoptional | string, 1–4000 | Optional note to the auditor. |
| evidenceRefIdsoptional | string[] (≤ 50) | refIds from get_audit_request.attachableEvidenceRefs. |
| uploadIdoptional | string, 1–120 | An uploadId from begin_audit_response_upload (after the PUT). |
| idempotencyKeyoptional | string, 1–120 | Usually omit — idempotency is content-derived. |
tools/call request
{ "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
{ "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
ScopeSEND 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
| Field | Type | Description |
|---|---|---|
| auditIdrequired | string, 1–120 | Audit id. |
| requestIdrequired | string, 1–200 | Request id. |
| responseIdrequired | string, 1–120 | The draft responseId from prepare_audit_request_response. |
tools/call request
{ "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
{ "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.
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "test result 'abc-123' not found" }], "_meta": { "strac.io/errorCode": "not_found" }, "isError": true }}| errorCode | Recover by |
|---|---|
| not_found | Try a different id; check the corresponding list_* first. |
| invalid_argument | Args passed Zod but failed semantic validation. Consult the tool input schema. |
| precondition_failed | Resource exists but is in a state that blocks the action. Mutate state first. |
| unauthorized | Bearer scope is fine but a tenant-internal role gate rejected. Use a higher-role bearer. |
| unknown | Handler signaled a client-facing error without a category. Inspect the message. |
| internal | Server-side failure. Retry; if persistent, contact support. The message is generic by design. |
Tenant-isolation invariants
Safe to build against
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
- Authentication — OAuth 2.1 / PKCE flow, discovery handshake
- REST API — script the same surface with a PAT
- MCP server landing — connect snippets for every client