Skip to content

Commit 85262bd

Browse files
committed
chore: refresh CLAUDE.md
Updates CLAUDE.md according to the latest code, as it hasn't been updated in a few eons. Updated via /init on Claude Opus 4.5, with some follow-up revisions to cover the message flow in more detail, as that's a frequent point of errors.
1 parent a6ee2cb commit 85262bd

File tree

1 file changed

+198
-10
lines changed

1 file changed

+198
-10
lines changed

CLAUDE.md

Lines changed: 198 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
# MCP TypeScript SDK Guide
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
24

35
## Build & Test Commands
46

57
```sh
68
npm run build # Build ESM and CJS versions
7-
npm run lint # Run ESLint
8-
npm test # Run all tests
9-
npx jest path/to/file.test.ts # Run specific test file
10-
npx jest -t "test name" # Run tests matching pattern
9+
npm run lint # Run ESLint and Prettier check
10+
npm run lint:fix # Auto-fix lint and formatting issues
11+
npm test # Run all tests (vitest)
12+
npm run test:watch # Run tests in watch mode
13+
npx vitest path/to/file.test.ts # Run specific test file
14+
npx vitest -t "test name" # Run tests matching pattern
15+
npm run typecheck # Type-check without emitting
1116
```
1217

1318
## Code Style Guidelines
@@ -16,13 +21,196 @@ npx jest -t "test name" # Run tests matching pattern
1621
- **Naming**: PascalCase for classes/types, camelCase for functions/variables
1722
- **Files**: Lowercase with hyphens, test files with `.test.ts` suffix
1823
- **Imports**: ES module style, include `.js` extension, group imports logically
19-
- **Error Handling**: Use TypeScript's strict mode, explicit error checking in tests
2024
- **Formatting**: 2-space indentation, semicolons required, single quotes preferred
2125
- **Testing**: Co-locate tests with source files, use descriptive test names
2226
- **Comments**: JSDoc for public APIs, inline comments for complex logic
2327

24-
## Project Structure
28+
## Architecture Overview
29+
30+
### Core Layers
31+
32+
The SDK is organized into three main layers:
33+
34+
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.
35+
36+
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.
37+
38+
3. **High-Level APIs**:
39+
- `Client` (`src/client/index.ts`) - Low-level client extending Protocol with typed methods for all MCP operations
40+
- `Server` (`src/server/index.ts`) - Low-level server extending Protocol with request handler registration
41+
- `McpServer` (`src/server/mcp.ts`) - High-level server API with simplified resource/tool/prompt registration
42+
43+
### Transport System
44+
45+
Transports (`src/shared/transport.ts`) provide the communication layer:
46+
47+
- **Streamable HTTP** (`src/server/streamableHttp.ts`, `src/client/streamableHttp.ts`) - Recommended transport for remote servers, supports SSE for streaming
48+
- **SSE** (`src/server/sse.ts`, `src/client/sse.ts`) - Legacy HTTP+SSE transport for backwards compatibility
49+
- **stdio** (`src/server/stdio.ts`, `src/client/stdio.ts`) - For local process-spawned integrations
50+
51+
### Server-Side Features
52+
53+
- **Tools/Resources/Prompts**: Registered via `McpServer.tool()`, `.resource()`, `.prompt()` methods
54+
- **OAuth/Auth**: Full OAuth 2.0 server implementation in `src/server/auth/`
55+
- **Completions**: Auto-completion support via `src/server/completable.ts`
56+
57+
### Client-Side Features
58+
59+
- **Auth**: OAuth client support in `src/client/auth.ts` and `src/client/auth-extensions.ts`
60+
- **Middleware**: Request middleware in `src/client/middleware.ts`
61+
- **Sampling**: Clients can handle `sampling/createMessage` requests from servers (LLM completions)
62+
- **Elicitation**: Clients can handle `elicitation/create` requests for user input (form or URL mode)
63+
- **Roots**: Clients can expose filesystem roots to servers via `roots/list`
64+
65+
### Experimental Features
66+
67+
Located in `src/experimental/`:
68+
- **Tasks**: Long-running task support with polling/resumption (`src/experimental/tasks/`)
69+
70+
### Zod Compatibility
71+
72+
The SDK uses `zod/v4` internally but supports both v3 and v4 APIs. Compatibility utilities:
73+
- `src/server/zod-compat.ts` - Schema parsing helpers that work across versions
74+
- `src/server/zod-json-schema-compat.ts` - Converts Zod schemas to JSON Schema
75+
76+
### Validation
77+
78+
Pluggable JSON Schema validation (`src/validation/`):
79+
- `ajv-provider.ts` - Default Ajv-based validator
80+
- `cfworker-provider.ts` - Cloudflare Workers-compatible alternative
81+
82+
### Examples
83+
84+
Runnable examples in `src/examples/`:
85+
- `server/` - Various server configurations (stateful, stateless, OAuth, etc.)
86+
- `client/` - Client examples (basic, OAuth, parallel calls, etc.)
87+
- `shared/` - Shared utilities like in-memory event store
88+
89+
## Message Flow (Bidirectional Protocol)
90+
91+
MCP is bidirectional: both client and server can send requests. Understanding this flow is essential when implementing new request types.
92+
93+
### Class Hierarchy
94+
95+
```
96+
Protocol (abstract base)
97+
├── Client (src/client/index.ts) - can send requests TO server, handle requests FROM server
98+
└── Server (src/server/index.ts) - can send requests TO client, handle requests FROM client
99+
└── McpServer (src/server/mcp.ts) - high-level wrapper around Server
100+
```
101+
102+
### Outbound Flow: Sending Requests
25103

26-
- `/src`: Source code with client, server, and shared modules
27-
- Tests alongside source files with `.test.ts` suffix
28-
- Node.js >= 18 required
104+
When code calls `client.callTool()` or `server.createMessage()`:
105+
106+
1. **High-level method** (e.g., `Client.callTool()`) calls `this.request()`
107+
2. **`Protocol.request()`**:
108+
- Assigns unique message ID
109+
- Checks capabilities via `assertCapabilityForMethod()` (abstract, implemented by Client/Server)
110+
- Creates response handler promise
111+
- Calls `transport.send()` with JSON-RPC request
112+
- Waits for response handler to resolve
113+
3. **Transport** serializes and sends over wire (HTTP, stdio, etc.)
114+
4. **`Protocol._onresponse()`** resolves the promise when response arrives
115+
116+
### Inbound Flow: Handling Requests
117+
118+
When a request arrives from the remote side:
119+
120+
1. **Transport** receives message, calls `transport.onmessage()`
121+
2. **`Protocol.connect()`** routes to `_onrequest()`, `_onresponse()`, or `_onnotification()`
122+
3. **`Protocol._onrequest()`**:
123+
- Looks up handler in `_requestHandlers` map (keyed by method name)
124+
- Creates `RequestHandlerExtra` with `signal`, `sessionId`, `sendNotification`, `sendRequest`
125+
- Invokes handler, sends JSON-RPC response back via transport
126+
4. **Handler** was registered via `setRequestHandler(Schema, handler)`
127+
128+
### Handler Registration
129+
130+
```typescript
131+
// In Client (for server→client requests like sampling, elicitation)
132+
client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => {
133+
// Handle sampling request from server
134+
return { role: "assistant", content: {...}, model: "..." };
135+
});
136+
137+
// In Server (for client→server requests like tools/call)
138+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
139+
// Handle tool call from client
140+
return { content: [...] };
141+
});
142+
```
143+
144+
### Request Handler Extra
145+
146+
The `extra` parameter in handlers (`RequestHandlerExtra`) provides:
147+
- `signal`: AbortSignal for cancellation
148+
- `sessionId`: Transport session identifier
149+
- `authInfo`: Validated auth token info (if authenticated)
150+
- `requestId`: JSON-RPC message ID
151+
- `sendNotification(notification)`: Send related notification back
152+
- `sendRequest(request, schema)`: Send related request (for bidirectional flows)
153+
- `taskStore`: Task storage interface (if tasks enabled)
154+
155+
### Capability Checking
156+
157+
Both sides declare capabilities during initialization. The SDK enforces these:
158+
159+
- **Client→Server**: `Client.assertCapabilityForMethod()` checks `_serverCapabilities`
160+
- **Server→Client**: `Server.assertCapabilityForMethod()` checks `_clientCapabilities`
161+
- **Handler registration**: `assertRequestHandlerCapability()` validates local capabilities
162+
163+
### Adding a New Request Type
164+
165+
1. **Define schema** in `src/types.ts` (request params, result schema)
166+
2. **Add capability** to `ClientCapabilities` or `ServerCapabilities` in types
167+
3. **Implement sender** method in Client or Server class
168+
4. **Add capability check** in the appropriate `assertCapabilityForMethod()`
169+
5. **Register handler** on the receiving side with `setRequestHandler()`
170+
6. **For McpServer**: Add high-level wrapper method if needed
171+
172+
### Server-Initiated Requests (Sampling, Elicitation)
173+
174+
Server can request actions from client (requires client capability):
175+
176+
```typescript
177+
// Server sends sampling request to client
178+
const result = await server.createMessage({
179+
messages: [...],
180+
maxTokens: 100
181+
});
182+
183+
// Client must have registered handler:
184+
client.setRequestHandler(CreateMessageRequestSchema, async (request, extra) => {
185+
// Client-side LLM call
186+
return { role: "assistant", content: {...} };
187+
});
188+
```
189+
190+
## Key Patterns
191+
192+
### Request Handler Registration (Low-Level Server)
193+
```typescript
194+
server.setRequestHandler(SomeRequestSchema, async (request, extra) => {
195+
// extra contains sessionId, authInfo, sendNotification, etc.
196+
return { /* result */ };
197+
});
198+
```
199+
200+
### Tool Registration (High-Level McpServer)
201+
```typescript
202+
mcpServer.tool("tool-name", { param: z.string() }, async ({ param }, extra) => {
203+
return { content: [{ type: "text", text: "result" }] };
204+
});
205+
```
206+
207+
### Transport Connection
208+
```typescript
209+
// Server
210+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
211+
await server.connect(transport);
212+
213+
// Client
214+
const transport = new StreamableHTTPClientTransport(new URL("http://localhost:3000/mcp"));
215+
await client.connect(transport);
216+
```

0 commit comments

Comments
 (0)