Claude
Skills
Sign in
Back

typescript-mcp

Included with Lifetime
$97 forever

Use this skill when building MCP (Model Context Protocol) servers with TypeScript on Cloudflare Workers. This skill provides production-tested patterns for implementing tools, resources, and prompts using the official @modelcontextprotocol/sdk. It prevents 10+ common errors including export syntax issues, schema validation failures, memory leaks from unclosed transports, CORS misconfigurations, and authentication vulnerabilities. This skill should be used when developers need stateless MCP servers for API integrations, external tool exposure, or serverless edge deployments. For stateful agents with WebSockets and persistent storage, consider the Cloudflare Agents SDK instead. Supports multiple authentication methods (API keys, OAuth, Zero Trust), Cloudflare service integrations (D1, KV, R2, Vectorize), and comprehensive testing strategies. Production tested with token savings of ~70% vs manual implementation. Keywords: mcp, model context protocol, typescript mcp, cloudflare workers mcp, mcp server, mcp tools, mcp resources, mcp sdk, @modelcontextprotocol/sdk, hono mcp, streamablehttpservertransport, mcp authentication, mcp cloudflare, edge mcp server, serverless mcp, typescript mcp server, mcp api, llm tools, ai tools, cloudflare d1 mcp, cloudflare kv mcp, mcp testing, mcp deployment, wrangler mcp, export syntax error, schema validation error, memory leak mcp, cors mcp, rate limiting mcp

Backend & APIs

What this skill does


# TypeScript MCP Server on Cloudflare Workers

Build production-ready Model Context Protocol (MCP) servers using TypeScript and deploy them on Cloudflare Workers. This skill covers the official `@modelcontextprotocol/sdk`, HTTP transport setup, authentication patterns, Cloudflare service integrations, and comprehensive error prevention.

---

## When to Use This Skill

Use this skill when:
- Building **MCP servers** to expose APIs, tools, or data to LLMs
- Deploying **serverless MCP endpoints** on Cloudflare Workers
- Integrating **external APIs** as MCP tools (REST, GraphQL, databases)
- Creating **stateless MCP servers** for edge deployment
- Exposing **Cloudflare services** (D1, KV, R2, Vectorize) via MCP protocol
- Implementing **authenticated MCP servers** with API keys, OAuth, or Zero Trust
- Building **multi-tool MCP servers** with resources and prompts
- Needing **production-ready templates** that prevent common MCP errors

**Do NOT use this skill when**:
- Building **Python MCP servers** (use FastMCP skill instead)
- Needing **stateful agents** with WebSockets (use Cloudflare Agents SDK)
- Wanting **long-running persistent agents** with SQLite storage (use Durable Objects)
- Building **local CLI tools** (use stdio transport, not HTTP)

---

## Core Concepts

### MCP Protocol Components

**1. Tools** - Functions LLMs can invoke
- Input/output schemas defined with Zod
- Async handlers return structured content
- Can call external APIs, databases, or computations

**2. Resources** - Static or dynamic data exposure
- URI-based addressing (e.g., `config://app/settings`)
- Templates support parameters (e.g., `user://{userId}`)
- Return text, JSON, or binary data

**3. Prompts** - Pre-configured prompt templates
- Provide reusable conversation starters
- Can include placeholders and dynamic content
- Help standardize LLM interactions

**4. Completions** (Optional) - Argument auto-complete
- Suggest valid values for tool arguments
- Improve developer experience

---

## Quick Start

### 1. Basic MCP Server Template

Use the `basic-mcp-server.ts` template for a minimal working server:

```typescript
// See templates/basic-mcp-server.ts
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'
});

// Register a simple tool
server.registerTool(
  'echo',
  {
    description: 'Echoes back the input text',
    inputSchema: z.object({
      text: z.string().describe('Text to echo back')
    })
  },
  async ({ text }) => ({
    content: [{ type: 'text', text }]
  })
);

// HTTP endpoint setup
const app = new Hono();

app.post('/mcp', async (c) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true
  });

  // CRITICAL: Close transport on response end 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;
```

**Install dependencies:**
```bash
npm install @modelcontextprotocol/sdk hono zod
npm install -D @cloudflare/workers-types wrangler typescript
```

**Deploy:**
```bash
wrangler deploy
```

---

### 2. Tool-Server Template

Use `tool-server.ts` for exposing multiple tools (API integrations, calculations):

```typescript
// Example: Weather API tool
server.registerTool(
  'get-weather',
  {
    description: 'Fetches current weather for a city',
    inputSchema: z.object({
      city: z.string().describe('City name'),
      units: z.enum(['metric', 'imperial']).default('metric')
    })
  },
  async ({ city, units }, env) => {
    const response = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=${units}&appid=${env.WEATHER_API_KEY}`
    );
    const data = await response.json();

    return {
      content: [{
        type: 'text',
        text: `Temperature in ${city}: ${data.main.temp}°${units === 'metric' ? 'C' : 'F'}`
      }]
    };
  }
);
```

---

### 3. Resource-Server Template

Use `resource-server.ts` for exposing data:

```typescript
import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';

// Static resource
server.registerResource(
  'config',
  new ResourceTemplate('config://app', { list: undefined }),
  { description: 'Application configuration' },
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: 'application/json',
      text: JSON.stringify({ version: '1.0.0', features: ['tool1', 'tool2'] })
    }]
  })
);

// Dynamic resource with parameter
server.registerResource(
  'user-profile',
  new ResourceTemplate('user://{userId}', { list: undefined }),
  { description: 'User profile data' },
  async (uri, { userId }, env) => {
    const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();

    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(user)
      }]
    };
  }
);
```

---

### 4. Authenticated Server Template

Use `authenticated-server.ts` for production security:

```typescript
import { Hono } from 'hono';

const app = new Hono();

// API Key authentication middleware
app.use('/mcp', async (c, next) => {
  const authHeader = c.req.header('Authorization');
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return c.json({ error: 'Unauthorized' }, 401);
  }

  const apiKey = authHeader.replace('Bearer ', '');
  const isValid = await c.env.MCP_API_KEYS.get(`key:${apiKey}`);

  if (!isValid) {
    return c.json({ error: 'Invalid API key' }, 403);
  }

  await next();
});

app.post('/mcp', async (c) => {
  // MCP server logic (user is authenticated)
  // ... transport setup and handling
});
```

---

## Authentication Patterns

### Pattern 1: API Key (Recommended for Most Cases)

**Setup:**
1. Create KV namespace: `wrangler kv namespace create MCP_API_KEYS`
2. Add to `wrangler.jsonc`:
```jsonc
{
  "kv_namespaces": [
    { "binding": "MCP_API_KEYS", "id": "YOUR_NAMESPACE_ID" }
  ]
}
```

**Implementation:**
```typescript
async function verifyApiKey(key: string, env: Env): Promise<boolean> {
  const storedKey = await env.MCP_API_KEYS.get(`key:${key}`);
  return storedKey !== null;
}
```

**Manage keys:**
```bash
# Add key
wrangler kv key put --binding=MCP_API_KEYS "key:abc123" "true"

# Revoke key
wrangler kv key delete --binding=MCP_API_KEYS "key:abc123"
```

### Pattern 2: Cloudflare Zero Trust Access

```typescript
import { verifyJWT } from '@cloudflare/workers-jwt';

const jwt = c.req.header('Cf-Access-Jwt-Assertion');
if (!jwt) {
  return c.json({ error: 'Access denied' }, 403);
}

const payload = await verifyJWT(jwt, c.env.CF_ACCESS_TEAM_DOMAIN);
// User authenticated via Cloudflare Access
```

### Pattern 3: OAuth 2.0

See `references/authentication-guide.md` for complete OAuth implementation.

---

## Cloudflare Service Integration

### D1 Database Tool Example

```typescript
server.registerTool(
  'query-database',
  {
    description: 'Executes SQL query on D1 database',
    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, null, 2)
      }]
    };
  }
);
```

**Wrangler config:**
```jsonc
{
  "d1_databases": [
    { "binding": "DB", "database_name": "my-db", "database_id": "..." }
  ]
}
```

### KV Storage Tool Example

```typescript
server.registerTool(
  'get-cache',
  {
    description: 'Retrieves cached value by key',
    inputSchema: z.object({ key: z.string() })
  },
  a

Related in Backend & APIs