diff --git a/CLAUDE.md b/CLAUDE.md index 272c4d990..6e768e559 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,13 +1,18 @@ -# MCP TypeScript SDK Guide +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build & Test Commands ```sh npm run build # Build ESM and CJS versions -npm run lint # Run ESLint -npm test # Run all tests -npx jest path/to/file.test.ts # Run specific test file -npx jest -t "test name" # Run tests matching pattern +npm run lint # Run ESLint and Prettier check +npm run lint:fix # Auto-fix lint and formatting issues +npm test # Run all tests (vitest) +npm run test:watch # Run tests in watch mode +npx vitest path/to/file.test.ts # Run specific test file +npx vitest -t "test name" # Run tests matching pattern +npm run typecheck # Type-check without emitting ``` ## Code Style Guidelines @@ -16,13 +21,206 @@ npx jest -t "test name" # Run tests matching pattern - **Naming**: PascalCase for classes/types, camelCase for functions/variables - **Files**: Lowercase with hyphens, test files with `.test.ts` suffix - **Imports**: ES module style, include `.js` extension, group imports logically -- **Error Handling**: Use TypeScript's strict mode, explicit error checking in tests - **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 -## Project Structure +## Architecture Overview + +### Core Layers + +The SDK is organized into three main layers: + +1. **Types Layer** (`src/types.ts`) - Protocol types generated from the MCP specification. All JSON-RPC message types, schemas, and protocol constants are defined here using Zod v4. + +2. **Protocol Layer** (`src/shared/protocol.ts`) - The abstract `Protocol` class that handles JSON-RPC message routing, request/response correlation, capability negotiation, and transport management. Both `Client` and `Server` extend this class. + +3. **High-Level APIs**: + - `Client` (`src/client/index.ts`) - Low-level client extending Protocol with typed methods for all MCP operations + - `Server` (`src/server/index.ts`) - Low-level server extending Protocol with request handler registration + - `McpServer` (`src/server/mcp.ts`) - High-level server API with simplified resource/tool/prompt registration + +### Transport System + +Transports (`src/shared/transport.ts`) provide the communication layer: + +- **Streamable HTTP** (`src/server/streamableHttp.ts`, `src/client/streamableHttp.ts`) - Recommended transport for remote servers, supports SSE for streaming +- **SSE** (`src/server/sse.ts`, `src/client/sse.ts`) - Legacy HTTP+SSE transport for backwards compatibility +- **stdio** (`src/server/stdio.ts`, `src/client/stdio.ts`) - For local process-spawned integrations + +### Server-Side Features + +- **Tools/Resources/Prompts**: Registered via `McpServer.tool()`, `.resource()`, `.prompt()` methods +- **OAuth/Auth**: Full OAuth 2.0 server implementation in `src/server/auth/` +- **Completions**: Auto-completion support via `src/server/completable.ts` + +### Client-Side Features + +- **Auth**: OAuth client support in `src/client/auth.ts` and `src/client/auth-extensions.ts` +- **Middleware**: Request middleware in `src/client/middleware.ts` +- **Sampling**: Clients can handle `sampling/createMessage` requests from servers (LLM completions) +- **Elicitation**: Clients can handle `elicitation/create` requests for user input (form or URL mode) +- **Roots**: Clients can expose filesystem roots to servers via `roots/list` + +### Experimental Features + +Located in `src/experimental/`: + +- **Tasks**: Long-running task support with polling/resumption (`src/experimental/tasks/`) + +### Zod Compatibility + +The SDK uses `zod/v4` internally but supports both v3 and v4 APIs. Compatibility utilities: + +- `src/server/zod-compat.ts` - Schema parsing helpers that work across versions +- `src/server/zod-json-schema-compat.ts` - Converts Zod schemas to JSON Schema + +### Validation + +Pluggable JSON Schema validation (`src/validation/`): + +- `ajv-provider.ts` - Default Ajv-based validator +- `cfworker-provider.ts` - Cloudflare Workers-compatible alternative + +### Examples + +Runnable examples in `src/examples/`: + +- `server/` - Various server configurations (stateful, stateless, OAuth, etc.) +- `client/` - Client examples (basic, OAuth, parallel calls, etc.) +- `shared/` - Shared utilities like in-memory event store + +## Message Flow (Bidirectional Protocol) + +MCP is bidirectional: both client and server can send requests. Understanding this flow is essential when implementing new request types. + +### Class Hierarchy + +``` +Protocol (abstract base) +├── Client (src/client/index.ts) - can send requests TO server, handle requests FROM server +└── Server (src/server/index.ts) - can send requests TO client, handle requests FROM client + └── McpServer (src/server/mcp.ts) - high-level wrapper around Server +``` + +### Outbound Flow: Sending Requests -- `/src`: Source code with client, server, and shared modules -- Tests alongside source files with `.test.ts` suffix -- Node.js >= 18 required +When code calls `client.callTool()` or `server.createMessage()`: + +1. **High-level method** (e.g., `Client.callTool()`) calls `this.request()` +2. **`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 +3. **Transport** serializes and sends over wire (HTTP, stdio, etc.) +4. **`Protocol._onresponse()`** resolves the promise when response arrives + +### Inbound Flow: Handling Requests + +When a request arrives from the remote side: + +1. **Transport** receives message, calls `transport.onmessage()` +2. **`Protocol.connect()`** routes to `_onrequest()`, `_onresponse()`, or `_onnotification()` +3. **`Protocol._onrequest()`**: + - Looks up handler in `_requestHandlers` map (keyed by method name) + - Creates `RequestHandlerExtra` with `signal`, `sessionId`, `sendNotification`, `sendRequest` + - Invokes handler, sends JSON-RPC response back via transport +4. **Handler** was registered via `setRequestHandler(Schema, handler)` + +### Handler Registration + +```typescript +// In Client (for server→client requests like sampling, elicitation) +client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => { + // Handle sampling request from server + return { role: "assistant", content: {...}, model: "..." }; +}); + +// In Server (for client→server requests like tools/call) +server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { + // Handle tool call from client + return { content: [...] }; +}); +``` + +### Request Handler Extra + +The `extra` parameter in handlers (`RequestHandlerExtra`) provides: + +- `signal`: AbortSignal for cancellation +- `sessionId`: Transport session identifier +- `authInfo`: Validated auth token info (if authenticated) +- `requestId`: JSON-RPC message ID +- `sendNotification(notification)`: Send related notification back +- `sendRequest(request, schema)`: Send related request (for bidirectional flows) +- `taskStore`: Task storage interface (if tasks enabled) + +### Capability Checking + +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 + +### Adding a New Request Type + +1. **Define schema** in `src/types.ts` (request params, result schema) +2. **Add capability** to `ClientCapabilities` or `ServerCapabilities` in types +3. **Implement sender** method in Client or Server class +4. **Add capability check** in the appropriate `assertCapabilityForMethod()` +5. **Register handler** on the receiving side with `setRequestHandler()` +6. **For McpServer**: Add high-level wrapper method if needed + +### Server-Initiated Requests (Sampling, Elicitation) + +Server can request actions from client (requires client capability): + +```typescript +// Server sends sampling request to client +const result = await server.createMessage({ + messages: [...], + maxTokens: 100 +}); + +// Client must have registered handler: +client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => { + // Client-side LLM call + return { role: "assistant", content: {...} }; +}); +``` + +## Key Patterns + +### Request Handler Registration (Low-Level Server) + +```typescript +server.setRequestHandler(SomeRequestSchema, async (request, extra) => { + // extra contains sessionId, authInfo, sendNotification, etc. + return { + /* result */ + }; +}); +``` + +### Tool Registration (High-Level McpServer) + +```typescript +mcpServer.tool('tool-name', { param: z.string() }, async ({ param }, extra) => { + return { content: [{ type: 'text', text: 'result' }] }; +}); +``` + +### Transport Connection + +```typescript +// Server +const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() }); +await server.connect(transport); + +// Client +const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); +await client.connect(transport); +```