Skip to content

DCP-AI ↔ Model Context Protocol (MCP) Bridge

Signed DCP receipts for MCP tool calls, plus DCP tools and resources for MCP servers.

Installation

Published on npm (26/10/2026). @dcp-ai/anthropic-mcp 2.1.0 is latest and depends on @dcp-ai/sdk ^2.2.0. The features described below are available from npm.

Signed receipts (withDcpReceipt, createReceiptIdentity, verifyDcpReceipt) are included. The examples also require @modelcontextprotocol/sdk and zod; install those separately.

npm install @dcp-ai/anthropic-mcp@^2.1.0

See the npm release inventory for all package versions.

For development against a source checkout, build and install the local package instead:

git clone https://github.com/dcp-ai-protocol/dcp-ai.git
cd dcp-ai/integrations/anthropic-mcp
npm ci && npm run build
# then, in your project:
npm install /path/to/dcp-ai/integrations/anthropic-mcp

Signed receipts per tool call

withDcpReceipt wraps any MCP tool handler so that every call comes back with a receipt: a signed declaration, made with the server's keys, of which tool was called, under which intent and policy decision, and the hashes of what went in and out. It shows that the holder of those keys declared this; it does not prove by itself who holds them (see What verification proves) or that the tool did what its result says. A receipt is an ordinary DCP v2.0 signed bundle: the agent's Responsible Principal Record (DCP-01) and Agent Passport, an Intent (DCP-02) composite-signed with Ed25519 + ML-DSA-65 before the tool runs, the Policy Decision, and one audit entry (DCP-03) with the SHA-256 of the arguments and of the result. Nothing new on the wire: any DCP verifier checks it offline.

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { withDcpReceipt, createReceiptIdentity, verifyDcpReceipt } from '@dcp-ai/anthropic-mcp';

// One identity per session: new keys and session nonce, signed RPR and passport.
// Production deployments pass their own signed RPR and passport instead.
const identity = await createReceiptIdentity({ legalName: 'Example Operator', jurisdiction: 'CL' });

const server = new McpServer({ name: 'weather', version: '1.0.0' });
server.registerTool(
  'get_weather',
  { description: 'Weather for a city', inputSchema: { city: z.string() } },
  withDcpReceipt('get_weather', async ({ city }) => ({ content: [{ type: 'text', text: `Sunny in ${city}` }] }), { identity }),
);

Every result now carries _meta.dcp_receipt. The caller, or anyone later, verifies it and checks that it is the receipt of this call:

// Fragment: `client` is an MCP client connected to the server above
const result = await client.callTool({ name: 'get_weather', arguments: { city: 'Santiago' } });
const { _meta, ...seen } = result;
const check = await verifyDcpReceipt(_meta.dcp_receipt, { tool: 'get_weather', arguments: { city: 'Santiago' }, result: seen });
check.verified; // false if the bundle, the tool name or the hash of { arguments, result } does not match

What happens on each call:

Policy decision Tool runs Returned Audit entry
approve yes the handler's result + _meta.dcp_receipt outcome: "completed", result_ref = hash of the result
escalate no { content, isError: true, _meta } outcome: "escalated", policy_decision: "escalated", result_ref: null
block no { content, isError: true, _meta } outcome: "blocked", policy_decision: "blocked", result_ref: null
handler throws yes { content, isError: true, _meta } outcome: "failed", result_ref = hash of that error result

A refusal is evidence too: the receipt of a blocked call shows the intent the agent declared and the decision that stopped it.

Classification and policy

By default every call is a low-impact api_call on channel api with data_classes: ["none"], which the default policy approves (risk 200). Tell the wrapper what a tool really does, and decide yourself when the defaults do not fit:

// Fragment: payInvoice is your tool handler, identity as above
withDcpReceipt('pay_invoice', payInvoice, {
  identity,
  classify: (tool, args) => ({
    action_type: 'initiate_payment',
    channel: 'payments',
    estimated_impact: 'high',
    data_classes: ['financial_data'],
  }),
  // Default policy: risk = impact (low 200 / medium 500 / high 900) + 150 per sensitive data class, capped at 1000;
  // approve < 500, escalate < 800, block >= 800. Replace it with your own:
  decide: (intent) => ({ decision: 'escalate', risk_score: 900, reasons: ['payments need a human'] }),
  appliedPolicyHash: 'sha256:…', // hash of the policy you applied (default: hash of the default rules)
});

The 'sha256:…' above is a placeholder: pass the real sha256: and 64 hex characters of your policy, or leave appliedPolicyHash out.

The intent's security_tier (and the decision's resolved_tier) is the SDK's tier for the intent (DCP-AI v2.0 §14.2), never below what the risk score implies (200 standard, 500 elevated, 800 maximum), so the default call is standard. appliedPolicyHash must be sha256:<64 hex> and createReceiptIdentity only accepts the passport schema's capabilities (browse, api_call, email, calendar, payments, crm, file_write, code_exec); both are refused up front because the reference verifier validates the schemas.

Evidence hashes

evidence.evidence_hash is sha256: over the JSON text of { arguments, result } with keys sorted at every depth (stableJson), and evidence.result_ref the same over the result alone when the tool ran. arguments are the arguments as received, copied before the handler runs, so a handler that rewrites its input cannot change what the receipt attests. result is exactly what the wrapper returns minus _meta (resultSeen(result)): the receipt travels in _meta, and a handler's own _meta is transport metadata, not evidence. A handler that returns a bare value gets it wrapped as { content: [{ type: "text", text }] }, and that wrapper is what is hashed. The arguments are snapshotted as JSON, so a Date is hashed as its ISO string, functions are dropped and an AbortSignal (as in the MCP SDK's extra) becomes {}. When the MCP extra carries a requestId, it is recorded as evidence.request_id, and verifyDcpReceipt(receipt, { requestId }) checks it.

Unlike the canonicalization profile, stableJson keeps any JSON number, so arguments and results with decimals hash fine. It sorts keys with JavaScript's sort(); an object whose keys are integer strings ({ "10": 1, "9": 2 }) is ordered the way V8 orders it, so recompute the hash in JavaScript or avoid such keys when another language must check it.

What verification proves, and what it does not

A receipt verifies with the keys its own passport and RPR list, like every DCP bundle: a verified result alone proves the receipt is self-consistent and untampered, not who those keys belong to. To make it an identity check, pass trustedKeys (the keys you already trust for the agent and its principal, from a registry or a pinned passport and RPR) together with expectedAgentId and expectedHumanId: every signature the SDK verifies in the receipt must then be made with one of those keys, resolved exactly as the SDK resolves it (first listed key with the signature's kid, in the key list of that artifact), active and unexpired in your list; the result says anchored: true only when that held. anchored is computed apart from the bundle verification, so it can be true while verified is false: accept a receipt only when both are true. A verifier that trusts a registry of passports can anchor every receipt an agent produces without re-reading the passport itself.

Limits to know about:

  • A receipt is bound to one call by its intent_id and audit_id, and to the MCP request by request_id when the server provides one. Session windows and modes are those the SDK verifier checks under the policy you pass (DEFAULT_VERIFIER_POLICY otherwise); verifyDcpReceipt adds no time window or deduplication of its own, so a verifier that must detect a replayed receipt keeps its own set of seen ids.
  • Each receipt is self-contained (one audit entry, prev_hash: "GENESIS"); batching a session's receipts into one bundle is on the roadmap.
  • createReceiptIdentity signs the RPR and the passport with the same key pair (the principal and the agent are one), which is right for tests and first runs only.

DCP tools and resources

The bridge also exposes DCP operations as MCP tools and resources, so an MCP-compatible model can:

  • Verify DCP bundles — run the SDK's V2 verifier (verifySignedBundleV2) on a signed bundle or receipt.
  • Create DCP identities — provision new Responsible Principal Records and Agent Passports.
  • Declare intents — submit intents for policy evaluation with automatic risk tier computation.
  • Check agent status — a placeholder: dcp_check_agent returns status: "check_pending" and fetches or verifies nothing.
  • Lifecycle management (DCP-05) — commission, report vitality, and decommission agents.
  • Succession (DCP-06) — create digital testaments with successor preferences.
  • Disputes (DCP-07) — file disputes between agents with evidence hashes.
  • Rights (DCP-08) — declare agent rights within a jurisdiction.
  • Delegation (DCP-09) — create human→agent delegation mandates.

Supported DCP Specifications

DCP-01 through DCP-09, partially: signed receipts and bundle verification, and drafts in the shapes of the other specifications. It does not implement them in full.

MCP Tools

Tool Description
dcp_verify_bundle Verify a signed DCP v2.0 bundle or receipt with the SDK's V2 verifier
dcp_create_identity Create a new DCP digital identity (RPR + Passport)
dcp_declare_intent Declare an intent for policy evaluation
dcp_check_agent Placeholder: returns check_pending without fetching or verifying anything
dcp_commission_agent Commission an agent (DCP-05)
dcp_report_vitality Report agent vitality metrics (DCP-05)
dcp_decommission_agent Decommission an agent (DCP-05)
dcp_create_testament Create a digital testament (DCP-06)
dcp_file_dispute File a dispute between agents (DCP-07)
dcp_declare_rights Declare agent rights (DCP-08)
dcp_create_mandate Create a delegation mandate (DCP-09)

MCP Resources

URI Description
dcp://protocol/version Current DCP protocol version and capabilities
dcp://protocol/algorithms Supported cryptographic algorithms
dcp://protocol/tiers Security tier definitions and requirements
dcp://protocol/lifecycle-states Agent lifecycle state definitions (DCP-05)

Usage

Registering with an MCP Server

DCP_MCP_TOOLS are tool descriptors (name, description, inputSchema as JSON Schema) and DCP_MCP_RESOURCES resource descriptors (uri, name, description, mimeType); the package does not provide the resources' contents. A sketch with the MCP TypeScript SDK's low-level Server, which takes JSON Schema tool descriptors as they are (not run here: it needs the MCP SDK):

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { DCP_MCP_TOOLS, DCP_MCP_RESOURCES, handleDcpToolCall } from '@dcp-ai/anthropic-mcp';

const server = new Server({ name: 'dcp', version: '1.0.0' }, { capabilities: { tools: {}, resources: {} } });

server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: DCP_MCP_TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const result = await handleDcpToolCall(request.params.name, request.params.arguments ?? {});
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
});
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: DCP_MCP_RESOURCES }));
// Serving a resource's contents (resources/read) is up to your server.

Tool Call Examples

import { handleDcpToolCall } from '@dcp-ai/anthropic-mcp';

// Fragment: myBundle is a signed bundle or receipt you hold
// Verify a bundle or a receipt
const result = await handleDcpToolCall('dcp_verify_bundle', {
  bundle_json: JSON.stringify(myBundle),
}); // { verified, errors, warnings, details, timestamp }

// Declare an intent
const intent = await handleDcpToolCall('dcp_declare_intent', {
  agent_id: 'agent:my-agent',
  action_type: 'api_call',
  description: 'Fetch weather data from external API',
  risk_score: 150,
});

Notes

  • dcp_verify_bundle runs verifySignedBundleV2 of @dcp-ai/sdk under DEFAULT_VERIFIER_POLICY (hashes, manifest, composite signature, session binding, the SHA-256 audit chain). It does not validate the JSON Schemas (the reference verifier lib/verify.js does) and @dcp-ai/sdk 2.2.0 does not check the secondary SHA3-256 fields of V2-11 (2.2.1 and later check those present, once your lockfile resolves it), and a verified result is not admission or authorization.
  • Apart from dcp_verify_bundle and the receipts, the tool handlers return drafts: identifiers and fields to sign, never keys or signatures (next_step says what is missing).
  • dcp_create_identity returns identifiers only — key generation and signing are separate steps.
  • Risk tier is computed automatically from the risk score: routine (<200), standard (200-499), elevated (500-799), maximum (800+).