Quick Start

Wrap an upstream MCP server with a logging middleware and serve it over stdio. Give your proxy a name and keep the upstream unchanged.

Prerequisites

  • Node.js 20 or newer.
  • @modelcontextprotocol/sdk ^1.17.0 as a peer dependency.
  • TypeScript is optional — mcpose ships ESM with first-class types.

Install

terminal
# core
npm install mcpose

# peer dependency — installed separately
npm install @modelcontextprotocol/sdk@"^1.17.0"

# compliance audit trails (optional)
npm install @mcpose/audit

Wrap a server

Connect to the upstream, define middleware, start the proxy. Middleware follows the onion model: each layer runs before and after the inner pipeline.

proxy.ts
import { createBackendClient, startProxy } from 'mcpose';
import type { ToolMiddleware } from 'mcpose';

// 1 · Connect to the upstream MCP server (stdio)
const backend = await createBackendClient({
  command: 'node',
  args: ['/path/to/backend-server.mjs'],
});

// 2 · Define middleware — before and after the upstream call
const logging: ToolMiddleware = async (req, next) => {
  console.error(`→ ${req.params.name}`);
  const result = await next(req);
  console.error(`← ${req.params.name} done`);
  return result;
};

// 3 · Start the proxy on stdio
await startProxy(backend, {
  name: 'my-proxy',
  toolMiddleware: [logging],
});
Note · Middleware order

Middleware arrays use response-processing order: the first element processes the response first. To keep raw PII out of audit logs, write toolMiddleware: [piiMW, auditMW] — the audit layer then only ever sees redacted data.

Run it

Save the example as proxy.ts and replace the backend path with your upstream server. Run npx tsx proxy.ts to launch it. The proxy is a normal MCP server. Point any client at it:

claude_desktop_config.json
{
  "mcpServers": {
    "governed-search": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/proxy.ts"]
    }
  }
}

Serving over HTTP instead? Swap startProxy for startHttpProxy — mTLS, session limits, per-session identity resolution, and SSE reconnect replay live there.

Routing paths

For each tool or resource, the proxy picks one of three paths:

PathOptionBehavior
HiddenhiddenToolsOmitted from lists; rejected with TOOL_HIDDEN at call time — still audited.
Pass-throughpassThroughToolsForwarded raw; transformers skipped, wrapped observers still run.
Middlewareeverything elseRouted through the full toolMiddleware pipeline.

Hidden beats pass-through: a tool listed in both stays hidden.

Next steps