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.
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_idandaudit_id, and to the MCP request byrequest_idwhen the server provides one. Session windows and modes are those the SDK verifier checks under thepolicyyou pass (DEFAULT_VERIFIER_POLICYotherwise);verifyDcpReceiptadds 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. createReceiptIdentitysigns 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_agentreturnsstatus: "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_bundlerunsverifySignedBundleV2of@dcp-ai/sdkunderDEFAULT_VERIFIER_POLICY(hashes, manifest, composite signature, session binding, the SHA-256 audit chain). It does not validate the JSON Schemas (the reference verifierlib/verify.jsdoes) and@dcp-ai/sdk2.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 averifiedresult is not admission or authorization.- Apart from
dcp_verify_bundleand the receipts, the tool handlers return drafts: identifiers and fields to sign, never keys or signatures (next_stepsays what is missing). dcp_create_identityreturns 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+).