Model Context Protocol (TypeScript)
Wrap MCP clients and servers with Checkrd policy enforcement.
Model Context Protocol — TypeScript
The MCP TypeScript SDK has no app-level middleware chain. Checkrd ships two adapters that target the canonical interception points:
wrapMcpClient(client, options)— Proxy-wraps an MCP client socallTool,readResource, andgetPromptcalls are policy-evaluated before they reach the server.wrapMcpServer(server, options)— Proxy-wraps an MCP server's request-handler registration so every tool / resource / prompt request is policy-evaluated before the user's handler runs.
Install
bash
npm install checkrd @modelcontextprotocol/sdkClient side
typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { initAsync } from "checkrd";
import { wrapMcpClient } from "checkrd/integrations/mcp";
const checkrd = await initAsync({
policy: "policy.yaml",
agentId: "github-agent",
});
const transport = new StdioClientTransport({
/* ... */
});
const rawClient = new Client(
{ name: "github-agent", version: "1.0.0" },
{ capabilities: {} },
);
await rawClient.connect(transport);
const client = wrapMcpClient(rawClient, {
engine: checkrd.engine,
enforce: true,
agentId: "github-agent",
sink: checkrd.sink,
serverName: "github-mcp",
});
// Each call is policy-evaluated before reaching the server.
await client.callTool({ name: "create_issue", arguments: { title: "..." } });Server side
typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { wrapMcpServer } from "checkrd/integrations/mcp";
const rawServer = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
const server = wrapMcpServer(rawServer, {
engine: checkrd.engine,
enforce: true,
agentId: "my-server",
sink: checkrd.sink,
serverName: "my-server",
});
// Every handler the user registers is wrapped automatically.
server.setRequestHandler(callToolRequestSchema, async (request) => {
// your handler runs only if the policy allowed the call
});What gets enforced
| Method | Synthetic URL |
|---|---|
callTool({name, arguments}) | https://{serverName}/tools/{name} |
readResource({uri}) | https://{serverName}/resources?uri={uri} |
getPrompt({name, arguments}) | https://{serverName}/prompts/{name} |
listTools() etc. | https://{serverName}/tools (kind=list) |
yaml
agent: github-agent
default: deny
rules:
- name: allow-issue-create-and-list
allow:
url: "github-mcp/tools/create_issue|github-mcp/tools/list_issues"
- name: allow-listing
allow:
url: "github-mcp/tools|github-mcp/resources|github-mcp/prompts"Why MCP
As of April 2026 the MCP SDK sees ~97M monthly downloads across Python and TypeScript. The Linux Foundation hosts the spec under the Agentic AI Foundation. 30+ CVEs have been disclosed in the server ecosystem in its first year. An audited, policy-enforcing middleware is the missing piece nobody else has shipped at this layer.
Caveats
- Duck-typed. The adapters target the structural shape of
@modelcontextprotocol/sdkrather than importing concrete classes. MCP's surface is iterating quickly; this lets a minor SDK bump not force a Checkrd release. - List operations are evaluated as
kind=list. Default-allow policies should explicitly allow listing. - No app middleware chain. Tool-call gating is per-handler; transport-level concerns (auth headers, IP allowlist) belong in your HTTP framework's middleware.