关于 typescript-mcp
面向在 Cloudflare Workers 上构建 MCP 服务的 TypeScript 开发者技能,提供 SDK、Hono 与 Zod 的实现模式。内容涵盖 Streamable HTTP、认证、长时任务、采样工具及常见生产问题的预防。
主要功能
- 01Streamable HTTP 服务模式
- 02API Key 与 Zero Trust 认证
- 03长时任务生命周期
- 04带工具的采样请求
- 05Cloudflare 服务工具示例
- 06生产问题排查指南
适用场景
- 01在 Workers 上部署 MCP 服务
- 02为 MCP 端点添加认证
- 03实现可轮询的长时操作
- 04连接 D1、KV、R2 或 Vectorize
MCP 相关
指导使用 TypeScript MCP SDK 在 Cloudflare Workers 上构建 MCP 服务,并通过 Streamable HTTP 向客户端提供工具。也涵盖任务和采样等 MCP 能力。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill typescript-mcp安装后在 Claude Code 中输入/typescript-mcp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsoncommands/init.mdreferences/authentication-guide.mdcloudflare-agents-vs-standalone.mdcloudflare-integration.mdcommon-errors.mddeployment-guide.mdtesting-guide.mdtool-patterns.mdrules/typescript-mcp.mdscripts/init-mcp-server.shtest-mcp-connection.shtemplates/authenticated-server.tsbasic-mcp-server.tsfull-server.tsresource-server.tstasks-server.tstool-server.ts详细介绍
Cloudflare Workers 上的 TypeScript MCP
最后更新:2026-01-21 版本:@modelcontextprotocol/[email protected]、[email protected]、[email protected] 规范版本:2025-11-25
快速开始
npm install @modelcontextprotocol/sdk@latest hono zod
npm install -D @cloudflare/workers-types wrangler typescript
传输方式建议:生产环境请使用 StreamableHTTPServerTransport。SSE 传输方式已弃用,仅为向后兼容而维护。Streamable HTTP 提供更好的错误恢复能力、双向通信和更简单的部署方式。
基本 MCP 服务:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { Hono } from 'hono';
import { z } from 'zod';
const server = new McpServer({ name: 'my-mcp-server', version: '1.0.0' });
server.registerTool(
'echo',
{
description: 'Echoes back input',
inputSchema: z.object({ text: z.string() })
},
async ({ text }) => ({ content: [{ type: 'text', text }] })
);
const app = new Hono();
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
// CRITICAL: Set error handler to catch transport errors
transport.onerror = (error) => {
console.error('MCP transport error:', error);
};
// CRITICAL: Close transport to prevent memory leaks
c.res.raw.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(c.req.raw, c.res.raw, await c.req.json());
return c.body(null);
});
export default app; // CRITICAL: Direct export, not { fetch: app.fetch }
部署:wrangler deploy
身份验证
API 密钥(基于 KV):
app.use('/mcp', async (c, next) => {
const apiKey = c.req.header('Authorization')?.replace('Bearer ', '');
const isValid = await c.env.MCP_API_KEYS.get(`key:${apiKey}`);
if (!isValid) return c.json({ error: 'Unauthorized' }, 403);
await next();
});
Cloudflare Zero Trust:
const jwt = c.req.header('Cf-Access-Jwt-Assertion');
const payload = await verifyJWT(jwt, c.env.CF_ACCESS_TEAM_DOMAIN);
任务(v1.24.0+)
任务支持长时间运行的操作,会返回一个句柄,供之后轮询结果。适用于耗时的计算、批处理,或可能需要输入的操作。
任务状态:working → input_required → completed / failed / cancelled
服务端能力声明:
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
capabilities: {
tasks: {
list: {},
cancel: {},
requests: {
tools: { call: {} }
}
}
}
});
支持任务的工具:
server.registerTool(
'long-running-analysis',
{
description: 'Analyze large dataset',
inputSchema: z.object({ datasetId: z.string() }),
execution: { taskSupport: 'optional' } // 'forbidden' | 'optional' | 'required'
},
async ({ datasetId }, extra) => {
// If invoked as task, extra.task contains taskId
const result = await performAnalysis(datasetId);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
}
);
客户端任务请求:
{
"method": "tools/call",
"params": {
"name": "long-running-analysis",
"arguments": { "datasetId": "abc123" },
"task": { "ttl": 60000 }
}
}
任务生命周期:
- 客户端发送带有
task参数的请求 → 收到taskId - 客户端通过
tasks/get携带taskId轮询 - 状态为
completed时,客户端调用tasks/result获取输出 - 可选:客户端可以调用
tasks/cancel取消任务
📚 规范:https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks
带工具的采样(v1.24.0+)
服务现在可以在采样请求中包含工具定义,从而支持服务端 Agent 循环。
使用场景:服务需要使用大模型和工具来编排多步推理,而无需自定义框架。
// Server initiates sampling with tools available
const result = await server.requestSampling({
messages: [{ role: 'user', content: 'Analyze this data and fetch more if needed' }],
maxTokens: 4096,
tools: [
{
name: 'fetch_data',
description: 'Fetch additional data from API',
inputSchema: { type: 'object', properties: { query: { type: 'string' } } }
}
]
});
// Handle tool calls in response
if (result.content[0].type === 'tool_use') {
const toolResult = await executeLocalTool(result.content[0]);
// Continue conversation with tool result...
}
要点:
- 将服务端 Agent 行为作为 MCP 的一等功能
- 使用标准 MCP 基元(无需自定义框架)
- 工具定义遵循与
tools/list相同的架构
📚 规范:SEP-1577
Cloudflare 服务工具
D1 数据库:
server.registerTool('query-db', {
inputSchema: z.object({ query: z.string(), params: z.array(z.union([z.string(), z.number()])).optional() })
}, async ({ query, params }, env) => {
const result = await env.DB.prepare(query).bind(...(params || [])).all();
return { content: [{ type: 'text', text: JSON.stringify(result.results) }] };
});
KV、R2、Vectorize:参见 references/cloudflare-integration.md
已知问题预防
此技能可预防官方 MCP SDK 和 Cloudflare 仓库中记录的 20 个生产问题:
问题 #1:导出语法问题(严重)
错误:"Cannot read properties of undefined (reading 'map')"
来源:honojs/hono#3955、honojs/vite-plugins#237
原因:Vite 构建时导出格式不正确会导致难以诊断的错误
预防:
// ❌ WRONG - Causes cryptic build errors
export default { fetch: app.fetch };
// ✅ CORRECT - Direct export
export default app;
问题 #2:传输连接未关闭
错误:内存泄漏、连接挂起 来源:SDK 维护者的最佳实践 原因:请求结束时未关闭 StreamableHTTPServerTransport 预防:
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPServerTransport(/*...*/);
// CRITICAL: Always close on response end
c.res.raw.on('close', () => transport.close());
// ... handle request
});
问题 #3:工具架构验证失败
错误:ListTools request handler fails to generate inputSchema
来源:GitHub modelcontextprotocol/typescript-sdk#1028
原因:Zod 架构未正确转换为 JSON Schema
预防:
// ✅ CORRECT - SDK handles Zod schema conversion automatically
server.registerTool(
'tool-name',
{
inputSchema: z.object({ a: z.number() })
},
handler
);
// No need for manual zodToJsonSchema() unless custom validation
问题 #4:工具参数未传递给处理程序
错误:处理程序收到的参数为 undefined
来源:GitHub modelcontextprotocol/typescript-sdk#1026
原因:注册与调用之间的架构类型不匹配
预防:
const schema = z.object({ a: z.number(), b: z.number() });
type Input = z.infer<typeof schema>;
server.registerTool(
'add',
{ inputSchema: schema },
async (args: Input) => {
// args.a and args.b properly typed and passed
return { content: [{ type: 'text', text: String(args.a + args.b) }] };
}
);
问题 #5:CORS 配置错误
错误:浏览器客户端无法连接到 MCP 服务 来源:常见的生产问题 原因:HTTP 传输缺少 CORS 标头 预防:
import { cors } from 'hono/cors';
app.use('/mcp', cors({
origin: ['http://localhost:3000', 'https://your-app.com'],
allowMethods: ['POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization']
}));
问题 #6:缺少速率限制
错误:API 滥用、存在 DDoS 漏洞 来源:生产环境安全最佳实践 原因:MCP 端点没有速率限制 预防:
app.post('/mcp', async (c) => {
const ip = c.req.header('CF-Connecting-IP');
const rateLimitKey = `ratelimit:${ip}`;
const count = await c.env.CACHE.get(rateLimitKey);
if (count && parseInt(count) > 100) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.env.CACHE.put(
rateLimitKey,
String((parseInt(count || '0') + 1)),
{ expirationTtl: 60 }
);
// Continue...
});
问题 #7:TypeScript 编译内存问题
错误:运行 tsc 构建时出现 Out of memory
来源:GitHub modelcontextprotocol/typescript-sdk#985
原因:MCP SDK 的依赖树庞大
预防:
# Add to package.json scripts
"build": "NODE_OPTIONS='--max-old-space-size=4096' tsc && vite build"
问题 #8:UriTemplate ReDoS 漏洞
错误:恶意 URI 模式导致服务器挂起 来源:GitHub modelcontextprotocol/typescript-sdk#965(安全) 原因:URI 模板解析中的正则表达式拒绝服务 预防:更新到 SDK v1.20.2 或更高版本(已包含修复)
问题 #9:身份验证绕过
错误:未经身份验证即可访问 MCP 工具 来源:生产环境安全最佳实践 原因:缺少身份验证或身份验证实现不当 预防:生产环境的服务始终应实现身份验证(参见“身份验证模式”部分)
问题 #10:环境变量泄露
错误:机密信息在错误消息或日志中暴露 来源:Cloudflare Workers 安全最佳实践 原因:环境变量被记录到日志中或在响应中返回 预防:
// ❌ WRONG - Exposes secrets
console.log('Env:', JSON.stringify(env));
// ✅ CORRECT - Never log env objects
try {
// ... use env.SECRET_KEY
} catch (error) {
// Don't include env in error context
console.error('Operation failed:', error.message);
}
问题 #11:复用服务器实例会破坏并发 HTTP 会话(严重)
错误:AbortError: This operation was aborted
来源:GitHub Issue #1405
原因:调用 Server.connect(transport) 时会静默覆盖之前的传输,且不发出警告,从而破坏所有先前的连接
预防:
// ✅ CORRECT - Create fresh McpServer per HTTP session
app.post('/mcp', async (c) => {
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// Register tools per request
server.registerTool('echo', { inputSchema: z.object({ text: z.string() }) },
async ({ text }) => ({ content: [{ type: 'text', text }] })
);
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
transport.onerror = (error) => console.error('Transport error:', error);
c.res.raw.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(c.req.raw, c.res.raw, await c.req.json());
return c.body(null);
});
// ❌ WRONG - Reusing server instance across sessions
const sharedServer = new McpServer({ name: 'my-server', version: '1.0.0' });
app.post('/mcp', async (c) => {
await sharedServer.connect(transport); // Breaks previous sessions!
});
问题 #12:TypeScript 严格模式下的 sessionIdGenerator 类型错误
错误:Type 'undefined' is not assignable to type '() => string'
来源:GitHub Issue #1397
原因:SDK 1.25.2 的类型会破坏在 tsconfig.json 中使用 exactOptionalPropertyTypes: true 的项目
预防:
// With exactOptionalPropertyTypes: true
// ✅ CORRECT - Omit the property instead of setting to undefined
const transport = new StreamableHTTPServerTransport({
enableJsonResponse: true
// sessionIdGenerator omitted entirely
});
// ❌ WRONG - Setting to undefined causes type error in SDK 1.25.2
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // Type error!
enableJsonResponse: true
});
// Alternative: Provide a generator function
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
enableJsonResponse: true
});
问题 #13:Hono 污染全局 fetch(SDK 1.25.0-1.25.2)
错误:导入 SDK 后,原生 Node.js fetch 行为发生异常
来源:GitHub Issue #1376
原因:Hono 的服务器代码会全局覆盖 global.fetch,破坏依赖原生行为的库
预防:
// FIXED in SDK v1.25.3 - Update to latest version
npm install @modelcontextprotocol/[email protected]
// Workaround for older versions (1.25.0-1.25.2):
const nativeFetch = global.fetch;
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
global.fetch = nativeFetch; // Restore if needed
问题 #14:任务错误包装掩盖验证错误
错误:令人困惑的错误消息掩盖了实际的验证失败原因 来源:GitHub Issue #1385 原因:任务增强型工具调用在创建任务前验证失败时,SDK 会错误地包装错误 预防:
// Expected error for invalid input:
// "Invalid arguments: Too small: expected number to be >=500"
// Actual error (confusing):
// "Invalid task creation result: expected object, received undefined"
// WORKAROUND: Add explicit validation before task logic
server.experimental.tasks.registerToolTask(
'batch_process',
{
inputSchema: z.object({
itemCount: z.number().min(1).max(10),
processingTimeMs: z.number().min(500).max(5000).optional()
})
},
{
createTask: async (args, extra) => {
// SDK should fix this - currently no workaround
// Validation errors are masked by task wrapping
}
}
);
问题 #15:所有字段均为可选项的工具架构会导致 InvalidParams
错误:"expected": "object", "received": "undefined"
来源:GitHub Issue #400
原因:当架构中的所有属性均为可选项时,某些大模型客户端会省略 arguments 字段
预防:
// ❌ WRONG - All optional fields may cause issues
server.registerTool('fetch-records', {
inputSchema: z.object({
limit: z.number().optional()
})
}, handler);
// ✅ CORRECT - Always include at least one required field
server.registerTool('fetch-records', {
inputSchema: z.object({
action: z.literal('fetch').default('fetch'), // Required
limit: z.number().optional()
})
}, handler);
// Alternative: Use empty object schema
server.registerTool('fetch-records', {
inputSchema: z.object({}).passthrough()
}, handler);
问题 #16:批量注册工具会触发 EventEmitter 内存泄漏警告
错误:MaxListenersExceededWarning: Possible EventEmitter memory leak detected
来源:GitHub Issue #842
原因:在循环中注册 80 个以上的工具时,快速发出的 sendToolListChanged() 通知会使 stdout 缓冲区不堪重负
预防:
// Workaround: Increase maxListeners before bulk registration
process.stdout.setMaxListeners(100);
const tools = [...]; // Array of 80+ tool definitions
for (const tool of tools) {
server.registerTool(tool.name, tool.schema, tool.handler);
}
// Future SDK may provide batch registration API
问题 #17:未设置 onerror 处理程序时,传输错误会被静默处理
错误:传输错误消失,没有日志或异常
来源:GitHub Issue #1395
原因:如果未设置 onerror 回调,SDK 会静默吞掉传输错误
预防:
// ✅ CORRECT - Always set onerror handler
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
transport.onerror = (error) => {
console.error('Transport error:', error);
// Handle error appropriately
};
await server.connect(transport);
问题 #18:通过查询字符串绕过数组限制导致拒绝服务
错误:恶意查询参数导致内存耗尽
来源:GitHub Issue #1368
原因:可以使用 ?foo[99999999]=bar 这样的方括号表示法绕过 qs 库的 arrayLimit
预防:
// Validate query parameters to prevent DoS
app.post('/mcp', async (c) => {
const queryParams = c.req.query();
// Reject malicious patterns
if (Object.keys(queryParams).some(key => /\[\d{6,}\]/.test(key))) {
return c.json({ error: 'Invalid query parameters' }, 400);
}
// ... handle request
});
问题 #19:传输关闭时未取消请求处理程序
错误:客户端断开连接后,长时间运行的处理程序仍会继续执行,浪费资源 来源:GitHub Issue #611 原因:传输连接关闭时,SDK 不会自动取消请求处理程序 预防措施:
// Workaround: Use AbortController pattern manually
server.registerTool(
'long-running-task',
{ inputSchema: z.object({ duration: z.number() }) },
async ({ duration }, extra) => {
const abortController = new AbortController();
// Listen for transport close
const transport = extra.transport;
if (transport) {
const originalOnClose = transport.onclose;
transport.onclose = () => {
abortController.abort();
if (originalOnClose) originalOnClose();
};
}
try {
await longRunningTask(duration, abortController.signal);
return { content: [{ type: 'text', text: 'Done' }] };
} catch (error) {
if (error.name === 'AbortError') {
return { content: [{ type: 'text', text: 'Cancelled' }], isError: true };
}
throw error;
}
}
);
问题 #20:SDK 1.22.0-1.22.x 中的 $defs 架构引用失败
错误:can't resolve reference #/$defs/...
来源:GitHub Issue #1175
原因:SDK 1.22.0 中的回归问题导致 cacheToolOutputSchemas 无法处理复杂的 JSON Schema,进而使 listTools() 失败
预防措施:更新到 SDK v1.23.0 或更高版本(已修复)。如果使用 1.22.x,请立即升级。
部署
# Local
wrangler dev # http://localhost:8787/mcp
# Production
wrangler deploy
测试:npx @modelcontextprotocol/inspector(连接到 http://localhost:8787/mcp)
模板与参考资料
模板:basic-mcp-server.ts、tool-server.ts、resource-server.ts、authenticated-server.ts、tasks-server.ts、wrangler.jsonc
参考资料:tool-patterns.md、authentication-guide.md、testing-guide.md、cloudflare-integration.md、common-errors.md
关键规则
务必:
- ✅ 每个 HTTP 请求都创建新的
McpServer实例(切勿跨会话复用) - ✅ 设置
transport.onerror处理程序,以捕获静默错误 - ✅ 在响应结束时关闭传输(
c.res.raw.on('close', () => transport.close())) - ✅ 直接导出(使用
export default app,而非{ fetch: app.fetch }) - ✅ 在生产环境中实现身份验证
- ✅ 更新到 SDK v1.25.3+,以获取安全修复、Tasks 支持和 fetch 污染修复
- ✅ 在工具架构中至少包含一个必填字段(避免所有字段都为可选)
- ✅ 在生产环境中使用
StreamableHTTPServerTransport(SSE 已弃用)
切勿:
- ❌ 在并发 HTTP 会话之间复用
McpServer实例 - ❌ 使用对象包装器导出
- ❌ 忘记关闭 StreamableHTTPServerTransport
- ❌ 省略
transport.onerror处理程序 - ❌ 记录环境变量或机密信息
- ❌ 使用过时的 SDK 版本(<1.23.0 存在架构错误,<1.25.3 存在 fetch 污染问题)