Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Model Context Protocol Typescript SDK
https://github.com/modelcontextprotocol/typescript-sdk
Admin
The MCP TypeScript SDK implements the Model Context Protocol, enabling easy creation of clients and
...
Tokens:
53,418
Snippets:
390
Trust Score:
7.8
Update:
19 hours ago
Context
Skills
Chat
Benchmark
77.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# MCP TypeScript SDK The Model Context Protocol (MCP) TypeScript SDK provides a standardized way for applications to provide context for Large Language Models. It separates the concerns of providing context from the actual LLM interaction, enabling modular AI-powered applications. The SDK runs on Node.js, Bun, and Deno, and includes server libraries for building tools/resources/prompts, client libraries for connecting to MCP servers, and middleware packages for frameworks like Express and Hono. This monorepo publishes split packages: `@modelcontextprotocol/server` for building MCP servers and `@modelcontextprotocol/client` for building MCP clients. Tool and prompt schemas use Standard Schema (Zod v4, Valibot, or ArkType). The SDK supports two transport mechanisms: Streamable HTTP for remote servers accessible over the network, and stdio for local servers spawned as child processes (Claude Desktop, CLI tools). ## Server APIs ### Creating an MCP Server with McpServer The `McpServer` class is the main entry point for building MCP servers. It handles tool registration, resource management, prompts, and connects to transports for communication with clients. ```typescript import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; // Create server instance with metadata const server = new McpServer( { name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} }, instructions: 'Always call list_tables before running queries. Results are limited to 1000 rows.' } ); // Connect to stdio transport for local integrations const transport = new StdioServerTransport(); await server.connect(transport); console.error('Server running on stdio'); ``` ### Registering Tools with registerTool Tools let clients invoke actions on your server. Register tools with `registerTool()` providing an `inputSchema` (Zod) to validate arguments, and optionally an `outputSchema` for structured return values. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import type { CallToolResult } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'calculator', version: '1.0.0' }); // Register a tool with input/output schemas server.registerTool( 'calculate-bmi', { title: 'BMI Calculator', description: 'Calculate Body Mass Index', inputSchema: z.object({ weightKg: z.number().describe('Weight in kilograms'), heightM: z.number().describe('Height in meters') }), outputSchema: z.object({ bmi: z.number() }), annotations: { readOnlyHint: true, idempotentHint: true } }, async ({ weightKg, heightM }): Promise<CallToolResult> => { const bmi = weightKg / (heightM * heightM); return { content: [{ type: 'text', text: `BMI: ${bmi.toFixed(2)}` }], structuredContent: { bmi: Number(bmi.toFixed(2)) } }; } ); // Register a tool with error handling server.registerTool( 'fetch-data', { description: 'Fetch data from a URL', inputSchema: z.object({ url: z.string() }) }, async ({ url }): Promise<CallToolResult> => { try { const res = await fetch(url); if (!res.ok) { return { content: [{ type: 'text', text: `HTTP ${res.status}: ${res.statusText}` }], isError: true }; } return { content: [{ type: 'text', text: await res.text() }] }; } catch (error) { return { content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); ``` ### Registering Resources with registerResource Resources expose read-only data (files, database schemas, configuration) that the host application can retrieve and attach as context for the model. ```typescript import { McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; import type { ReadResourceResult } from '@modelcontextprotocol/server'; const server = new McpServer({ name: 'resource-server', version: '1.0.0' }); // Static resource at a fixed URI server.registerResource( 'config', 'config://app', { title: 'Application Config', description: 'Application configuration data', mimeType: 'application/json' }, async (uri): Promise<ReadResourceResult> => ({ contents: [{ uri: uri.href, text: JSON.stringify({ theme: 'dark', language: 'en' }) }] }) ); // 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 }): Promise<ReadResourceResult> => ({ contents: [{ uri: uri.href, text: JSON.stringify({ userId, name: 'Example User', email: `user${userId}@example.com` }) }] }) ); ``` ### Registering Prompts with registerPrompt Prompts are reusable templates that help structure interactions with models. Use prompts for canned interaction patterns that users invoke explicitly. ```typescript import { McpServer, completable } from '@modelcontextprotocol/server'; import type { GetPromptResult } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'prompt-server', version: '1.0.0' }); // Register a prompt with argument schema server.registerPrompt( 'review-code', { title: 'Code Review', description: 'Review code for best practices and potential issues', argsSchema: z.object({ code: z.string().describe('The code to review'), // completable() provides autocompletion suggestions language: completable( z.string().describe('Programming language'), value => ['typescript', 'javascript', 'python', 'rust', 'go'] .filter(lang => lang.startsWith(value)) ) }) }, ({ code, language }): GetPromptResult => ({ messages: [ { role: 'user' as const, content: { type: 'text' as const, text: `Please review this ${language} code for best practices:\n\n\`\`\`${language}\n${code}\n\`\`\`` } } ] }) ); ``` ### Streamable HTTP Server Transport For remote HTTP servers, use `NodeStreamableHTTPServerTransport` with Express or Hono middleware for DNS rebinding protection. ```typescript import { randomUUID } from 'node:crypto'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import { McpServer, isInitializeRequest } from '@modelcontextprotocol/server'; import cors from 'cors'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'http-server', version: '1.0.0' }); server.registerTool('greet', { description: 'Greet a user', inputSchema: z.object({ name: z.string() }) }, async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] })); // Create Express app with DNS rebinding protection const app = createMcpExpressApp(); app.use(cors({ exposedHeaders: ['WWW-Authenticate', 'Mcp-Session-Id', 'Mcp-Protocol-Version'], origin: '*' })); // Store transports by session ID const transports: Map<string, NodeStreamableHTTPServerTransport> = new Map(); app.post('/mcp', async (req, res) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (sessionId && transports.has(sessionId)) { // Reuse existing transport await transports.get(sessionId)!.handleRequest(req, res, req.body); } else if (!sessionId && isInitializeRequest(req.body)) { // New session initialization const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: sid => transports.set(sid, transport) }); transport.onclose = () => { if (transport.sessionId) transports.delete(transport.sessionId); }; await server.connect(transport); await transport.handleRequest(req, res, req.body); } else { res.status(400).json({ error: 'Invalid request' }); } }); app.listen(3000, () => console.log('MCP server listening on port 3000')); ``` ### Server Logging and Progress Notifications Servers can send logging messages and progress updates to connected clients during tool execution. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import type { CallToolResult, ServerContext } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer( { name: 'logging-server', version: '1.0.0' }, { capabilities: { logging: {} } } ); server.registerTool( 'process-files', { description: 'Process multiple files with progress updates', inputSchema: z.object({ files: z.array(z.string()) }) }, async ({ files }, ctx: ServerContext): Promise<CallToolResult> => { const progressToken = ctx.mcpReq._meta?.progressToken; for (let i = 0; i < files.length; i++) { // Send log message await ctx.mcpReq.log('info', `Processing file: ${files[i]}`); // Simulate processing await new Promise(resolve => setTimeout(resolve, 500)); // Send progress notification if client requested it if (progressToken !== undefined) { await ctx.mcpReq.notify({ method: 'notifications/progress', params: { progressToken, progress: i + 1, total: files.length, message: `Processed ${files[i]}` } }); } } return { content: [{ type: 'text', text: `Processed ${files.length} files successfully` }] }; } ); ``` ### Server-Initiated Sampling and Elicitation Servers can request LLM completions (sampling) or user input (elicitation) from connected clients during tool execution. ```typescript import { McpServer } from '@modelcontextprotocol/server'; import type { CallToolResult, ServerContext } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; const server = new McpServer({ name: 'interactive-server', version: '1.0.0' }); // Tool that uses sampling to get LLM completion server.registerTool( 'summarize', { description: 'Summarize text using the client LLM', inputSchema: z.object({ text: z.string() }) }, async ({ text }, ctx: ServerContext): Promise<CallToolResult> => { const response = await ctx.mcpReq.requestSampling({ messages: [{ role: 'user', content: { type: 'text', text: `Please summarize:\n\n${text}` } }], maxTokens: 500 }); return { content: [{ type: 'text', text: `Summary: ${JSON.stringify(response.content)}` }] }; } ); // Tool that elicits user input via form server.registerTool( 'collect-feedback', { description: 'Collect user feedback via a form', inputSchema: z.object({}) }, async (_args, ctx: ServerContext): Promise<CallToolResult> => { const result = await ctx.mcpReq.elicitInput({ mode: 'form', message: 'Please share your feedback:', requestedSchema: { type: 'object', properties: { rating: { type: 'integer', title: 'Rating (1-5)', minimum: 1, maximum: 5 }, comment: { type: 'string', title: 'Comment' } }, required: ['rating'] } }); if (result.action === 'accept') { return { content: [{ type: 'text', text: `Thanks for your feedback! ${JSON.stringify(result.content)}` }] }; } return { content: [{ type: 'text', text: 'Feedback declined.' }] }; } ); ``` ## Client APIs ### Creating an MCP Client The `Client` class connects to MCP servers, discovers available tools/resources/prompts, and invokes them. It supports various transports and authentication methods. ```typescript import { Client, StreamableHTTPClientTransport, StdioClientTransport } from '@modelcontextprotocol/client'; // Create client for HTTP server const client = new Client( { name: 'my-client', version: '1.0.0' }, { capabilities: { sampling: {}, elicitation: { form: {} } } } ); // Connect via Streamable HTTP const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); await client.connect(transport); // Or connect via stdio for local servers const stdioTransport = new StdioClientTransport({ command: 'node', args: ['server.js'] }); await client.connect(stdioTransport); // Get server instructions for system prompt const instructions = client.getInstructions(); console.log('Server instructions:', instructions); // Disconnect gracefully await transport.terminateSession(); await client.close(); ``` ### Listing and Calling Tools Discover available tools and invoke them with arguments. Handle both tool-level errors (isError) and protocol-level errors (exceptions). ```typescript import { Client, StreamableHTTPClientTransport, ProtocolError, SdkError, SdkErrorCode } from '@modelcontextprotocol/client'; import type { Tool } from '@modelcontextprotocol/client'; const client = new Client({ name: 'tool-client', version: '1.0.0' }); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); // List all tools (handle pagination) const allTools: Tool[] = []; let cursor: string | undefined; do { const { tools, nextCursor } = await client.listTools({ cursor }); allTools.push(...tools); cursor = nextCursor; } while (cursor); console.log('Available tools:', allTools.map(t => t.name)); // Call a tool with error handling try { const result = await client.callTool({ name: 'calculate-bmi', arguments: { weightKg: 70, heightM: 1.75 } }); // Check for tool-level error if (result.isError) { console.error('Tool error:', result.content); return; } // Access text content for (const item of result.content) { if (item.type === 'text') console.log(item.text); } // Access structured output if (result.structuredContent) { console.log('BMI value:', result.structuredContent.bmi); } } catch (error) { // Handle protocol-level errors if (error instanceof ProtocolError) { console.error(`Protocol error ${error.code}: ${error.message}`); } else if (error instanceof SdkError) { if (error.code === SdkErrorCode.RequestTimeout) { console.error('Request timed out'); } else if (error.code === SdkErrorCode.ConnectionClosed) { console.error('Connection closed'); } } } ``` ### Reading Resources Discover and read server-provided resources. Subscribe to resource changes for real-time updates. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import type { Resource } from '@modelcontextprotocol/client'; const client = new Client({ name: 'resource-client', version: '1.0.0' }); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); // List all resources const allResources: Resource[] = []; let cursor: string | undefined; do { const { resources, nextCursor } = await client.listResources({ cursor }); allResources.push(...resources); cursor = nextCursor; } while (cursor); console.log('Available resources:', allResources.map(r => `${r.name}: ${r.uri}`)); // Read a specific resource const { contents } = await client.readResource({ uri: 'config://app' }); for (const item of contents) { console.log(`URI: ${item.uri}`); if ('text' in item) console.log(`Content: ${item.text}`); } // Subscribe to resource changes await client.subscribeResource({ uri: 'config://app' }); client.setNotificationHandler('notifications/resources/updated', async notification => { if (notification.params.uri === 'config://app') { const { contents } = await client.readResource({ uri: 'config://app' }); console.log('Config updated:', contents); } }); // Unsubscribe when done await client.unsubscribeResource({ uri: 'config://app' }); ``` ### Getting Prompts List available prompts and retrieve them with arguments for structured LLM interactions. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import type { Prompt } from '@modelcontextprotocol/client'; const client = new Client({ name: 'prompt-client', version: '1.0.0' }); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); // List all prompts const allPrompts: Prompt[] = []; let cursor: string | undefined; do { const { prompts, nextCursor } = await client.listPrompts({ cursor }); allPrompts.push(...prompts); cursor = nextCursor; } while (cursor); console.log('Available prompts:', allPrompts.map(p => p.name)); // Get a prompt with arguments const { messages } = await client.getPrompt({ name: 'review-code', arguments: { code: 'function add(a, b) { return a + b; }', language: 'javascript' } }); // Use messages with your LLM for (const msg of messages) { console.log(`${msg.role}: ${msg.content.type === 'text' ? msg.content.text : '[non-text]'}`); } // Get completion suggestions for prompt arguments const { completion } = await client.complete({ ref: { type: 'ref/prompt', name: 'review-code' }, argument: { name: 'language', value: 'type' } }); console.log('Suggestions:', completion.values); // ['typescript'] ``` ### Handling Server-Initiated Requests Clients can handle sampling and elicitation requests from servers during tool execution. ```typescript import { Client, StreamableHTTPClientTransport, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/client'; const client = new Client( { name: 'interactive-client', version: '1.0.0' }, { capabilities: { sampling: {}, elicitation: { form: {} }, roots: { listChanged: true } } } ); // Handle sampling requests (server asks for LLM completion) client.setRequestHandler('sampling/createMessage', async request => { const lastMessage = request.params.messages.at(-1); console.log('Sampling request:', lastMessage?.content); // In production, send to your LLM return { model: 'gpt-4', role: 'assistant' as const, content: { type: 'text' as const, text: 'Response from the model' } }; }); // Handle elicitation requests (server asks for user input) client.setRequestHandler('elicitation/create', async request => { console.log('Elicitation request:', request.params.message); if (request.params.mode === 'form') { console.log('Schema:', request.params.requestedSchema); // In production, show form to user and collect input return { action: 'accept', content: { rating: 5, comment: 'Great!' } }; } return { action: 'decline' }; }); // Handle roots list request (server asks for workspace directories) client.setRequestHandler('roots/list', async () => ({ roots: [ { uri: 'file:///home/user/projects/my-app', name: 'My App' }, { uri: 'file:///home/user/data', name: 'Data' } ] })); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); ``` ### Client Authentication The SDK supports various authentication methods including bearer tokens, client credentials, and full OAuth flows. ```typescript import { Client, StreamableHTTPClientTransport, ClientCredentialsProvider, PrivateKeyJwtProvider, CrossAppAccessProvider, discoverAndRequestJwtAuthGrant } from '@modelcontextprotocol/client'; import type { AuthProvider } from '@modelcontextprotocol/client'; // Simple bearer token provider const tokenProvider: AuthProvider = { token: async () => process.env.API_TOKEN || '' }; // Client credentials OAuth flow const clientCredentials = new ClientCredentialsProvider({ clientId: 'my-service', clientSecret: 'my-secret' }); // Private key JWT authentication const privateKeyJwt = new PrivateKeyJwtProvider({ clientId: 'my-service', privateKey: process.env.PRIVATE_KEY!, algorithm: 'RS256' }); // Cross-App Access (Enterprise Managed Authorization) const crossAppAccess = new CrossAppAccessProvider({ assertion: async ctx => { const result = await discoverAndRequestJwtAuthGrant({ idpUrl: 'https://idp.example.com', audience: ctx.authorizationServerUrl, resource: ctx.resourceUrl, idToken: await getIdToken(), clientId: 'my-idp-client', clientSecret: 'my-idp-secret', scope: ctx.scope, fetchFn: ctx.fetchFn }); return result.jwtAuthGrant; }, clientId: 'my-mcp-client', clientSecret: 'my-mcp-secret' }); // Use any auth provider with transport const client = new Client({ name: 'auth-client', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport( new URL('http://localhost:3000/mcp'), { authProvider: clientCredentials } ); await client.connect(transport); ``` ### Progress Tracking and Timeouts Track progress during long-running tool calls and configure request timeouts. ```typescript import { Client, StreamableHTTPClientTransport, SdkError, SdkErrorCode } from '@modelcontextprotocol/client'; const client = new Client({ name: 'progress-client', version: '1.0.0' }); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); try { const result = await client.callTool( { name: 'process-files', arguments: { files: ['a.txt', 'b.txt', 'c.txt'] } }, { // Track progress notifications onprogress: ({ progress, total, message }) => { console.log(`Progress: ${progress}/${total ?? '?'} - ${message ?? ''}`); }, // Reset timeout on each progress update resetTimeoutOnProgress: true, // Absolute maximum timeout (10 minutes) maxTotalTimeout: 600_000, // Per-operation timeout (2 minutes, default is 60s) timeout: 120_000 } ); console.log('Result:', result.content); } catch (error) { if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { console.error('Request timed out'); } } ``` ### Automatic List Change Tracking The `listChanged` option keeps a local cache of tools, prompts, or resources in sync with the server automatically. ```typescript import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; const client = new Client( { name: 'auto-sync-client', version: '1.0.0' }, { listChanged: { tools: { debounceMs: 300, // Default debounce onChanged: (error, tools) => { if (error) { console.error('Failed to refresh tools:', error); return; } console.log('Tools updated:', tools?.map(t => t.name)); } }, prompts: { onChanged: (error, prompts) => { if (!error) console.log('Prompts updated:', prompts?.length); } }, resources: { onChanged: (error, resources) => { if (!error) console.log('Resources updated:', resources?.length); } } } } ); await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'))); // Manual notification handlers for other notification types client.setNotificationHandler('notifications/message', notification => { console.log(`[${notification.params.level}]`, notification.params.data); }); // Set minimum log level await client.setLoggingLevel('warning'); ``` ## Summary The MCP TypeScript SDK is designed for building AI applications that need to expose tools, resources, and prompts to LLMs in a standardized way. Common use cases include: building MCP servers that expose domain-specific tools (databases, APIs, file systems) for LLM consumption; creating clients that connect to MCP servers and integrate their capabilities into AI workflows; implementing authentication and authorization for secure MCP deployments; and building interactive AI applications with sampling (LLM completions) and elicitation (user input collection). Integration patterns typically involve creating an McpServer instance, registering tools/resources/prompts with Zod schemas for input validation, connecting via stdio transport for local integrations (Claude Desktop, CLI tools) or Streamable HTTP for remote deployments. Clients connect using the appropriate transport, discover available capabilities via list methods, and invoke tools while handling both synchronous results and asynchronous notifications. The SDK supports advanced patterns like progress tracking, resumability with event stores, session management, and server-initiated requests for bidirectional communication.