typescript-mcp
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript MCP on Cloudflare Workers
Cloudflare Workers 上的 TypeScript MCP 服务器
Last Updated: 2026-01-21
Versions: @modelcontextprotocol/sdk@1.25.3, hono@4.11.3, zod@4.3.5
Spec Version: 2025-11-25
最后更新时间:2026-01-21
版本:@modelcontextprotocol/sdk@1.25.3, hono@4.11.3, zod@4.3.5
规范版本:2025-11-25
Quick Start
快速开始
bash
npm install @modelcontextprotocol/sdk@latest hono zod
npm install -D @cloudflare/workers-types wrangler typescriptTransport Recommendation: Use for production. SSE transport is deprecated and maintained for backwards compatibility only. Streamable HTTP provides better error recovery, bidirectional communication, and simplified deployment.
StreamableHTTPServerTransportBasic MCP Server:
typescript
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 }Deploy:
wrangler deploybash
npm install @modelcontextprotocol/sdk@latest hono zod
npm install -D @cloudflare/workers-types wrangler typescript传输方式推荐:生产环境使用。SSE传输已被弃用,仅为了向后兼容而维护。可流式HTTP传输提供更好的错误恢复、双向通信和简化的部署流程。
StreamableHTTPServerTransport基础MCP服务器:
typescript
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
});
// 关键:设置错误处理程序以捕获传输错误
transport.onerror = (error) => {
console.error('MCP 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);
});
export default app; // 关键:直接导出,而非 { fetch: app.fetch }部署:
wrangler deployAuthentication
身份验证
API Key (KV-based):
typescript
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:
typescript
const jwt = c.req.header('Cf-Access-Jwt-Assertion');
const payload = await verifyJWT(jwt, c.env.CF_ACCESS_TEAM_DOMAIN);API密钥(基于KV):
typescript
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:
typescript
const jwt = c.req.header('Cf-Access-Jwt-Assertion');
const payload = await verifyJWT(jwt, c.env.CF_ACCESS_TEAM_DOMAIN);Tasks (v1.24.0+)
任务(v1.24.0+)
Tasks enable long-running operations that return a handle for polling results later. Useful for expensive computations, batch processing, or operations that may need input.
Task States: → → / /
workinginput_requiredcompletedfailedcancelledServer Capability Declaration:
typescript
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
capabilities: {
tasks: {
list: {},
cancel: {},
requests: {
tools: { call: {} }
}
}
}
});Tool with Task Support:
typescript
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) }] };
}
);Client Task Request:
json
{
"method": "tools/call",
"params": {
"name": "long-running-analysis",
"arguments": { "datasetId": "abc123" },
"task": { "ttl": 60000 }
}
}Task Lifecycle:
- Client sends request with param → receives
tasktaskId - Client polls via with
tasks/gettaskId - When status is , client calls
completedto get outputtasks/result - Optional: Client can to abort
tasks/cancel
任务支持长时间运行的操作,返回一个句柄用于后续轮询结果。适用于耗时计算、批量处理或可能需要输入的操作。
任务状态: → → / /
workinginput_requiredcompletedfailedcancelled服务器能力声明:
typescript
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
capabilities: {
tasks: {
list: {},
cancel: {},
requests: {
tools: { call: {} }
}
}
}
});支持任务的工具:
typescript
server.registerTool(
'long-running-analysis',
{
description: 'Analyze large dataset',
inputSchema: z.object({ datasetId: z.string() }),
execution: { taskSupport: 'optional' } // 'forbidden' | 'optional' | 'required'
},
async ({ datasetId }, extra) => {
// 如果作为任务调用,extra.task包含taskId
const result = await performAnalysis(datasetId);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
}
);客户端任务请求:
json
{
"method": "tools/call",
"params": {
"name": "long-running-analysis",
"arguments": { "datasetId": "abc123" },
"task": { "ttl": 60000 }
}
}任务生命周期:
- 客户端发送带有参数的请求 → 收到
tasktaskId - 客户端通过接口传入
tasks/get进行轮询taskId - 当状态为时,客户端调用
completed获取输出结果tasks/result - 可选操作:客户端可调用中止任务
tasks/cancel
Sampling with Tools (v1.24.0+)
采样与工具(v1.24.0+)
Servers can now include tool definitions in sampling requests, enabling server-side agent loops.
Use Case: Server needs to orchestrate multi-step reasoning using LLM + tools without custom frameworks.
typescript
// 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...
}Key Points:
- Server-side agentic behavior as first-class MCP feature
- Standard MCP primitives (no custom frameworks)
- Tool definitions follow same schema as
tools/list
📚 Spec: SEP-1577
服务器现在可以在采样请求中包含工具定义,支持服务器端的智能体循环。
使用场景:服务器需要在不依赖自定义框架的情况下,通过LLM + 工具编排多步骤推理流程。
typescript
// 服务器发起包含可用工具的采样请求
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' } } }
}
]
});
// 处理响应中的工具调用
if (result.content[0].type === 'tool_use') {
const toolResult = await executeLocalTool(result.content[0]);
// 使用工具结果继续对话...
}关键点:
- 服务器端智能体行为作为MCP的一等特性
- 使用标准MCP原语(无需自定义框架)
- 工具定义遵循与相同的模式
tools/list
📚 规范文档:SEP-1577
Cloudflare Service Tools
Cloudflare服务工具
D1 Database:
typescript
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: See
references/cloudflare-integration.mdD1数据库:
typescript
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.mdKnown Issues Prevention
已知问题预防
This skill prevents 20 production issues documented in official MCP SDK and Cloudflare repos:
本技能可预防MCP SDK官方仓库和Cloudflare仓库中记录的20种生产环境问题:
Issue #1: Export Syntax Issues (CRITICAL)
问题#1:导出语法问题(严重)
Error:
Source: honojs/hono#3955, honojs/vite-plugins#237
Why It Happens: Incorrect export format with Vite build causes cryptic errors
Prevention:
"Cannot read properties of undefined (reading 'map')"typescript
// ❌ WRONG - Causes cryptic build errors
export default { fetch: app.fetch };
// ✅ CORRECT - Direct export
export default app;错误:
来源:honojs/hono#3955, honojs/vite-plugins#237
原因:Vite构建时使用错误的导出格式导致模糊错误
预防方案:
"Cannot read properties of undefined (reading 'map')"typescript
// ❌ 错误写法 - 会导致模糊的构建错误
export default { fetch: app.fetch };
// ✅ 正确写法 - 直接导出
export default app;Issue #2: Unclosed Transport Connections
问题#2:未关闭的传输连接
Error: Memory leaks, hanging connections
Source: Best practice from SDK maintainers
Why It Happens: Not closing StreamableHTTPServerTransport on request end
Prevention:
typescript
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPServerTransport(/*...*/);
// CRITICAL: Always close on response end
c.res.raw.on('close', () => transport.close());
// ... handle request
});错误:内存泄漏、连接挂起
来源:SDK维护者推荐的最佳实践
原因:未在请求结束时关闭StreamableHTTPServerTransport
预防方案:
typescript
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPServerTransport(/*...*/);
// 关键:始终在响应结束时关闭连接
c.res.raw.on('close', () => transport.close());
// ... 处理请求
});Issue #3: Tool Schema Validation Failure
问题#3:工具模式验证失败
Error:
Source: GitHub modelcontextprotocol/typescript-sdk#1028
Why It Happens: Zod schemas not properly converted to JSON Schema
Prevention:
ListTools request handler fails to generate inputSchematypescript
// ✅ 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错误:
来源:GitHub modelcontextprotocol/typescript-sdk#1028
原因:Zod模式未正确转换为JSON Schema
预防方案:
ListTools request handler fails to generate inputSchematypescript
// ✅ 正确写法 - SDK自动处理Zod模式转换
server.registerTool(
'tool-name',
{
inputSchema: z.object({ a: z.number() })
},
handler
);
// 除非需要自定义验证,否则无需手动调用zodToJsonSchema()Issue #4: Tool Arguments Not Passed to Handler
问题#4:工具参数未传递给处理程序
Error: Handler receives arguments
Source: GitHub modelcontextprotocol/typescript-sdk#1026
Why It Happens: Schema type mismatch between registration and invocation
Prevention:
undefinedtypescript
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) }] };
}
);错误:处理程序收到参数
来源:GitHub modelcontextprotocol/typescript-sdk#1026
原因:注册和调用时的模式类型不匹配
预防方案:
undefinedtypescript
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和args.b会被正确类型化并传递
return { content: [{ type: 'text', text: String(args.a + args.b) }] };
}
);Issue #5: CORS Misconfiguration
问题#5:CORS配置错误
Error: Browser clients can't connect to MCP server
Source: Common production issue
Why It Happens: Missing CORS headers for HTTP transport
Prevention:
typescript
import { cors } from 'hono/cors';
app.use('/mcp', cors({
origin: ['http://localhost:3000', 'https://your-app.com'],
allowMethods: ['POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization']
}));错误:浏览器客户端无法连接到MCP服务器
来源:常见生产环境问题
原因:HTTP传输缺少CORS头
预防方案:
typescript
import { cors } from 'hono/cors';
app.use('/mcp', cors({
origin: ['http://localhost:3000', 'https://your-app.com'],
allowMethods: ['POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization']
}));Issue #6: Missing Rate Limiting
问题#6:缺少速率限制
Error: API abuse, DDoS vulnerability
Source: Production security best practice
Why It Happens: No rate limiting on MCP endpoints
Prevention:
typescript
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...
});错误:API滥用、DDoS漏洞
来源:生产环境安全最佳实践
原因:MCP端点未设置速率限制
预防方案:
typescript
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 }
);
// 继续处理...
});Issue #7: TypeScript Compilation Memory Issues
问题#7:TypeScript编译内存问题
Error: during build
Source: GitHub modelcontextprotocol/typescript-sdk#985
Why It Happens: Large dependency tree in MCP SDK
Prevention:
Out of memorytscbash
undefined错误:在构建期间
来源:GitHub modelcontextprotocol/typescript-sdk#985
原因:MCP SDK的依赖树过大
预防方案:
Out of memorytscbash
undefinedAdd to package.json scripts
添加到package.json的scripts中
"build": "NODE_OPTIONS='--max-old-space-size=4096' tsc && vite build"
undefined"build": "NODE_OPTIONS='--max-old-space-size=4096' tsc && vite build"
undefinedIssue #8: UriTemplate ReDoS Vulnerability
问题#8:UriTemplate ReDoS漏洞
Error: Server hangs on malicious URI patterns
Source: GitHub modelcontextprotocol/typescript-sdk#965 (Security)
Why It Happens: Regex denial-of-service in URI template parsing
Prevention: Update to SDK v1.20.2 or later (includes fix)
错误:服务器在恶意URI模式下挂起
来源:GitHub modelcontextprotocol/typescript-sdk#965(安全问题)
原因:URI模板解析中的正则表达式拒绝服务攻击
预防方案:升级到SDK v1.20.2或更高版本(包含修复)
Issue #9: Authentication Bypass
问题#9:身份验证绕过
Error: Unauthenticated access to MCP tools
Source: Production security best practice
Why It Happens: Missing or improperly implemented authentication
Prevention: Always implement authentication for production servers (see Authentication Patterns section)
错误:未授权访问MCP工具
来源:生产环境安全最佳实践
原因:缺少或实现不当的身份验证
预防方案:生产环境服务器始终实现身份验证(参见身份验证模式章节)
Issue #10: Environment Variable Leakage
问题#10:环境变量泄漏
Error: Secrets exposed in error messages or logs
Source: Cloudflare Workers security best practice
Why It Happens: Environment variables logged or returned in responses
Prevention:
typescript
// ❌ 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);
}错误:机密信息在错误消息或日志中暴露
来源:Cloudflare Workers安全最佳实践
原因:环境变量被记录或在响应中返回
预防方案:
typescript
// ❌ 错误写法 - 暴露机密信息
console.log('Env:', JSON.stringify(env));
// ✅ 正确写法 - 永远不要记录环境对象
try {
// ... 使用env.SECRET_KEY
} catch (error) {
// 错误上下文不要包含环境变量
console.error('Operation failed:', error.message);
}Issue #11: Server Instance Reuse Breaks Concurrent HTTP Sessions (CRITICAL)
问题#11:服务器实例复用破坏并发HTTP会话(严重)
Error:
Source: GitHub Issue #1405
Why It Happens: Calling silently overwrites the previous transport without warning, breaking all earlier connections
Prevention:
AbortError: This operation was abortedServer.connect(transport)typescript
// ✅ 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!
});错误:
来源:GitHub Issue #1405
原因:调用会静默覆盖之前的传输连接,破坏所有早期连接
预防方案:
AbortError: This operation was abortedServer.connect(transport)typescript
// ✅ 正确写法 - 每个HTTP请求创建新的McpServer实例
app.post('/mcp', async (c) => {
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// 每个请求注册工具
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);
});
// ❌ 错误写法 - 跨会话复用服务器实例
const sharedServer = new McpServer({ name: 'my-server', version: '1.0.0' });
app.post('/mcp', async (c) => {
await sharedServer.connect(transport); // 破坏之前的会话!
});Issue #12: sessionIdGenerator Type Error with TypeScript Strict Mode
问题#12:TypeScript严格模式下的sessionIdGenerator类型错误
Error:
Source: GitHub Issue #1397
Why It Happens: SDK 1.25.2 types break projects using in tsconfig.json
Prevention:
Type 'undefined' is not assignable to type '() => string'exactOptionalPropertyTypes: truetypescript
// 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
});错误:
来源:GitHub Issue #1397
原因:SDK 1.25.2的类型定义在tsconfig.json中使用的项目中会出错
预防方案:
Type 'undefined' is not assignable to type '() => string'exactOptionalPropertyTypes: truetypescript
// 当exactOptionalPropertyTypes: true时
// ✅ 正确写法 - 省略该属性而非设置为undefined
const transport = new StreamableHTTPServerTransport({
enableJsonResponse: true
// 完全省略sessionIdGenerator
});
// ❌ 错误写法 - 设置为undefined会在SDK 1.25.2中导致类型错误
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // 类型错误!
enableJsonResponse: true
});
// 替代方案:提供一个生成函数
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
enableJsonResponse: true
});Issue #13: Global fetch Pollution from Hono (SDK 1.25.0-1.25.2)
问题#13:Hono导致的全局fetch污染(SDK 1.25.0-1.25.2)
Error: Native Node.js fetch behavior breaks after importing SDK
Source: GitHub Issue #1376
Why It Happens: Hono's server code globally overwrites , breaking libraries expecting native behavior
Prevention:
global.fetchtypescript
// FIXED in SDK v1.25.3 - Update to latest version
npm install @modelcontextprotocol/sdk@1.25.3
// 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错误:导入SDK后Node.js原生fetch行为被破坏
来源:GitHub Issue #1376
原因:Hono的服务器代码全局覆盖了,破坏了依赖原生行为的库
预防方案:
global.fetchtypescript
// SDK v1.25.3已修复 - 升级到最新版本
npm install @modelcontextprotocol/sdk@1.25.3
// 旧版本(1.25.0-1.25.2)的解决方法:
const nativeFetch = global.fetch;
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
global.fetch = nativeFetch; // 如需恢复原生fetchIssue #14: Task Error Wrapping Masks Validation Errors
问题#14:任务错误包装掩盖验证错误
Error: Confusing error message hides actual validation failure
Source: GitHub Issue #1385
Why It Happens: When task-augmented tool call fails validation before task creation, SDK wraps error incorrectly
Prevention:
typescript
// 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
}
}
);错误:混淆的错误消息隐藏了实际的验证失败
来源:GitHub Issue #1385
原因:当任务增强的工具调用在任务创建前验证失败时,SDK错误地包装了错误
预防方案:
typescript
// 无效输入的预期错误:
// "Invalid arguments: Too small: expected number to be >=500"
// 实际错误(混淆):
// "Invalid task creation result: expected object, received undefined"
// 解决方法:在任务逻辑前添加显式验证
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应修复此问题 - 当前无解决方法
// 验证错误被任务包装掩盖
}
}
);Issue #15: Tool Schema with All Optional Fields Causes InvalidParams
问题#15:所有字段可选的工具模式导致InvalidParams
Error:
Source: GitHub Issue #400
Why It Happens: Some LLM clients omit field when all schema properties are optional
Prevention:
"expected": "object", "received": "undefined"argumentstypescript
// ❌ 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);错误:
来源:GitHub Issue #400
原因:当模式的所有属性都是可选的时,部分LLM客户端会省略字段
预防方案:
"expected": "object", "received": "undefined"argumentstypescript
// ❌ 错误写法 - 所有可选字段可能导致问题
server.registerTool('fetch-records', {
inputSchema: z.object({
limit: z.number().optional()
})
}, handler);
// ✅ 正确写法 - 始终包含至少一个必填字段
server.registerTool('fetch-records', {
inputSchema: z.object({
action: z.literal('fetch').default('fetch'), // 必填
limit: z.number().optional()
})
}, handler);
// 替代方案:使用空对象模式
server.registerTool('fetch-records', {
inputSchema: z.object({}).passthrough()
}, handler);Issue #16: Bulk Tool Registration Triggers EventEmitter Memory Leak Warnings
问题#16:批量工具注册触发EventEmitter内存泄漏警告
Error:
Source: GitHub Issue #842
Why It Happens: Registering 80+ tools in a loop overwhelms stdout buffer with rapid notifications
Prevention:
MaxListenersExceededWarning: Possible EventEmitter memory leak detectedsendToolListChanged()typescript
// 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错误:
来源:GitHub Issue #842
原因:循环注册80+个工具会通过频繁的通知 overwhelm stdout缓冲区
预防方案:
MaxListenersExceededWarning: Possible EventEmitter memory leak detectedsendToolListChanged()typescript
// 解决方法:批量注册前增加最大监听器数量
process.stdout.setMaxListeners(100);
const tools = [...]; // 包含80+个工具定义的数组
for (const tool of tools) {
server.registerTool(tool.name, tool.schema, tool.handler);
}
// 未来SDK可能会提供批量注册APIIssue #17: Silent Transport Errors Without onerror Handler
问题#17:无onerror处理程序时的静默传输错误
Error: Transport errors vanish without logs or exceptions
Source: GitHub Issue #1395
Why It Happens: SDK silently swallows transport errors if callback is not set
Prevention:
onerrortypescript
// ✅ 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);错误:传输错误消失,无日志或异常
来源:GitHub Issue #1395
原因:如果未设置回调,SDK会静默吞掉传输错误
预防方案:
onerrortypescript
// ✅ 正确写法 - 始终设置onerror处理程序
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
transport.onerror = (error) => {
console.error('Transport error:', error);
// 适当处理错误
};
await server.connect(transport);Issue #18: DoS via Query String Array Limit Bypass
问题#18:通过查询字符串数组限制绕过的DoS攻击
Error: Memory exhaustion from malicious query parameters
Source: GitHub Issue #1368
Why It Happens: The library's can be bypassed using bracket notation like
Prevention:
qsarrayLimit?foo[99999999]=bartypescript
// 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
});typescript
// 验证查询参数以防止DoS攻击
app.post('/mcp', async (c) => {
const queryParams = c.req.query();
// 拒绝恶意模式
if (Object.keys(queryParams).some(key => /\[\d{6,}\]/.test(key))) {
return c.json({ error: 'Invalid query parameters' }, 400);
}
// ... 处理请求
});Issue #19: Request Handlers Not Cancelled on Transport Close
问题#19:传输关闭时请求处理程序未被取消
Error: Long-running handlers continue executing after client disconnect, wasting resources
Source: GitHub Issue #611
Why It Happens: SDK doesn't automatically cancel request handlers when transport connection closes
Prevention:
typescript
// 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;
}
}
);错误:客户端断开连接后,长时间运行的处理程序继续执行,浪费资源
来源:GitHub Issue #611
原因:SDK不会在传输连接关闭时自动取消请求处理程序
预防方案:
typescript
// 解决方法:手动使用AbortController模式
server.registerTool(
'long-running-task',
{ inputSchema: z.object({ duration: z.number() }) },
async ({ duration }, extra) => {
const abortController = new AbortController();
// 监听传输关闭事件
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;
}
}
);Issue #20: $defs Schema References Failed in SDK 1.22.0-1.22.x
问题#20:SDK 1.22.0-1.22.x中$defs模式引用失败
Error:
Source: GitHub Issue #1175
Why It Happens: SDK 1.22.0 regression in broke with complex JSON Schema
Prevention: Update to SDK v1.23.0 or later (fixed). If on 1.22.x, upgrade immediately.
can't resolve reference #/$defs/...cacheToolOutputSchemaslistTools()错误:
来源:GitHub Issue #1175
原因:SDK 1.22.0中的回归问题破坏了带有复杂JSON Schema的
预防方案:升级到SDK v1.23.0或更高版本(已修复)。如果使用1.22.x版本,立即升级。
can't resolve reference #/$defs/...cacheToolOutputSchemaslistTools()Deployment
部署
bash
undefinedbash
undefinedLocal
本地开发
wrangler dev # http://localhost:8787/mcp
wrangler dev # http://localhost:8787/mcp
Production
生产环境部署
wrangler deploy
**Testing**: `npx @modelcontextprotocol/inspector` (connect to http://localhost:8787/mcp)
---wrangler deploy
**测试**:`npx @modelcontextprotocol/inspector`(连接到http://localhost:8787/mcp)
---Templates & References
模板与参考
Templates: , , , , ,
basic-mcp-server.tstool-server.tsresource-server.tsauthenticated-server.tstasks-server.tswrangler.jsoncReferences: , , , ,
tool-patterns.mdauthentication-guide.mdtesting-guide.mdcloudflare-integration.mdcommon-errors.md模板:, , , , ,
basic-mcp-server.tstool-server.tsresource-server.tsauthenticated-server.tstasks-server.tswrangler.jsonc参考文档:, , , ,
tool-patterns.mdauthentication-guide.mdtesting-guide.mdcloudflare-integration.mdcommon-errors.mdCritical Rules
关键规则
Always:
- ✅ Create fresh instance per HTTP request (never reuse across sessions)
McpServer - ✅ Set handler to catch silent errors
transport.onerror - ✅ Close transport on response end ()
c.res.raw.on('close', () => transport.close()) - ✅ Use direct export (, NOT
export default app){ fetch: app.fetch } - ✅ Implement authentication for production
- ✅ Update to SDK v1.25.3+ for security fixes, Tasks support, and fetch pollution fix
- ✅ Include at least one required field in tool schemas (avoid all-optional)
- ✅ Use for production (SSE is deprecated)
StreamableHTTPServerTransport
Never:
- ❌ Reuse instance across concurrent HTTP sessions
McpServer - ❌ Export with object wrapper
- ❌ Forget to close StreamableHTTPServerTransport
- ❌ Omit handler
transport.onerror - ❌ Log environment variables or secrets
- ❌ Use outdated SDK versions (<1.23.0 has schema bugs, <1.25.3 has fetch pollution)
始终:
- ✅ 每个HTTP请求创建新的实例(永远不要跨会话复用)
McpServer - ✅ 设置处理程序以捕获静默错误
transport.onerror - ✅ 响应结束时关闭传输连接()
c.res.raw.on('close', () => transport.close()) - ✅ 使用直接导出(,而非
export default app){ fetch: app.fetch } - ✅ 生产环境实现身份验证
- ✅ 升级到SDK v1.25.3+以获取安全修复、任务支持和fetch污染修复
- ✅ 工具模式中至少包含一个必填字段(避免全可选)
- ✅ 生产环境使用(SSE已弃用)
StreamableHTTPServerTransport
永远不要:
- ❌ 跨并发HTTP会话复用实例
McpServer - ❌ 使用对象包装导出
- ❌ 忘记关闭StreamableHTTPServerTransport
- ❌ 省略处理程序
transport.onerror - ❌ 记录环境变量或机密信息
- ❌ 使用过时的SDK版本(<1.23.0存在模式bug,<1.25.3存在fetch污染问题)