mcpField
T
typescript-mcp

typescript-mcp

github.com/jezweb开发1.1k335 次安装3 周前更新
代码仓库

关于 typescript-mcp

面向在 Cloudflare Workers 上构建 MCP 服务的 TypeScript 开发者技能,提供 SDK、Hono 与 Zod 的实现模式。内容涵盖 Streamable HTTP、认证、长时任务、采样工具及常见生产问题的预防。

主要功能

  1. 01Streamable HTTP 服务模式
  2. 02API Key 与 Zero Trust 认证
  3. 03长时任务生命周期
  4. 04带工具的采样请求
  5. 05Cloudflare 服务工具示例
  6. 06生产问题排查指南

适用场景

  1. 01在 Workers 上部署 MCP 服务
  2. 02为 MCP 端点添加认证
  3. 03实现可轮询的长时操作
  4. 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.json
commands/
init.md
references/
authentication-guide.md
cloudflare-agents-vs-standalone.md
cloudflare-integration.md
common-errors.md
deployment-guide.md
testing-guide.md
tool-patterns.md
rules/
typescript-mcp.md
scripts/
init-mcp-server.sh
test-mcp-connection.sh
templates/
authenticated-server.ts
basic-mcp-server.ts
full-server.ts
resource-server.ts
tasks-server.ts
tool-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 }
  }
}

任务生命周期:

  1. 客户端发送带有 task 参数的请求 → 收到 taskId
  2. 客户端通过 tasks/get 携带 taskId 轮询
  3. 状态为 completed 时,客户端调用 tasks/result 获取输出
  4. 可选:客户端可以调用 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 污染问题)

评论

支持 Markdown