Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 11 additions & 221 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Loading