diff --git a/CLAUDE.md b/CLAUDE.md index 2cbb850dfc..b825792ba4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,226 +1,16 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This branch is the v1 maintenance line: small, low-risk fixes only; new features and improvements go to `main` first. -## Build & Test Commands +The principles this SDK is built and reviewed by. They are not a checklist: apply them with judgement. -```sh -npm run build # Build ESM and CJS versions -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 -``` +1. **Minimalism** — The SDK should do less, not more: prefer changes that add no API or option unless justified. +2. **Spec is the anchor** — The spec decides; where it leaves a choice open, check what the other official SDKs do before making your own choice. +3. **Strict by default** — Accept what the spec allows, and add no leniency for peers that break it. +4. **Backwards compatible** — New features and changes arrive in backwards compatible ways; when a bug, a spec violation or an unsafe default forces a break, the changeset says what breaks. +5. **Small changes** — Guard scope hard: a smaller change that lands beats a bigger one that doesn't, and small, independent pull requests can land in any order. +6. **The words are true** — Changesets, docs and comments say what the code does. +7. **Test what users see** — Prefer tests that cover the end-to-end usage a user would actually see, where sensible; `test/e2e/requirements.ts` lists those behaviours. +8. **Brief comments** — Keep inline comments to one line and JSDoc to the public API; the reasoning goes in the commit message or PR description. -## Code Style Guidelines - -- **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.ts` suffix -- **Imports**: ES module style, include `.js` extension, group imports logically -- **Formatting**: 2-space indentation, semicolons required, single quotes preferred -- **Testing**: Co-locate tests with source files, use descriptive test names. Use `vi.useFakeTimers()` instead of real `setTimeout`/`await` delays in tests -- **Comments**: JSDoc for public APIs, inline comments for complex logic - -## 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 - -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); -``` +Style belongs to Prettier and ESLint. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ace9542dbf..78193ab1a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,16 @@ We welcome contributions to the Model Context Protocol TypeScript SDK! This docu - Keep changes focused and atomic - Provide a clear description of changes +## Good to know + +Details about this branch that neither the code nor the tools will tell you. + +- **Tests** — Tests live under `test/`, mirroring `src/`; a test file next to its source never runs. +- **Imports** — Relative imports carry the `.js` extension; the compiler accepts them without it, Node does not. +- **Public surface** — Everything under `src/` is reachable by users, because `package.json` exports `./*`. +- **Zod** — User-supplied schemas may be Zod v3 or v4; `src/server/zod-compat.ts` handles both. +- **Both lines** — A fix that also applies to v2 lands on `main` as well. + ## Running Examples - Start the server: `npm run server`