mcp-middleware
v0.0.1
Published
Middleware for MCP servers. Intercept or transform requests; pass the rest through.
Maintainers
Readme
mcp-middleware
Middleware for MCP servers. mcp-middleware sits between your client and
a working MCP server so you can intercept the calls you care about —
everything else passes straight through.
Why
You've got a working MCP server you don't want to (or can't) modify, and you need to:
- tweak a request before it hits the server,
- rewrite or enrich a response on its way back,
- post-process a tool's output,
- short-circuit a specific call with your own logic,
- log, meter, or redact traffic.
mcp-middleware lets you do any of the above as a thin layer in front,
without forking or patching the underlying server.
Install
npm install mcp-middlewareUsage
import { mcpMiddleware } from 'mcp-middleware';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const { connect } = await mcpMiddleware({
makeTransport: () => new StdioClientTransport({ command: 'some-mcp-server' }),
requestHandler: async (req, extra, client) => {
const callTool = CallToolRequestSchema.safeParse(req);
if (callTool.success && callTool.data.params.name === 'greet') {
return { content: [{ type: 'text', text: 'intercepted' }] };
}
return client.request(req, z.any(), extra);
},
});
await connect();Recipe: modify a request or response
Mutate the request before it's forwarded, the response before it's returned, or both.
import { mcpMiddleware } from 'mcp-middleware';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const { connect } = await mcpMiddleware({
makeTransport: () => new StdioClientTransport({ command: 'some-mcp-server' }),
requestHandler: async (req, extra, client) => {
const call = CallToolRequestSchema.safeParse(req);
// Request side: force `read_only: true` on every tool call.
if (call.success) {
req.params.arguments = { ...req.params.arguments, read_only: true };
}
const res = await client.request(req, z.any(), extra);
// Response side: redact email addresses in any text content.
if (call.success && Array.isArray(res.content)) {
res.content = res.content.map((c) =>
c.type === 'text'
? { ...c, text: c.text.replace(/[\w.-]+@[\w.-]+/g, '[redacted]') }
: c,
);
}
return res;
},
});
await connect();Recipe: hide internal tools
Strip specific tools out of tools/list; everything else is forwarded
untouched.
import { mcpMiddleware } from 'mcp-middleware';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const HIDDEN = new Set(['internal-debug', 'dangerous-reset']);
const { connect } = await mcpMiddleware({
makeTransport: () => new StdioClientTransport({ command: 'some-mcp-server' }),
requestHandler: async (req, extra, client) => {
if (ListToolsRequestSchema.safeParse(req).success) {
const res = await client.request(req, z.any(), extra);
return { ...res, tools: res.tools.filter((t) => !HIDDEN.has(t.name)) };
}
return client.request(req, z.any(), extra);
},
});
await connect();