# MCP TypeScript SDK The Model Context Protocol (MCP) TypeScript SDK provides a standardized way for applications to provide context for Large Language Models (LLMs), separating context provisioning from LLM interaction. This SDK enables building both MCP servers that expose tools, resources, and prompts, and MCP clients that connect to and consume these services. The SDK supports multiple transport mechanisms including Streamable HTTP for remote servers and stdio for local process-spawned integrations. The SDK is organized as a monorepo with split packages: `@modelcontextprotocol/server` for building MCP servers, `@modelcontextprotocol/client` for building clients, and optional middleware packages (`@modelcontextprotocol/node`, `@modelcontextprotocol/express`, `@modelcontextprotocol/hono`) for framework-specific integrations. All packages require Zod v4 as a peer dependency for schema validation. The SDK requires Node.js 20+ and ships ESM only. --- ## Server Installation Install the server package with zod for building MCP servers. ```bash npm install @modelcontextprotocol/server zod ``` --- ## Client Installation Install the client package with zod for building MCP clients. ```bash npm install @modelcontextprotocol/client zod ``` --- ## McpServer - Create Server Instance The `McpServer` class provides a high-level API for creating MCP servers with tools, resources, and prompts. ```typescript import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); // Connect via stdio for local integrations const transport = new StdioServerTransport(); await server.connect(transport); ``` --- ## registerTool - Register Server Tools Tools let MCP clients ask your server to perform actions. Register tools with input/output schemas using Zod for validation. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); server.registerTool( 'calculate-bmi', { title: 'BMI Calculator', description: 'Calculate Body Mass Index', inputSchema: z.object({ weightKg: z.number(), heightM: z.number() }), outputSchema: z.object({ bmi: z.number() }) }, async ({ weightKg, heightM }) => { const output = { bmi: weightKg / (heightM * heightM) }; return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output }; } ); ``` --- ## registerResource - Register Server Resources Resources expose data to clients without heavy computation. Register static resources at fixed URIs or dynamic resources with templates. ```typescript import { McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); // Static resource at fixed URI server.registerResource( 'config', 'config://app', { title: 'Application Config', description: 'Application configuration data', mimeType: 'text/plain' }, async uri => ({ contents: [{ uri: uri.href, text: 'App configuration here' }] }) ); // Dynamic resource with URI template server.registerResource( 'user-profile', new ResourceTemplate('user://{userId}/profile', { list: async () => ({ resources: [ { uri: 'user://123/profile', name: 'Alice' }, { uri: 'user://456/profile', name: 'Bob' } ] }) }), { title: 'User Profile', description: 'User profile data', mimeType: 'application/json' }, async (uri, { userId }) => ({ contents: [ { uri: uri.href, text: JSON.stringify({ userId, name: 'Example User' }) } ] }) ); ``` --- ## registerPrompt - Register Server Prompts Prompts are reusable templates that help users interact with models consistently. Register prompts with argument schemas. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); server.registerPrompt( 'review-code', { title: 'Code Review', description: 'Review code for best practices and potential issues', argsSchema: z.object({ code: z.string() }) }, ({ code }) => ({ messages: [ { role: 'user' as const, content: { type: 'text' as const, text: `Please review this code:\n\n${code}` } } ] }) ); ``` --- ## NodeStreamableHTTPServerTransport - HTTP Transport Create HTTP-based MCP servers with session management, SSE streaming, and resumability support. ```typescript import { randomUUID } from 'node:crypto'; import { McpServer } from '@modelcontextprotocol/server'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); const app = createMcpExpressApp(); // Stateful transport with sessions const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() }); await server.connect(transport); app.post('/mcp', async (req, res) => { await transport.handleRequest(req, res, req.body); }); app.listen(3000, () => { console.log('MCP server running on port 3000'); }); ``` --- ## Client - Create Client Instance The `Client` class connects to MCP servers over different transports and provides methods to interact with tools, resources, and prompts. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport( new URL('http://localhost:3000/mcp') ); await client.connect(transport); ``` --- ## StdioClientTransport - Local Process Transport Connect to local MCP servers spawned as child processes using stdio transport. ```typescript import { Client, StdioClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StdioClientTransport({ command: 'node', args: ['server.js'] }); await client.connect(transport); ``` --- ## listTools / callTool - Invoke Server Tools Discover available tools and invoke them with arguments. The client validates structured output against declared schemas. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); // List available tools const { tools } = await client.listTools(); console.log('Available tools:', tools.map(t => t.name)); // Call a tool const result = await client.callTool({ name: 'calculate-bmi', arguments: { weightKg: 70, heightM: 1.75 } }); console.log('Result:', result.content); // Output: [{ type: 'text', text: '{"bmi":22.857142857142858}' }] if (result.structuredContent) { console.log('BMI:', result.structuredContent.bmi); } ``` --- ## listResources / readResource - Access Server Resources Discover and read resources exposed by the server. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); // List available resources const { resources } = await client.listResources(); console.log('Available resources:', resources.map(r => r.name)); // Read a specific resource const { contents } = await client.readResource({ uri: 'config://app' }); for (const item of contents) { console.log('Content:', item.text); } ``` --- ## listPrompts / getPrompt - Retrieve Prompt Templates Discover and retrieve prompt templates from the server. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); // List available prompts const { prompts } = await client.listPrompts(); console.log('Available prompts:', prompts.map(p => p.name)); // Get a prompt with arguments const { messages } = await client.getPrompt({ name: 'review-code', arguments: { code: 'console.log("hello")' } }); console.log('Prompt messages:', messages); // Output: [{ role: 'user', content: { type: 'text', text: 'Please review this code:\n\nconsole.log("hello")' } }] ``` --- ## setNotificationHandler - Handle Server Notifications Register handlers for server-sent notifications like log messages and list changes. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); // Handle server log messages client.setNotificationHandler('notifications/message', notification => { const { level, data } = notification.params; console.log(`[${level}]`, data); }); // Handle resource list changes client.setNotificationHandler('notifications/resources/list_changed', async () => { const { resources } = await client.listResources(); console.log('Resources changed:', resources.length); }); // Handle tool list changes client.setNotificationHandler('notifications/tools/list_changed', async () => { const { tools } = await client.listTools(); console.log('Tools updated:', tools.map(t => t.name)); }); await client.connect(transport); ``` --- ## Logging from Tool Handlers Send structured log messages to clients from within tool handlers using the server context. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import type { CallToolResult } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer( { name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } } ); server.registerTool( 'fetch-data', { description: 'Fetch data from an API', inputSchema: z.object({ url: z.string() }) }, async ({ url }, ctx): Promise => { await ctx.mcpReq.log('info', `Fetching ${url}`); const res = await fetch(url); await ctx.mcpReq.log('debug', `Response status: ${res.status}`); const text = await res.text(); return { content: [{ type: 'text', text }] }; } ); ``` --- ## Client Capabilities - Sampling and Elicitation Declare client capabilities for handling server-initiated requests like LLM sampling and user input elicitation. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client( { name: 'my-client', version: '1.0.0' }, { capabilities: { sampling: {}, elicitation: { form: {} } } } ); // Handle sampling requests (LLM completions) client.setRequestHandler('sampling/createMessage', async request => { const lastMessage = request.params.messages.at(-1); console.log('Sampling request:', lastMessage); // Send to your LLM and return response return { model: 'gpt-4', role: 'assistant' as const, content: { type: 'text' as const, text: 'Response from the model' } }; }); // Handle elicitation requests (user input) client.setRequestHandler('elicitation/create', async request => { console.log('Server asks:', request.params.message); if (request.params.mode === 'form') { console.log('Schema:', request.params.requestedSchema); // Collect user input based on schema return { action: 'accept', content: { confirm: true } }; } return { action: 'decline' }; }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); ``` --- ## OAuth Authentication Connect to OAuth-protected MCP servers using client credentials or private key JWT authentication. ```typescript import { Client, ClientCredentialsProvider, PrivateKeyJwtProvider, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; // Client credentials authentication const clientCredentialsAuth = new ClientCredentialsProvider({ clientId: 'my-service', clientSecret: 'my-secret' }); const client1 = new Client({ name: 'my-client', version: '1.0.0' }); const transport1 = new StreamableHTTPClientTransport( new URL('http://localhost:3000/mcp'), { authProvider: clientCredentialsAuth } ); await client1.connect(transport1); // Private key JWT authentication const privateKeyAuth = new PrivateKeyJwtProvider({ clientId: 'my-service', privateKey: '-----BEGIN PRIVATE KEY-----\n...', algorithm: 'RS256' }); const client2 = new Client({ name: 'my-client', version: '1.0.0' }); const transport2 = new StreamableHTTPClientTransport( new URL('http://localhost:3000/mcp'), { authProvider: privateKeyAuth } ); await client2.connect(transport2); ``` --- ## Client Middleware Compose fetch middleware pipelines to add headers, handle retries, or log requests. ```typescript import { applyMiddlewares, createMiddleware, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const authMiddleware = createMiddleware(async (next, input, init) => { const headers = new Headers(init?.headers); headers.set('Authorization', 'Bearer my-token'); headers.set('X-Custom-Header', 'my-value'); return next(input, { ...init, headers }); }); const loggingMiddleware = createMiddleware(async (next, input, init) => { console.log('Request:', input); const response = await next(input, init); console.log('Response status:', response.status); return response; }); const transport = new StreamableHTTPClientTransport( new URL('http://localhost:3000/mcp'), { fetch: applyMiddlewares(authMiddleware, loggingMiddleware)(fetch) } ); ``` --- ## Completions - Argument Autocompletion Support argument autocompletion for prompts and resources using the `completable` wrapper. ```typescript import { McpServer, completable } from '@modelcontextprotocol/server'; import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import * as z from 'zod/v4'; // Server: Register prompt with completable arguments const server = new McpServer({ name: 'my-server', version: '1.0.0' }); server.registerPrompt( 'review-code', { title: 'Code Review', description: 'Review code for best practices', argsSchema: z.object({ language: completable( z.string().describe('Programming language'), value => ['typescript', 'javascript', 'python', 'rust', 'go'] .filter(lang => lang.startsWith(value)) ) }) }, ({ language }) => ({ messages: [{ role: 'user' as const, content: { type: 'text' as const, text: `Review this ${language} code for best practices.` } }] }) ); // Client: Request completions const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); const { completion } = await client.complete({ ref: { type: 'ref/prompt', name: 'review-code' }, argument: { name: 'language', value: 'type' } }); console.log(completion.values); // ['typescript'] ``` --- ## DNS Rebinding Protection Protect localhost MCP servers from DNS rebinding attacks using the Express middleware. ```typescript import { createMcpExpressApp } from '@modelcontextprotocol/express'; // Auto-enabled DNS rebinding protection for localhost const app = createMcpExpressApp(); // Also auto-enabled for explicit localhost binding const appLocal = createMcpExpressApp({ host: 'localhost' }); // Custom allowed hosts when binding to all interfaces const appPublic = createMcpExpressApp({ host: '0.0.0.0', allowedHosts: ['localhost', '127.0.0.1', 'myhost.local', 'api.example.com'] }); appPublic.listen(3000); ``` --- ## ResourceLink - Return Resource References Tools can return resource links instead of embedding large content directly. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server'; const server = new McpServer({ name: 'my-server', version: '1.0.0' }); server.registerTool( 'list-files', { title: 'List Files', description: 'Returns files as resource links without embedding content' }, async (): Promise => { const links: ResourceLink[] = [ { type: 'resource_link', uri: 'file:///projects/readme.md', name: 'README', mimeType: 'text/markdown' }, { type: 'resource_link', uri: 'file:///projects/config.json', name: 'Config', mimeType: 'application/json' } ]; return { content: links }; } ); ``` --- ## SSE Fallback for Legacy Servers Support both modern Streamable HTTP and legacy SSE servers with automatic fallback. ```typescript import { Client, StreamableHTTPClientTransport, SSEClientTransport } from '@modelcontextprotocol/client'; async function connectWithFallback(url: string) { const baseUrl = new URL(url); try { // Try modern Streamable HTTP transport first const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport(baseUrl); await client.connect(transport); console.log('Connected via Streamable HTTP'); return { client, transport }; } catch (error) { // Fall back to legacy SSE transport console.log('Falling back to SSE transport'); const client = new Client({ name: 'my-client', version: '1.0.0' }); const transport = new SSEClientTransport(baseUrl); await client.connect(transport); return { client, transport }; } } const { client, transport } = await connectWithFallback('http://localhost:3000/mcp'); ``` --- The MCP TypeScript SDK provides a complete solution for building AI context providers and consumers. Servers use `McpServer` to expose tools (actions the LLM can invoke), resources (read-only data), and prompts (reusable templates). Clients use the `Client` class to discover and interact with these primitives, with automatic schema validation and structured output support. Common integration patterns include: building CLI tools with stdio transport, deploying HTTP servers with Express/Hono middleware for web-based access, implementing OAuth-protected services for multi-tenant environments, and creating AI agents that can call tools and consume context from multiple MCP servers. The SDK's modular architecture allows mixing transports and middleware packages based on deployment requirements, while the experimental tasks API enables long-running operations with status streaming.