Build MCP servers with TypeScript on Cloudflare Workers. Covers tools, resources, prompts, tasks, authentication (API keys, OAuth, Zero Trust), and Cloudflare service integrations. Prevents 20 documented errors. Use when exposing APIs to LLMs or troubleshooting export syntax errors, transport leaks, server instance reuse bugs, CORS misconfigurations, or task validation errors.
View on GitHubSelect agents to install to:
npx add-skill https://github.com/jezweb/claude-skills/blob/main/skills/typescript-mcp/SKILL.md -a claude-code --skill typescript-mcpInstallation paths:
.claude/skills/typescript-mcp/# TypeScript MCP on Cloudflare Workers
**Last Updated**: 2026-01-21
**Versions**: @modelcontextprotocol/sdk@1.25.3, hono@4.11.3, zod@4.3.5
**Spec Version**: 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`
---
## 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.M