typescript-mcp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript 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 typescript
Transport Recommendation: Use
StreamableHTTPServerTransport
for production. SSE transport is deprecated and maintained for backwards compatibility only. Streamable HTTP provides better error recovery, bidirectional communication, and simplified deployment.
Basic 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 deploy

bash
npm install @modelcontextprotocol/sdk@latest hono zod
npm install -D @cloudflare/workers-types wrangler typescript
传输方式推荐:生产环境使用
StreamableHTTPServerTransport
。SSE传输已被弃用,仅为了向后兼容而维护。可流式HTTP传输提供更好的错误恢复、双向通信和简化的部署流程。
基础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 deploy

Authentication

身份验证

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:
working
input_required
completed
/
failed
/
cancelled
Server 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:
  1. Client sends request with
    task
    param → receives
    taskId
  2. Client polls via
    tasks/get
    with
    taskId
  3. When status is
    completed
    , client calls
    tasks/result
    to get output
  4. Optional: Client can
    tasks/cancel
    to abort

任务支持长时间运行的操作,返回一个句柄用于后续轮询结果。适用于耗时计算、批量处理或可能需要输入的操作。
任务状态
working
input_required
completed
/
failed
/
cancelled
服务器能力声明
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 }
  }
}
任务生命周期
  1. 客户端发送带有
    task
    参数的请求 → 收到
    taskId
  2. 客户端通过
    tasks/get
    接口传入
    taskId
    进行轮询
  3. 当状态为
    completed
    时,客户端调用
    tasks/result
    获取输出结果
  4. 可选操作:客户端可调用
    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.md

D1数据库
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.md

Known 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:
"Cannot read properties of undefined (reading 'map')"
Source: honojs/hono#3955, honojs/vite-plugins#237 Why It Happens: Incorrect export format with Vite build causes cryptic errors Prevention:
typescript
// ❌ WRONG - Causes cryptic build errors
export default { fetch: app.fetch };

// ✅ CORRECT - Direct export
export default app;
错误
"Cannot read properties of undefined (reading 'map')"
来源:honojs/hono#3955, honojs/vite-plugins#237 原因:Vite构建时使用错误的导出格式导致模糊错误 预防方案
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:
ListTools request handler fails to generate inputSchema
Source: GitHub modelcontextprotocol/typescript-sdk#1028 Why It Happens: Zod schemas not properly converted to JSON Schema Prevention:
typescript
// ✅ 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
错误
ListTools request handler fails to generate inputSchema
来源:GitHub modelcontextprotocol/typescript-sdk#1028 原因:Zod模式未正确转换为JSON Schema 预防方案
typescript
// ✅ 正确写法 - 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
undefined
arguments Source: GitHub modelcontextprotocol/typescript-sdk#1026 Why It Happens: Schema type mismatch between registration and invocation Prevention:
typescript
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) }] };
  }
);
错误:处理程序收到
undefined
参数 来源:GitHub modelcontextprotocol/typescript-sdk#1026 原因:注册和调用时的模式类型不匹配 预防方案
typescript
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:
Out of memory
during
tsc
build Source: GitHub modelcontextprotocol/typescript-sdk#985 Why It Happens: Large dependency tree in MCP SDK Prevention:
bash
undefined
错误
Out of memory
tsc
构建期间 来源:GitHub modelcontextprotocol/typescript-sdk#985 原因:MCP SDK的依赖树过大 预防方案
bash
undefined

Add 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"
undefined

Issue #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:
AbortError: This operation was aborted
Source: GitHub Issue #1405 Why It Happens: Calling
Server.connect(transport)
silently overwrites the previous transport without warning, breaking all earlier connections Prevention:
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!
});
错误
AbortError: This operation was aborted
来源GitHub Issue #1405 原因:调用
Server.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:
Type 'undefined' is not assignable to type '() => string'
Source: GitHub Issue #1397 Why It Happens: SDK 1.25.2 types break projects using
exactOptionalPropertyTypes: true
in tsconfig.json Prevention:
typescript
// 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
});
错误
Type 'undefined' is not assignable to type '() => string'
来源GitHub Issue #1397 原因:SDK 1.25.2的类型定义在tsconfig.json中使用
exactOptionalPropertyTypes: true
的项目中会出错 预防方案
typescript
// 当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
global.fetch
, breaking libraries expecting native behavior Prevention:
typescript
// 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.fetch
,破坏了依赖原生行为的库 预防方案
typescript
// 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; // 如需恢复原生fetch

Issue #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:
"expected": "object", "received": "undefined"
Source: GitHub Issue #400 Why It Happens: Some LLM clients omit
arguments
field when all schema properties are optional Prevention:
typescript
// ❌ 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);
错误
"expected": "object", "received": "undefined"
来源GitHub Issue #400 原因:当模式的所有属性都是可选的时,部分LLM客户端会省略
arguments
字段 预防方案
typescript
// ❌ 错误写法 - 所有可选字段可能导致问题
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:
MaxListenersExceededWarning: Possible EventEmitter memory leak detected
Source: GitHub Issue #842 Why It Happens: Registering 80+ tools in a loop overwhelms stdout buffer with rapid
sendToolListChanged()
notifications Prevention:
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
错误
MaxListenersExceededWarning: Possible EventEmitter memory leak detected
来源GitHub Issue #842 原因:循环注册80+个工具会通过频繁的
sendToolListChanged()
通知 overwhelm stdout缓冲区 预防方案
typescript
// 解决方法:批量注册前增加最大监听器数量
process.stdout.setMaxListeners(100);

const tools = [...]; // 包含80+个工具定义的数组
for (const tool of tools) {
  server.registerTool(tool.name, tool.schema, tool.handler);
}

// 未来SDK可能会提供批量注册API

Issue #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
onerror
callback is not set Prevention:
typescript
// ✅ 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 原因:如果未设置
onerror
回调,SDK会静默吞掉传输错误 预防方案
typescript
// ✅ 正确写法 - 始终设置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
qs
library's
arrayLimit
can be bypassed using bracket notation like
?foo[99999999]=bar
Prevention:
typescript
// 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
});
错误:恶意查询参数导致内存耗尽 来源GitHub Issue #1368 原因
qs
库的
arrayLimit
可以通过类似
?foo[99999999]=bar
的括号表示法绕过 预防方案
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:
can't resolve reference #/$defs/...
Source: GitHub Issue #1175 Why It Happens: SDK 1.22.0 regression in
cacheToolOutputSchemas
broke
listTools()
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/...
来源GitHub Issue #1175 原因:SDK 1.22.0中
cacheToolOutputSchemas
的回归问题破坏了带有复杂JSON Schema的
listTools()
预防方案:升级到SDK v1.23.0或更高版本(已修复)。如果使用1.22.x版本,立即升级。

Deployment

部署

bash
undefined
bash
undefined

Local

本地开发

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.ts
,
tool-server.ts
,
resource-server.ts
,
authenticated-server.ts
,
tasks-server.ts
,
wrangler.jsonc
References:
tool-patterns.md
,
authentication-guide.md
,
testing-guide.md
,
cloudflare-integration.md
,
common-errors.md

模板
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

Critical Rules

关键规则

Always:
  • ✅ Create fresh
    McpServer
    instance per HTTP request (never reuse across sessions)
  • ✅ Set
    transport.onerror
    handler to catch silent errors
  • ✅ Close transport on response end (
    c.res.raw.on('close', () => transport.close())
    )
  • ✅ Use direct export (
    export default app
    , NOT
    { 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
    StreamableHTTPServerTransport
    for production (SSE is deprecated)
Never:
  • ❌ Reuse
    McpServer
    instance across concurrent HTTP sessions
  • ❌ Export with object wrapper
  • ❌ Forget to close StreamableHTTPServerTransport
  • ❌ Omit
    transport.onerror
    handler
  • ❌ 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污染修复
  • ✅ 工具模式中至少包含一个必填字段(避免全可选)
  • ✅ 生产环境使用
    StreamableHTTPServerTransport
    (SSE已弃用)
永远不要
  • ❌ 跨并发HTTP会话复用
    McpServer
    实例
  • ❌ 使用对象包装导出
  • ❌ 忘记关闭StreamableHTTPServerTransport
  • ❌ 省略
    transport.onerror
    处理程序
  • ❌ 记录环境变量或机密信息
  • ❌ 使用过时的SDK版本(<1.23.0存在模式bug,<1.25.3存在fetch污染问题)