TailraceTailrace
Guides

Govern MCP tool calls

Wrap an MCP client transport so tools/call arguments and results respect Tailrace policy without tearing down the connection.

Use @tailrace/mcp to enforce data policy on MCP client transports - in-process, with no proxy.

When to use this

  • Agents call MCP servers that should not receive API keys or raw PII.
  • Inbound tool or resources/read results may contain secrets you must not feed back to the model.
  • You want a JSON-RPC error the client can handle, not a dead transport.

Prerequisites

  • Quickstart or equivalent createTailrace setup
  • @modelcontextprotocol/sdk >=1

Step 1: Wrap the transport

Fluent (recommended):

import { createTailrace } from "@tailrace/core";
import { withMcp } from "@tailrace/mcp";

const tailrace = withMcp(createTailrace());
const transport = tailrace.transport(sseTransport, { server: "salesforce" });
// Pass `transport` into the MCP Client.

Standalone:

import { wrapTransport } from "@tailrace/mcp";

const transport = wrapTransport(tailrace, sseTransport, { server: "salesforce" });

server becomes the policy boundary key prefix: mcp:salesforce/{tool}.

Step 2: Set agent and workflow ID

const transport = tailrace.transport(sseTransport, {
  server: "salesforce",
  agent: "support-bot",
  workflowId: sessionId,
});

Defaults are "default" when omitted.

What gets scanned

DirectionMethodBoundary
Outtools/call arguments{ kind: "mcp", server, tool: name, direction: "out" }
Intools/call resultsame with direction: "in"
Inresources/read result{ kind: "mcp", server, tool: "read", direction: "in" }

All other JSON-RPC messages pass through unchanged. For resources/read, tool is always the literal "read" so policy keys stay stable.

Verify it works

Outbound tools/call with a synthetic Stripe key (sk_test_…FAKE) should never reach the server. The client receives a JSON-RPC error:

{
  "jsonrpc": "2.0",
  "id": "<request-id>",
  "error": {
    "code": -32001,
    "message": "Blocked by data policy: api_key may not be sent to mcp (rule: …)",
    "data": { "type": "policy_violation", "entity": "api_key", "rule": "…" }
  }
}

Tokenize actions rewrite arguments / results in place and forward.

Troubleshooting

SymptomFix
Nothing is scannedConfirm you wrapped the transport the client actually uses
Policy key mismatchMatch server and tool name to mcp:{server}/{tool} in definePolicy
Transport closed on blockShould not happen - upgrade @tailrace/mcp; block synthesizes an error response

On this page