typescript-mcp
from jezweb/claude-skills
Skills for Claude Code CLI such as full stack dev Cloudflare, React, Tailwind v4, and AI integrations.
npx skills add https://github.com/jezweb/claude-skills --skill typescript-mcpSKILL.md
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
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:
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):
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);
Tasks (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:
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
capabilities: {
tasks: {
list: {},
cancel: {},
requests: {
tools: { call: {} }
}
}
}
});
Tool with Task Support:
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:
{
"method": "tools/call",
"params": {
"name": "long-running-analysis",
"arguments": { "datasetId": "abc123" },
"task": { "ttl": 60000 }
}
}
Task Lifecycle:
- Client sends request with
taskparam ā receivestaskId - Client polls via
tasks/getwithtaskId - When status is
completed, client callstasks/resultto get output - Optional: Client can
tasks/cancelto abort
š Spec: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks
Sampling with Tools (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.
// 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
Cloudflare Service Tools
D1 Database:
...