Treeship
SDK

@treeship/mcp

Drop-in MCP client replacement. Every tool call receipted automatically.

Install

npm install @treeship/mcp

Usage

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:

  1. Intent (before the call, awaited) -- proves what was about to happen. The tool name and a SHA-256 digest of the arguments are recorded.
  2. 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 -- unless TREESHIP_STRICT=1 is set, in which case a signing failure throws instead. This fail-open default is separate from the halt check below, which can refuse a call outright regardless of TREESHIP_STRICT.

The kill switch (halt)

Before every callTool(), the client checks treeship halt list for the calling actor (or *). A standing halt refuses the call before it reaches the wrapped MCP client, signs the refusal as blocked.v1, and throws TreeshipHaltedError -- this one does break the call, always, by design. If the halt check itself cannot run (no CLI on PATH, or a CLI too old to have halt), the client fails open by default (prints a one-time warning to stderr and proceeds) or, under TREESHIP_STRICT=1, refuses instead.

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 failed

If 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

VariableEffect
TREESHIP_DISABLE=1Disable all attestation. Pure passthrough to the upstream MCP client.
TREESHIP_ACTOROverride the default actor URI (default: agent://mcp-{clientName})
TREESHIP_APPROVAL_NONCEAttach an approval nonce to intent attestations
TREESHIP_STRICT=1Fail closed instead of open: a halt check that can't run refuses the call, and a signing failure throws instead of letting the tool call proceed unattested

There is no TREESHIP_DEBUG; nothing in the bridge or SDK reads it.

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-agent

Override 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=abc123

This is useful when a human reviews and approves a planned action before the agent executes it.

The treeship-mcp server

Everything above is the wrapped client -- for attesting calls your agent makes to other MCP servers. @treeship/mcp also ships an MCP server (bridges/mcp/src/server.ts) exposing Treeship's own operations as tools, so any MCP-speaking client can attest, verify, and hand off as Treeship. It registers 9 tools:

ToolWhat it does
treeship_session_statusCurrent session state
treeship_session_eventAppend a structured event to the session timeline
treeship_attest_actionSign an action attestation
treeship_verifyVerify an artifact or chain
treeship_session_reportPublish the session report
treeship_mint_challengeMint a liveness-check challenge nonce
treeship_presentPresent this agent's proof (respond to a challenge)
treeship_verify_presentationVerify another agent's presentation
treeship_attest_handoffRecord a handoff, and how custody was established

This is the same tool set the Claude Code plugin bundles for treeship_mint_challenge/treeship_present/treeship_verify_presentation/treeship_attest_handoff-style A2A verification from inside a session.