@treeship/mcp
Drop-in MCP client replacement. Every tool call receipted automatically.
Install
npm install @treeship/mcpUsage
One import change. Zero other code changes.
// Before (no attestation):
import { Client } from '@modelcontextprotocol/sdk';
// After (every tool call attested):
import { Client } from '@treeship/mcp';Everything else stays the same. The wrapped Client class extends the original MCP Client and re-exports all other symbols unchanged.
import { Client } from '@treeship/mcp';
const client = new Client(
{ name: 'my-agent', version: '1.0' },
{ capabilities: {} },
);
await client.connect(transport);
const result = await client.callTool({
name: 'search',
arguments: { q: 'treeship' },
});
// Result now carries attestation metadata:
// result._treeship = { intent: "art_xxx", tool: "search", actor: "agent://mcp-my-agent" }What gets attested
For every callTool() invocation, the bridge produces two attestations:
- Intent (before the call, awaited) -- proves what was about to happen. The tool name and a SHA-256 digest of the arguments are recorded.
- Receipt (after the call, fire-and-forget) -- proves what came back. Records elapsed time, exit status, and a digest of the output. The receipt is never awaited, so it never blocks the response.
Arguments and results are never stored directly. Only their SHA-256 digests enter the artifact. The digest proves which data was involved without exposing the data itself.
If attestation fails for any reason (CLI not installed, network issue, signing error), the tool call still completes normally. Treeship never breaks your MCP workflow.
Result metadata
When attestation succeeds, the result object includes a _treeship field with the full ToolReceipt shape:
interface ToolReceipt {
intent?: string; // artifact ID of the intent attestation
receipt?: string; // artifact ID of the receipt (undefined until async attestation completes)
receiptReady?: Promise<string | undefined>; // resolves when receipt attestation finishes
tool: string; // tool name, e.g. "search"
actor: string; // actor URI used for both attestations
}Async receipt contract
The receipt field starts as undefined because receipt attestation happens off the critical path (fire-and-forget). This means the tool call returns immediately without waiting for the receipt to be signed and stored.
If you need the receipt artifact ID -- for example, to chain it as a parent in a subsequent attestation -- await the receiptReady promise:
const result = await client.callTool({ name: "search", arguments: {} });
// receipt is undefined right after the call returns
console.log(result._treeship?.receipt); // undefined
// await receiptReady to get the receipt artifact ID
const receiptId = await result._treeship?.receiptReady;
console.log(receiptId); // "art_xyz..." or undefined if attestation failedIf receipt attestation fails (CLI not installed, signing error, network issue), receiptReady resolves to undefined rather than rejecting. This keeps the error-handling contract simple: the promise never throws.
Environment variables
| Variable | Effect |
|---|---|
TREESHIP_DISABLE=1 | Disable all attestation. Pure passthrough to the upstream MCP client. |
TREESHIP_ACTOR | Override the default actor URI (default: agent://mcp-{clientName}) |
TREESHIP_APPROVAL_NONCE | Attach an approval nonce to intent attestations |
TREESHIP_DEBUG=1 | Log all attestation activity to stderr |
Actor naming
By default, the actor URI is derived from the name field in your Implementation object:
new Client({ name: 'my-agent', version: '1.0' }, { capabilities: {} });
// Default actor: agent://mcp-my-agentOverride it with the TREESHIP_ACTOR environment variable when you need a specific identity.
Approval flow
To attest that a human approved a tool call before it happened, set the TREESHIP_APPROVAL_NONCE environment variable. The nonce is included in the intent attestation and links back to the approval artifact.
export TREESHIP_APPROVAL_NONCE=abc123This is useful when a human reviews and approves a planned action before the agent executes it.