284 lines
15 KiB
Markdown
284 lines
15 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Build & Test Commands
|
||
|
||
```sh
|
||
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-internal test # vitest run (core)
|
||
pnpm --filter @modelcontextprotocol/core-internal test:watch # vitest (watch)
|
||
pnpm --filter @modelcontextprotocol/core-internal test -- path/to/file.test.ts
|
||
pnpm --filter @modelcontextprotocol/core-internal test -- -t "test name"
|
||
```
|
||
|
||
## Breaking Changes
|
||
|
||
When making breaking changes, add to the relevant subsystem section in
|
||
`docs/migration/upgrade-to-v2.md` (or `docs/migration/support-2026-07-28.md` if the
|
||
change is 2026-07-28-only). Mechanical renames go in
|
||
`packages/codemod/src/migrations/v1-to-v2/mappings/` and the codemod handles them — do
|
||
not reproduce mapping tables in the guide; link to the mapping file instead.
|
||
|
||
Include what changed, why, and how to migrate. Search for related sections and group related changes together rather than adding new standalone sections.
|
||
|
||
## 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, no `.js` extension on relative imports (project uses `moduleResolution: bundler`), group imports logically
|
||
- **Formatting**: 2-space indentation, semicolons required, single quotes preferred
|
||
- **Testing**: Place tests under each package's `test/` directory (vitest only includes `test/**/*.test.ts`), use descriptive test names
|
||
- **Comments**: JSDoc for public APIs, inline comments for complex logic
|
||
|
||
### JSDoc `@example` Code Snippets
|
||
|
||
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.
|
||
|
||
## Architecture Overview
|
||
|
||
### Core Layers
|
||
|
||
The SDK is organized into three main layers:
|
||
|
||
1. **Types Layer** (`packages/core-internal/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.
|
||
|
||
2. **Protocol Layer** (`packages/core-internal/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` (`packages/client/src/client/client.ts`) - Client implementation extending Protocol with typed methods for MCP operations
|
||
- `Server` (`packages/server/src/server/server.ts`) - Server implementation extending Protocol with request handler registration
|
||
- `McpServer` (`packages/server/src/server/mcp.ts`) - High-level server API with simplified resource/tool/prompt registration
|
||
|
||
### Public API Exports
|
||
|
||
The SDK separates internal code from the public API surface:
|
||
|
||
- **`@modelcontextprotocol/core-internal`** (main entry, `packages/core-internal/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`).
|
||
- **`@modelcontextprotocol/core-internal/public`** (`packages/core-internal/src/exports/public/index.ts`) — Curated public API. Exports TypeScript types, error classes, constants, guards, and the `Protocol` base class (+ `mergeCapabilities`). Re-exported by client and server packages.
|
||
- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core-internal/public`.
|
||
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package and the canonical home of the schema source modules (`src/schemas.ts`, `src/auth.ts`, `src/constants.ts`). The root entry re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID) — the published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). The `./internal` subpath re-exports the schema modules wholesale for the sibling packages: `core-internal` re-exports them at the old module paths, and the `client`/`server`/`server-legacy` bundles resolve `@modelcontextprotocol/core/internal` as a real external dependency instead of carrying their own schema copies (their public surfaces stay Zod-free).
|
||
|
||
When modifying exports:
|
||
|
||
- Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/public`.
|
||
- Adding a symbol to a package `index.ts` makes it public API — do so intentionally.
|
||
- Internal helpers should stay in the core internal barrel and not be added to `core-internal/public` or package index files.
|
||
- The package root entry must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it. Exports whose module graph transitively touches unpolyfillable Node builtins (`node:child_process`, `node:net`, `cross-spawn`, etc.) must live at a named subpath export (e.g. `./stdio`) and be covered by a `barrelClean` test in that package.
|
||
|
||
### Transport System
|
||
|
||
Transports (`packages/core-internal/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
|
||
|
||
### Server-Side Features
|
||
|
||
- **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`
|
||
|
||
### Client-Side Features
|
||
|
||
- **Auth**: OAuth client support in `packages/client/src/client/auth.ts` and `packages/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/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`
|
||
|
||
### Middleware packages (framework/runtime adapters)
|
||
|
||
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.
|
||
|
||
### Experimental Features
|
||
|
||
Located in `packages/*/src/experimental/`. Currently empty.
|
||
|
||
### Zod Schemas
|
||
|
||
The SDK uses `zod/v4` internally. Schema utilities live in:
|
||
|
||
- `packages/core-internal/src/util/schema.ts` - AnySchema alias and helpers for inspecting Zod objects
|
||
|
||
### Validation
|
||
|
||
Pluggable JSON Schema validation (`packages/core-internal/src/validators/`):
|
||
|
||
- `ajvProvider.ts` - Default Ajv-based validator
|
||
- `cfWorkerProvider.ts` - Cloudflare Workers-compatible alternative
|
||
|
||
### Examples
|
||
|
||
Runnable examples in `examples/<story>/{server.ts,client.ts}` — each story is its own
|
||
`@mcp-examples/<story>` workspace package and a self-verifying e2e test (the client connects,
|
||
asserts results, exits non-zero on mismatch). `pnpm run:examples` runs every story over its
|
||
configured transport×era legs; the `examples (build + e2e)` CI job is part of the per-PR gate
|
||
basket. See `examples/README.md` for the full story matrix.
|
||
|
||
- `examples/shared/` — `@mcp-examples/shared` package. Root export is args-only (`parseExampleArgs`, `check`, `siblingPath`); the demo OAuth provider and `InMemoryEventStore` live at the `@mcp-examples/shared/auth` subpath so non-auth stories don't eagerly evaluate better-auth/express/better-sqlite3. Stories import only this plumbing and inline the SDK transport setup themselves — see `examples/CONTRIBUTING.md`.
|
||
- `scripts/examples/` — runner (`run-examples.ts`)
|
||
- `examples/guides/` — per-page snippet companions for the `docs/` guide pages (one `<section>/<page>.examples.ts` per page); fences sync via `pnpm sync:snippets`, and the runnable ones are executed in CI by `pnpm docs:examples`
|
||
|
||
## 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 (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
|
||
```
|
||
|
||
### 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 `BaseContext` with `signal`, `sessionId`, `sendNotification`, `sendRequest`, etc.
|
||
- Calls `buildContext()` to let subclasses enrich the context (e.g., Server adds HTTP request info)
|
||
- Invokes handler, sends JSON-RPC response back via transport
|
||
4. **Handler** was registered via `setRequestHandler('method', handler)`
|
||
|
||
### Handler Registration
|
||
|
||
```typescript
|
||
// 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: [...] };
|
||
});
|
||
```
|
||
|
||
### Request Handler Context
|
||
|
||
The `ctx` parameter in handlers provides a structured context:
|
||
|
||
**`BaseContext`** (common to both Server and Client), fields organized into nested groups:
|
||
|
||
- `sessionId?`: Transport session identifier
|
||
- `mcpReq`: Request-level concerns
|
||
- `id`: JSON-RPC message ID
|
||
- `method`: Request method string (e.g., 'tools/call')
|
||
- `_meta?`: Request metadata
|
||
- `signal`: AbortSignal for cancellation
|
||
- `send(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
|
||
|
||
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:
|
||
|
||
- `mcpReq` adds: `log(level, data, logger?)`, `elicitInput(params, options?)`, `requestSampling(params, options?)`
|
||
- `http?` adds: `req?` (HTTP request info), `closeSSE?`, `closeStandaloneSSE?`
|
||
|
||
**`ClientContext`** is currently identical to `BaseContext`.
|
||
|
||
### 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('sampling/createMessage', async (request, extra) => {
|
||
// Client-side LLM call
|
||
return { role: "assistant", content: {...} };
|
||
});
|
||
```
|
||
|
||
## Key Patterns
|
||
|
||
### Request Handler Registration (Low-Level Server)
|
||
|
||
```typescript
|
||
server.setRequestHandler('tools/call', 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
|
||
// (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);
|
||
```
|