# MCP TypeScript SDK Deep Dive

## What Is It?

The **MCP TypeScript SDK** is the official TypeScript implementation for building Model Context Protocol servers and clients. With **11.2k+ stars**, it provides type-safe APIs, Zod schema validation, and multiple transport options for extending AI assistants.

**Repository:** [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk)

---

## The Problem It Solves

Building MCP implementations in TypeScript requires:
- Type-safe protocol handling
- Schema validation for tools and resources
- Multiple transport support
- Client and server APIs

MCP TypeScript SDK provides:
- **Full type safety** with TypeScript
- **Zod integration** for runtime validation
- **Multiple transports** (STDIO, HTTP, SSE)
- **Comprehensive examples** for common patterns

---

## Key Statistics

| Metric | Value |
|--------|-------|
| **Stars** | 11,200 |
| **Forks** | 1,200 |
| **Server Examples** | 15+ |
| **Client Examples** | 10+ |
| **License** | MIT |
| **TypeScript** | Native |

---

## Installation

```bash
# npm
npm install @modelcontextprotocol/sdk

# yarn
yarn add @modelcontextprotocol/sdk

# pnpm
pnpm add @modelcontextprotocol/sdk
```

---

## Server API

### Basic Server

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// Start with STDIO transport
const transport = new StdioServerTransport();
await server.connect(transport);
```

### Registering Tools

```typescript
import { z } from "zod";

server.tool(
  "calculate",
  "Perform mathematical calculations",
  {
    operation: z.enum(["add", "subtract", "multiply", "divide"]),
    a: z.number(),
    b: z.number()
  },
  async ({ operation, a, b }) => {
    let result: number;
    switch (operation) {
      case "add": result = a + b; break;
      case "subtract": result = a - b; break;
      case "multiply": result = a * b; break;
      case "divide": result = a / b; break;
    }
    return {
      content: [{ type: "text", text: `Result: ${result}` }]
    };
  }
);
```

### Registering Resources

```typescript
// Static resource
server.resource(
  "config://settings",
  "Application configuration",
  async () => ({
    contents: [{
      uri: "config://settings",
      text: JSON.stringify({ theme: "dark", language: "en" })
    }]
  })
);

// Dynamic resource with URI template
server.resource(
  "user://{userId}/profile",
  "User profile by ID",
  async (uri) => {
    const userId = uri.pathname.split("/")[1];
    const profile = await fetchUserProfile(userId);
    return {
      contents: [{
        uri: uri.href,
        text: JSON.stringify(profile)
      }]
    };
  }
);
```

### Registering Prompts

```typescript
server.prompt(
  "code-review",
  "Generate a code review prompt",
  {
    language: z.string().optional(),
    focus: z.enum(["security", "performance", "style"]).optional()
  },
  async ({ language, focus }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Review the following ${language || ""} code, focusing on ${focus || "general quality"}:`
      }
    }]
  })
);
```

---

## Transport Options

### STDIO Transport

For local development and Claude Desktop:

```typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Streamable HTTP Transport

For remote servers:

```typescript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js";

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID(),
});

// Express integration
app.use("/mcp", async (req, res) => {
  await transport.handleRequest(req, res);
});
```

### SSE Transport

For real-time streaming:

```typescript
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
```

---

## Client API

### Basic Client

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const client = new Client({
  name: "example-client",
  version: "1.0.0"
});

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

await client.connect(transport);
```

### Calling Tools

```typescript
const result = await client.callTool("calculate", {
  operation: "add",
  a: 10,
  b: 5
});

console.log(result.content[0].text); // "Result: 15"
```

### Reading Resources

```typescript
const resource = await client.readResource("config://settings");
const config = JSON.parse(resource.contents[0].text);
console.log(config.theme); // "dark"
```

### Using Prompts

```typescript
const prompt = await client.getPrompt("code-review", {
  language: "typescript",
  focus: "security"
});

console.log(prompt.messages[0].content.text);
```

### Listing Capabilities

```typescript
// List all tools
const tools = await client.listTools();
tools.tools.forEach(tool => {
  console.log(`${tool.name}: ${tool.description}`);
});

// List all resources
const resources = await client.listResources();
resources.resources.forEach(resource => {
  console.log(`${resource.uri}: ${resource.name}`);
});

// List all prompts
const prompts = await client.listPrompts();
prompts.prompts.forEach(prompt => {
  console.log(`${prompt.name}: ${prompt.description}`);
});
```

---

## Zod Integration

TypeScript SDK uses Zod for runtime validation:

### Tool Schemas

```typescript
import { z } from "zod";

// Complex input schema
const SearchSchema = z.object({
  query: z.string().min(1).describe("Search query"),
  filters: z.object({
    category: z.enum(["all", "docs", "code"]).optional(),
    dateRange: z.object({
      start: z.string().datetime(),
      end: z.string().datetime()
    }).optional()
  }).optional(),
  limit: z.number().int().min(1).max(100).default(10)
});

server.tool(
  "search",
  "Search documents",
  SearchSchema,
  async (input) => {
    // input is fully typed based on schema
    const results = await searchDocuments(input);
    return {
      content: [{ type: "text", text: JSON.stringify(results) }]
    };
  }
);
```

### Benefits

1. **Type inference** - TypeScript types from schemas
2. **Runtime validation** - Catches invalid inputs
3. **Documentation** - `.describe()` generates help
4. **Defaults** - `.default()` provides fallbacks

---

## Complete Server Example

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "notes-server",
  version: "1.0.0"
});

// In-memory storage
const notes = new Map<string, { title: string; content: string }>();

// Tool: Create note
server.tool(
  "create_note",
  "Create a new note",
  {
    title: z.string().min(1),
    content: z.string()
  },
  async ({ title, content }) => {
    const id = crypto.randomUUID();
    notes.set(id, { title, content });
    return {
      content: [{ type: "text", text: `Created note with ID: ${id}` }]
    };
  }
);

// Tool: List notes
server.tool(
  "list_notes",
  "List all notes",
  {},
  async () => {
    const list = Array.from(notes.entries()).map(([id, note]) => ({
      id,
      title: note.title
    }));
    return {
      content: [{ type: "text", text: JSON.stringify(list, null, 2) }]
    };
  }
);

// Resource: Get note by ID
server.resource(
  "note://{id}",
  "Get note content by ID",
  async (uri) => {
    const id = uri.pathname.slice(1);
    const note = notes.get(id);
    if (!note) {
      throw new Error(`Note not found: ${id}`);
    }
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(note)
      }]
    };
  }
);

// Prompt: Summarize notes
server.prompt(
  "summarize",
  "Summarize all notes",
  {},
  async () => {
    const allNotes = Array.from(notes.values())
      .map(n => `## ${n.title}\n${n.content}`)
      .join("\n\n");
    return {
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `Summarize these notes:\n\n${allNotes}`
        }
      }]
    };
  }
);

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Notes MCP server running on stdio");
```

---

## OAuth Provider Support

For authenticated services:

```typescript
import { OAuthProvider } from "@modelcontextprotocol/sdk/server/auth.js";

const oauthProvider: OAuthProvider = {
  async authenticate(request) {
    // Validate token
    const token = request.headers.authorization?.slice(7);
    if (!token) throw new Error("No token");

    const user = await validateToken(token);
    return { userId: user.id };
  },

  async getAuthorizationUrl(state, scopes) {
    return `https://auth.example.com/authorize?state=${state}&scope=${scopes.join(" ")}`;
  },

  async exchangeCode(code) {
    const response = await fetch("https://auth.example.com/token", {
      method: "POST",
      body: JSON.stringify({ code })
    });
    return response.json();
  }
};

const transport = new StreamableHTTPServerTransport({
  oauthProvider,
  sessionIdGenerator: () => crypto.randomUUID()
});
```

---

## Express Integration

```typescript
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamablehttp.js";

const app = express();
const server = new McpServer({ name: "api-server", version: "1.0.0" });

// Register tools...

// MCP endpoint
const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: () => crypto.randomUUID()
});

app.use("/mcp", async (req, res) => {
  try {
    await transport.handleRequest(req, res, server);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log("MCP server running on http://localhost:3000/mcp");
});
```

---

## Error Handling

```typescript
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";

server.tool(
  "risky_operation",
  "An operation that might fail",
  { input: z.string() },
  async ({ input }) => {
    if (!input) {
      throw new McpError(
        ErrorCode.InvalidParams,
        "Input is required"
      );
    }

    try {
      const result = await riskyOperation(input);
      return {
        content: [{ type: "text", text: result }]
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Operation failed: ${error.message}`
      );
    }
  }
);
```

### Error Codes

| Code | Meaning |
|------|---------|
| `ParseError` | Invalid JSON |
| `InvalidRequest` | Malformed request |
| `MethodNotFound` | Unknown method |
| `InvalidParams` | Wrong parameters |
| `InternalError` | Server error |

---

## Testing

### Unit Testing Tools

```typescript
import { describe, it, expect } from "vitest";

describe("calculate tool", () => {
  it("adds numbers correctly", async () => {
    const result = await server.callToolHandler("calculate", {
      operation: "add",
      a: 5,
      b: 3
    });

    expect(result.content[0].text).toBe("Result: 8");
  });
});
```

### Integration Testing

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/testing.js";

describe("server integration", () => {
  it("connects and lists tools", async () => {
    const [clientTransport, serverTransport] = InMemoryTransport.createPair();

    await server.connect(serverTransport);
    await client.connect(clientTransport);

    const tools = await client.listTools();
    expect(tools.tools.length).toBeGreaterThan(0);
  });
});
```

---

## Claude Desktop Configuration

```json
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["path/to/server.js"],
      "env": {
        "API_KEY": "your-key"
      }
    }
  }
}
```

## Claude Code Configuration

```bash
claude mcp add my-server node path/to/server.js
```

---

## Comparison: TypeScript vs Python SDK

| Aspect | TypeScript | Python |
|--------|------------|--------|
| **Type System** | Compile-time + Zod | Runtime (Pydantic) |
| **API Style** | Method chaining | Decorators |
| **Ecosystem** | Node.js, web | AI/ML, data science |
| **Transport** | All supported | All supported |

### When to Choose TypeScript

- Web development background
- Strong typing requirements
- Node.js ecosystem integration
- Frontend + backend in same language

### When to Choose Python

- AI/ML team
- Data science tools
- Rapid prototyping
- NumPy/pandas usage

---

## Best Practices

### Naming

```typescript
// Clear, descriptive names
server.tool(
  "search_documents",        // verb_noun
  "Search documents by query",
  { /* schema */ },
  async (input) => { /* ... */ }
);
```

### Descriptions

```typescript
// Helpful descriptions for LLM understanding
server.tool(
  "send_email",
  "Send an email message. Requires recipient, subject, and body. " +
  "Returns success status and message ID.",
  { /* schema */ },
  async (input) => { /* ... */ }
);
```

### Resource URIs

```typescript
// Hierarchical, consistent URIs
server.resource("docs://api/endpoints");
server.resource("config://app/database");
server.resource("user://{id}/settings");
```

---

## Resources

- **Repository:** https://github.com/modelcontextprotocol/typescript-sdk
- **npm:** https://www.npmjs.com/package/@modelcontextprotocol/sdk
- **MCP Specification:** https://modelcontextprotocol.io
- **Examples:** https://github.com/modelcontextprotocol/servers

---

*The MCP TypeScript SDK brings full type safety to AI integrations. With Zod validation and comprehensive transport support, it's the production-ready choice for TypeScript developers building MCP servers.*
