Skip to main content

MCP Server

The Model Context Protocol is an open spec from Anthropic for exposing structured resources (read-only data the LLM can consult) and tools (callable verbs the LLM can invoke) to a host LLM at inference time. The Adjudon MCP server is a read-mostly bridge that lets a customer's LLM agent ask Adjudon "what policies are active right now?", "what does the recent trace history look like?", or "verify this hash-chain segment for me" without the agent having to wire an X-API-Key integration itself. Tested against API version v1. JWT auth on every endpoint; plan-gated by mcpServer (Scale+).

Protocol posture

The Adjudon server speaks MCP protocol version 0.1.0 and advertises resources: true, tools: true, prompts: false. Resources and tools are surfaced via three HTTP endpoints — the actual stdio / WebSocket transport that the MCP spec defines is not used. A customer's MCP-aware client (Claude Desktop, the official Python or TypeScript MCP SDK, a hand-rolled client) hits these endpoints over JSON-over-HTTPS like any other Adjudon API.

This is a deliberate v1 simplification — the stdio transport carries operational complexity that does not fit the "server-side compliance bridge" use case. If a customer needs true stdio transport, they wrap these endpoints client-side. The wrapper is a few dozen lines.

Endpoints

GET  /api/v1/mcp-server/manifest
POST /api/v1/mcp-server/resources/read
POST /api/v1/mcp-server/tools/call

Get the manifest

GET /api/v1/mcp-server/manifest

Returns the static manifest the customer's MCP client introspects during connection setup:

{
"success": true,
"data": {
"protocolVersion": "0.1.0",
"serverInfo": { "name": "adjudon-mcp", "version": "1.0.0" },
"capabilities": { "resources": true, "tools": true, "prompts": false },
"resources": [
{ "uri": "adjudon://policies", "name": "policies", "description": "Active enforcement policies for the org" },
{ "uri": "adjudon://traces/recent", "name": "recent-traces", "description": "Last 50 trace decisions (PII-scrubbed)" },
{ "uri": "adjudon://hash-chain/status","name": "hash-chain-status", "description": "Current verifiable audit-trail status" }
],
"tools": [
{ "name": "lookup_decision", "inputSchema": { "type": "object", "properties": { "traceId": { "type": "string" } }, "required": ["traceId"] } },
{ "name": "verify_chain_segment", "inputSchema": { "type": "object", "properties": { "fromSeq": { "type": "integer" }, "toSeq": { "type": "integer" } }, "required": ["fromSeq"] } }
]
}
}

The manifest is static across customers — every Adjudon org sees the same resource/tool catalogue. Per-org variation happens only when the resources are read; the catalogue itself does not branch.

Read a resource

POST /api/v1/mcp-server/resources/read

Body: { uri }. Three URIs are recognised:

URIWhat it returns
adjudon://policiesAll isActive: true policies for the caller's org
adjudon://traces/recentThe last 50 traces, projected to traceId, status, confidence, createdAt, rationale (PII-scrubbed)
adjudon://hash-chain/statusCurrent chain head, sequence, last-verified-at, verification status
curl
curl -X POST https://api.adjudon.com/api/v1/mcp-server/resources/read \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{ "uri": "adjudon://traces/recent" }'

Response shape follows the MCP spec:

{
"success": true,
"data": {
"contents": [
{ "uri": "adjudon://traces/recent", "mimeType": "application/json", "text": "[...]" }
]
}
}

The text field is a JSON-stringified array; clients parse it on the way in. Errors: 400 VALIDATION_ERROR (missing uri), 400 MCP_READ_FAILED (unknown URI; the error message is deliberately vague per Cardinal Rule #4 to avoid leaking trace- adjacent error context), 401, 403, 500.

Call a tool

POST /api/v1/mcp-server/tools/call

Body: { name, arguments }. Two tools are exposed:

ToolArgumentsWhat it does
lookup_decision{ traceId }Returns one trace by traceId, scoped to the caller's org
verify_chain_segment{ fromSeq, toSeq? }Verifies a hash-chain segment; calls into the Hash Chain verifier
curl
curl -X POST https://api.adjudon.com/api/v1/mcp-server/tools/call \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{ "name": "verify_chain_segment", "arguments": { "fromSeq": 1, "toSeq": 100 } }'

Response:

{
"success": true,
"data": {
"content": [{ "type": "text", "text": "{...}" }],
"isError": false
}
}

isError: true indicates the tool ran but returned a logical failure (e.g. lookup_decision for a traceId that does not exist, or verify_chain_segment returning verified: false). The HTTP status is still 200; consume isError for the verdict.

Errors: 400 VALIDATION_ERROR (missing name), 400 MCP_TOOL_FAILED (unknown tool, malformed arguments — vague per Cardinal Rule #4), 401, 403, 500.

Common gotchas

  • HTTP transport, not stdio. The Adjudon server intentionally speaks MCP over HTTPS rather than the spec's stdio / WebSocket transport. MCP clients that expect stdio must wrap these endpoints; the wrapper is small and one is shipped in the Python SDK as a reference example.
  • Read-only by design. No tool here mutates Adjudon state. An MCP-aware LLM cannot create policies, sign off auto-approval patterns, or post traces via this surface; mutations stay on their respective REST resources behind explicit API keys.
  • Vague error messages. MCP_READ_FAILED and MCP_TOOL_FAILED do not echo the underlying exception message. The service-layer error can carry trace-adjacent payload fragments (URIs, argument values, tool names); echoing them back risks Cardinal Rule #4 leaks. Use the error code to disambiguate.
  • PII-scrubbed reads. Resources surface trace data only after the standard piiScrubber pass; the recent-traces projection also drops large payload fields. This is the same guarantee every other read endpoint inherits.
  • Idempotency. The Idempotency-Key middleware is wired only on POST /traces; MCP tool calls are not auto-idempotent. lookup_decision and verify_chain_segment are functionally idempotent today (both are reads); future write-tools must declare their idempotency posture explicitly.

See also

  • Traces API — the source of truth recent-traces and lookup_decision read from
  • Hash Chain — the verify_chain_segment tool's authoritative endpoint
  • Policies API — the adjudon://policies resource's authoritative store
  • Plans & Features — the mcpServer feature gate
  • Error Codes — the broader error taxonomy