This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pnpm install # Install all workspace dependencies
pnpm build:all # Build all packages
pnpm lint:all # Run ESLint + Prettier checks across all packages
pnpm lint:fix:all # Auto-fix lint and formatting issues across all packages
pnpm typecheck:all # Type-check all packages
pnpm test:all # Run all tests (vitest) across all packages
pnpm check:all # typecheck + lint across all packages
# Run a single package script (examples)
# Run a single package script from the repo root with pnpm filter
pnpm --filter @modelcontextprotocol/core test # vitest run (core)
pnpm --filter @modelcontextprotocol/core test:watch # vitest (watch)
pnpm --filter @modelcontextprotocol/core test -- path/to/file.test.ts
pnpm --filter @modelcontextprotocol/core test -- -t "test name"When making breaking changes, document them in both:
docs/migration.md— human-readable guide with before/after code examplesdocs/migration-SKILL.md— LLM-optimized mapping tables for mechanical migration
Include what changed, why, and how to migrate. Search for related sections and group related changes together rather than adding new standalone sections.
- TypeScript: Strict type checking, ES modules, explicit return types
- Naming: PascalCase for classes/types, camelCase for functions/variables
- Files: Lowercase with hyphens, test files with
.test.tssuffix - Imports: ES module style, include
.jsextension, group imports logically - Formatting: 2-space indentation, semicolons required, single quotes preferred
- Testing: Co-locate tests with source files, use descriptive test names
- Comments: JSDoc for public APIs, inline comments for complex logic
JSDoc @example tags should pull type-checked code from companion .examples.ts files (e.g., client.ts → client.examples.ts). Use ```ts source="./file.examples.ts#regionName" fences referencing //#region regionName blocks; region names follow exportedName_variant or ClassName_methodName_variant pattern (e.g., applyMiddlewares_basicUsage, Client_connect_basicUsage). For whole-file inclusion (any file type), omit the #regionName.
Run pnpm sync:snippets to sync example content into JSDoc comments and markdown files.
The SDK is organized into three main layers:
-
Types Layer (
packages/core/src/types/types.ts) - Protocol types generated from the MCP specification. All JSON-RPC message types, schemas, and protocol constants are defined here using Zod v4. -
Protocol Layer (
packages/core/src/shared/protocol.ts) - The abstractProtocolclass that handles JSON-RPC message routing, request/response correlation, capability negotiation, and transport management. BothClientandServerextend this class. -
High-Level APIs:
Client(packages/client/src/client/client.ts) - Client implementation extending Protocol with typed methods for MCP operationsServer(packages/server/src/server/server.ts) - Server implementation extending Protocol with request handler registrationMcpServer(packages/server/src/server/mcp.ts) - High-level server API with simplified resource/tool/prompt registration
Transports (packages/core/src/shared/transport.ts) provide the communication layer:
- Streamable HTTP (
packages/server/src/server/streamableHttp.ts,packages/client/src/client/streamableHttp.ts) - Recommended transport for remote servers, supports SSE for streaming - SSE (
packages/server/src/server/sse.ts,packages/client/src/client/sse.ts) - Legacy HTTP+SSE transport for backwards compatibility - stdio (
packages/server/src/server/stdio.ts,packages/client/src/client/stdio.ts) - For local process-spawned integrations
- Tools/Resources/Prompts: Registered via
McpServer.tool(),.resource(),.prompt()methods - OAuth/Auth: Full OAuth 2.0 server implementation in
packages/server/src/server/auth/ - Completions: Auto-completion support via
packages/server/src/server/completable.ts
- Auth: OAuth client support in
packages/client/src/client/auth.tsandpackages/client/src/client/auth-extensions.ts - Client middleware: Request middleware in
packages/client/src/client/middleware.ts(unrelated to the framework adapter packages below) - Sampling: Clients can handle
sampling/createMessagerequests from servers (LLM completions) - Elicitation: Clients can handle
elicitation/createrequests for user input (form or URL mode) - Roots: Clients can expose filesystem roots to servers via
roots/list
The repo also ships “middleware” packages under packages/middleware/ (e.g. @modelcontextprotocol/express, @modelcontextprotocol/hono, @modelcontextprotocol/node). These are thin integration layers for specific frameworks/runtimes and should not add new MCP functionality.
Located in packages/*/src/experimental/:
- Tasks: Long-running task support with polling/resumption (
packages/core/src/experimental/tasks/)
The SDK uses zod/v4 internally. Schema utilities live in:
packages/core/src/util/schema.ts- AnySchema alias and helpers for inspecting Zod objects
Pluggable JSON Schema validation (packages/core/src/validators/):
ajvProvider.ts- Default Ajv-based validatorcfWorkerProvider.ts- Cloudflare Workers-compatible alternative
Runnable examples in examples/:
examples/server/src/- Various server configurations (stateful, stateless, OAuth, etc.)examples/client/src/- Client examples (basic, OAuth, parallel calls, etc.)examples/shared/src/- Shared utilities (OAuth demo provider, etc.)
MCP is bidirectional: both client and server can send requests. Understanding this flow is essential when implementing new request types.
Protocol (abstract base)
├── Client (packages/client/src/client/client.ts) - can send requests TO server, handle requests FROM server
└── Server (packages/server/src/server/server.ts) - can send requests TO client, handle requests FROM client
└── McpServer (packages/server/src/server/mcp.ts) - high-level wrapper around Server
When code calls client.callTool() or server.createMessage():
- High-level method (e.g.,
Client.callTool()) callsthis.request() Protocol.request():- Assigns unique message ID
- Checks capabilities via
assertCapabilityForMethod()(abstract, implemented by Client/Server) - Creates response handler promise
- Calls
transport.send()with JSON-RPC request - Waits for response handler to resolve
- Transport serializes and sends over wire (HTTP, stdio, etc.)
Protocol._onresponse()resolves the promise when response arrives
When a request arrives from the remote side:
- Transport receives message, calls
transport.onmessage() Protocol.connect()routes to_onrequest(),_onresponse(), or_onnotification()Protocol._onrequest():- Looks up handler in
_requestHandlersmap (keyed by method name) - Creates
BaseContextwithsignal,sessionId,sendNotification,sendRequest, etc. - Calls
buildContext()to let subclasses enrich the context (e.g., Server addsrequestInfo) - Invokes handler, sends JSON-RPC response back via transport
- Looks up handler in
- Handler was registered via
setRequestHandler('method', handler)
// In Client (for server→client requests like sampling, elicitation)
client.setRequestHandler('sampling/createMessage', async (request, ctx) => {
// Handle sampling request from server
return { role: "assistant", content: {...}, model: "..." };
});
// In Server (for client→server requests like tools/call)
server.setRequestHandler('tools/call', async (request, ctx) => {
// Handle tool call from client
return { content: [...] };
});The ctx parameter in handlers provides a structured context:
BaseContext (common to both Server and Client), fields organized into nested groups:
sessionId?: Transport session identifiermcpReq: Request-level concernsid: JSON-RPC message IDmethod: Request method string (e.g., 'tools/call')_meta?: Request metadatasignal: AbortSignal for cancellationsend(request, schema, options?): Send related request (for bidirectional flows)notify(notification): Send related notification back
http?: HTTP transport info (undefined for stdio)authInfo?: Validated auth token info
task?: Task context ({ id?, store, requestedTtl? }) when task storage is configured
ServerContext extends BaseContext.mcpReq and BaseContext.http? via type intersection:
mcpReqadds:log(level, data, logger?),elicitInput(params, options?),requestSampling(params, options?)http?adds:req?(HTTP request info),closeSSE?,closeStandaloneSSE?
ClientContext is currently identical to BaseContext.
Both sides declare capabilities during initialization. The SDK enforces these:
- Client→Server:
Client.assertCapabilityForMethod()checks_serverCapabilities - Server→Client:
Server.assertCapabilityForMethod()checks_clientCapabilities - Handler registration:
assertRequestHandlerCapability()validates local capabilities
- Define schema in
src/types.ts(request params, result schema) - Add capability to
ClientCapabilitiesorServerCapabilitiesin types - Implement sender method in Client or Server class
- Add capability check in the appropriate
assertCapabilityForMethod() - Register handler on the receiving side with
setRequestHandler() - For McpServer: Add high-level wrapper method if needed
Server can request actions from client (requires client capability):
// Server sends sampling request to client
const result = await server.createMessage({
messages: [...],
maxTokens: 100
});
// Client must have registered handler:
client.setRequestHandler('sampling/createMessage', async (request, extra) => {
// Client-side LLM call
return { role: "assistant", content: {...} };
});server.setRequestHandler('tools/call', async (request, extra) => {
// extra contains sessionId, authInfo, sendNotification, etc.
return {
/* result */
};
});mcpServer.tool('tool-name', { param: z.string() }, async ({ param }, extra) => {
return { content: [{ type: 'text', text: 'result' }] };
});// Server
// (Node.js IncomingMessage/ServerResponse wrapper; exported by @modelcontextprotocol/node)
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
await server.connect(transport);
// Client
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
await client.connect(transport);