mcpose
](https://www.npmjs.com/package/mcpose)    
MCP服务器的透明中间件代理——通过可组合的功能中间件拦截、转换和管理工具调用和工具发现。
如果你喜欢使用中间件功能或LEGO,你可能会喜欢它。
1.2.0中的新功能
onRequest钩子——HTTP代理上的身份验证/请求门控onErrorcallback--自定义错误处理程序(替换console.error)maxBodyBytes--机身大小上限,返回413(默认4 MB)maxSessions--并发会话上限,超额回报503sessionTtlMs--具有自动关闭功能的会话TTLcreateProxyContext导出用于手动构建上下文
______________________________________________________________________
______________________________________________________________________
背景
mcpose提取自 financial-elastic-mcp-server,为需要在每次工具调用时进行PII编辑和审计日志记录的金融机构构建的Elasticsearch MCP服务器。这些跨领域的问题最初被硬编码到单个服务器中。mcpose将该模式提升到一个可重用、可组合的中间件层中,该层可以封装 任何 上游MCP服务器。
______________________________________________________________________
概念
mcpose是一个 透明代理 在LLM客户端和上游MCP服务器之间。它反映了上游MCP表面,并通过中间件路由支持的呼叫。客户端看到一个正常的MCP服务器;上游看到正常的MCP客户端。
______________________________________________________________________
安装
npm install mcpose对等依赖 --必须单独安装:
npm install @modelcontextprotocol/sdk@>=1.0.0______________________________________________________________________
快速开始
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
const loggingMW: 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, {
toolMiddleware: [loggingMW],
});______________________________________________________________________
代理模型
┌──────────────┐ ┌────────────────────────────────┐ ┌────────────────────┐
│ LLM client │ ◄────► │ mcpose │ ◄────► │ Upstream MCP │
│ (Claude, │ │ · visibility filters │ │ server │
│ Cursor…) │ │ · middleware pipelines │ │ (stdio or HTTP) │
└──────────────┘ └────────────────────────────────┘ └────────────────────┘对于每个支持的工具或资源,mcpose会选择三条路由路径之一:
| 路径 | 选项 | 行为 |
|---|---|---|
| 隐藏 | hiddenTools / hiddenResources | 从列表回复中省略;呼叫时出现错误而被拒绝 |
| 通过 | passThroughTools / passThroughResources | 直接转发到上游--跳过所有中间件 |
| 中间件 | 其他一切 | 已全部安排 toolMiddleware / resourceMiddleware 管道 |
当上游支持提示时,提示会按原样转发。
代理端到端保留了核心请求语义:
- 广告功能从上游服务器镜像
- 中止信号被转发给上游工具、资源和提示调用
- 上游进度更新被中继回下游客户端
- 当上游支持列表更改通知时,这些通知会被公告并展开
list_tools反应可以通过以下方式转变listToolsMiddleware不削弱当地hiddenTools保证
______________________________________________________________________
中间件模型
中间件遵循 洋葱模型:外层在之前运行代码 *和* 内层之后。每个中间件接收请求 next 调用管道其余部分的函数,以及一个规范化的 ProxyContext.
request ──►
┌──────────────────────────────────────────┐
│ outerMW (enter) │
│ ┌────────────────────────────────────┐ │
│ │ innerMW (enter) │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ upstream call │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ innerMW (exit) ◄── response │ │
│ └────────────────────────────────────┘ │
│ outerMW (exit) ◄── response │
└──────────────────────────────────────────┘
◄── response数组顺序 ProxyOptions 用途 响应处理顺序:第一个元素处理响应 *第一* (最内层)。 ProxyOptions 电话 pipe() 内部——无需手动包装。为确保审计永远不会看到原始PII:
toolMiddleware: [piiMW, auditMW]
// Execution:
// 1. auditMW enter → capture startTime (outermost)
// 2. piiMW enter → transform request
// 3. upstream call
// 4. piiMW exit → redact PII from response (processes response first)
// 5. auditMW exit → log already-clean data (processes response last)compose([outerMW, innerMW]) 使用 相反的 (最外层优先)惯例-- ProxyOptions 数组是 不 可互换 compose() 论据。
中间件可以 短路 不打电话就回来 next,或 处理上游错误 通过包装 await next(req) 尝试/抓住。现有的双参数中间件保持不变; mcpose 补给 ProxyContext 作为可选的第三个论点。
______________________________________________________________________
API 参考
ProxyContext · Middleware · ToolMiddleware · ResourceMiddleware · ListToolsMiddleware · compose() · createProxyContext()
interface ProxyContext {
requestId: string;
transport: 'stdio' | 'http';
sessionId?: string;
headers?: Readonly>;
signal?: AbortSignal;
}
// Builds a ProxyContext with a fresh requestId; useful in tests or custom orchestration:
function createProxyContext(overrides?: Partial
): ProxyContext;
type Middleware = (
req: Req,
next: (req: Req) => Promise,
context: ProxyContext,
) => Promise;
// Convenience aliases for the three pipeline types:
type ToolMiddleware = Middleware;
type ResourceMiddleware = Middleware;
type ListToolsMiddleware = Middleware
;
function compose(
middlewares: ReadonlyArray>,
): {
(req: Req, next: (req: Req) => Promise): Promise;
(req: Req, next: (req: Req) => Promise, context: ProxyContext): Promise;
};
// Type guard — narrows CompatibilityCallToolResult to CallToolResult
// (safe access to .content and .isError without casts):
function hasToolContent(r: CompatibilityCallToolResult): r is CallToolResult;compose 接受一个数组 最外层优先 订单。使用 hasToolContent 在访问之前的中间件实现中 .content 或 .isError,因为 CompatibilityCallToolResult 还包括遗产 { toolResult } 形状。 ProxyContext.signal 当传输提供下行中止信号时,携带下行中止信号。
______________________________________________________________________
BackendConfig · createBackendClient()
interface BackendConfig {
command?: string; // Executable to spawn for stdio transport (e.g., "node")
args?: string[]; // Arguments for the spawned process
url?: string; // HTTP endpoint of a running MCP server (takes precedence over stdio)
}
async function createBackendClient(config: BackendConfig): Promise;BackendClient 是SDK的别名 Client如果两者都没有,它就会抛出 command 也不 url 或者如果连接失败。
______________________________________________________________________
ProxyOptions · startProxy() · createProxyServer()
interface ProxyOptions {
toolMiddleware?: ReadonlyArray;
resourceMiddleware?: ReadonlyArray;
listToolsMiddleware?: ReadonlyArray
;
passThroughTools?: ReadonlyArray;
passThroughResources?: ReadonlyArray;
hiddenTools?: ReadonlyArray;
hiddenResources?: ReadonlyArray;
}
async function startProxy(backend: BackendClient, options?: ProxyOptions): Promise;
function createProxyServer(backend: BackendClient, options?: ProxyOptions): Server;| 选项 | 描述 |
|---|---|
toolMiddleware | 用于工具调用的中间件堆栈,按响应处理顺序排列(第一个元素先处理响应)。 |
resourceMiddleware | 按响应处理顺序读取资源的中间件堆栈。 |
listToolsMiddleware | 中间件堆栈 list_tools,按照响应处理顺序。本地 hiddenTools 过滤仍然在此管道之前和之后运行。 |
passThroughTools | 工具名称直接转发到上游——完全跳过了中间件。 |
passThroughResources | 资源URI直接转发到上游——完全跳过了中间件。 |
hiddenTools | 工具名称已从中删除 list_tools 和 在通话时被拒绝 MethodNotFound. |
hiddenResources | 已从中删除资源URI list_resources 和 在通话时被拒绝 InvalidRequest. |
createProxyServer 仅反映了所暴露的上游能力 backend.getServerCapabilities()。不支持的提示、资源和工具终结点不会被通告或注册。
startProxy 将代理连接到 StdioServerTransport. createProxyServer 返回已配置的 Server 无需连接,这对于在没有实时传输的情况下测试请求处理程序非常有用。
______________________________________________________________________
HttpProxyOptions · startHttpProxy()
interface HttpProxyOptions {
port?: number; // Default: 3000
host?: string; // Default: all interfaces
path?: string; // Default: '/mcp'
onRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean | Promise;
onError?: (err: unknown) => void;
maxBodyBytes?: number; // Default: 4 MB — returns 413 on excess
maxSessions?: number; // Excess requests return 503
sessionTtlMs?: number; // Sessions auto-close after this duration
}
function startHttpProxy(
backend: BackendClient,
options?: ProxyOptions,
httpOptions?: HttpProxyOptions,
): Promise;使用有状态会话通过Streamable HTTP启动代理。为每个客户端连接分配一个 mcp-session-id上游列表更改通知(tools/list_changed, resources/list_changed, prompts/list_changed)当上游发布广告时,这些会话被分散到所有活动会话。
import { createBackendClient, startHttpProxy } from 'mcpose';
const backend = await createBackendClient({ url: 'http://upstream-mcp-server/mcp' });
const server = await startHttpProxy(backend, { toolMiddleware: [loggingMW] }, { port: 8080 });
// HTTP server is now listening on port 8080 at /mcp关闭时,活动代理会话在底层服务器之前关闭 http.Server 结束关闭。
限制:
- 不支持SSE重新连接重播(否
EventStore).
______________________________________________________________________
mcpose/testing
import { createMockBackendClient, runToolMiddleware } from 'mcpose/testing';createMockBackendClient() 返回一个带有功能查找和通知挂钩的内存后端存根。它对两者都有效 createProxyServer() 和 startHttpProxy() 测验。
______________________________________________________________________
配方:list_tools重写
使用 listToolsMiddleware 当您想在不更改本地路由保证的情况下重写可见工具目录时:
import type { ListToolsMiddleware } from 'mcpose';
const enrichDescriptions: ListToolsMiddleware = async (req, next, context) => {
const result = await next(req);
return {
...result,
tools: result.tools.map((tool) =>
tool.name === 'wire_transfer'
? {
...tool,
description: `${tool.description ?? 'Wire transfer'} (approval required on ${context.transport})`,
}
: tool,
),
};
};hiddenTools 即使有 listToolsMiddleware 尝试将隐藏的工具添加回响应中。
______________________________________________________________________
配方:PII编辑
mcpose的原始用例:一个金融级MCP服务器,其中每个Elasticsearch工具响应在到达LLM或审计日志之前都必须清除PII。
使用工厂来保持中间件的可配置性和可测试性:
import { hasToolContent } from 'mcpose';
import type { ToolMiddleware } from 'mcpose';
function createPiiMiddleware(patterns: RegExp[]): ToolMiddleware {
return async (req, next) => {
const result = await next(req);
if (!hasToolContent(result)) return result;
return {
...result,
content: result.content.map((item) =>
item.type === 'text'
? { ...item, text: redactPii(item.text, patterns) }
: item,
),
};
};
}
function redactPii(text: string, patterns: RegExp[]): string {
return patterns.reduce((t, re) => t.replace(re, '[REDACTED]'), text);
}将其与审计中间件堆叠在一起——PII在数组中位于首位,因此审计始终可以看到干净的数据:
await startProxy(backend, {
toolMiddleware: [
createPiiMiddleware([/\b\d{9}\b/g, /[A-Z]{2}\d{6}/g]), // SSNs, account numbers
createAuditMiddleware({ destination: auditLog }),
],
});数组顺序保证:PII被编辑 *之前* 审计层永远不会看到响应。没有原始PII到达日志,满足金融监管要求。
参考实施: elastic-pii-proxy 是这种模式的一个生产示例——一个Elasticsearch MCP代理,它使用mcpose、PII编辑中间件和审计中间件将财务数据安全地提供给LLM代理。______________________________________________________________________
路线图
- \[x\] HTTP/SSE服务器传输 —
startHttpProxy()添加具有有状态会话的可流化HTTP服务器端传输 - \[ \] ATXP协议支持 --通过实施ATXP(代理交易协议)标准,让工具提供商将定价和计费元数据附加到响应中,实现MCP货币化
______________________________________________________________________
许可证
麻省理工学院
