refactor: replace RequestHandlerExtra with structured context types (#1467)

This commit is contained in:
Felix Weinberger 2026-02-03 20:35:02 +00:00 • committed by GitHub
commit 7bb79ebbbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 1224 additions and 770 deletions

View file

@ -297,7 +297,7 @@ extra.requestInfo?.headers['mcp-session-id']
// v2: Headers object, .get() access
headers: new Headers({ 'Authorization': 'Bearer token' })
extra.requestInfo?.headers.get('mcp-session-id')
ctx.http?.req?.headers.get('mcp-session-id')
```
## 8. Removed Server Features
@ -366,11 +366,41 @@ Schema to method string mapping:
Request/notification params remain fully typed. Remove unused schema imports after migration.
## 10. Client Behavioral Changes
## 10. Request Handler Context Types
`RequestHandlerExtra` → structured context types with nested groups. Rename `extra` → `ctx` in all handler callbacks.
| v1 | v2 |
|----|-----|
| `RequestHandlerExtra` | `ServerContext` (server) / `ClientContext` (client) / `BaseContext` (base) |
| `extra` (param name) | `ctx` |
| `extra.signal` | `ctx.mcpReq.signal` |
| `extra.requestId` | `ctx.mcpReq.id` |
| `extra._meta` | `ctx.mcpReq._meta` |
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
| `extra.authInfo` | `ctx.http?.authInfo` |
| `extra.sessionId` | `ctx.sessionId` |
| `extra.requestInfo` | `ctx.http?.req` (only `ServerContext`) |
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only `ServerContext`) |
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only `ServerContext`) |
| `extra.taskStore` | `ctx.task?.store` |
| `extra.taskId` | `ctx.task?.id` |
| `extra.taskRequestedTtl` | `ctx.task?.requestedTtl` |
`ServerContext` convenience methods (new in v2, no v1 equivalent):
| Method | Description | Replaces |
|--------|-------------|----------|
| `ctx.mcpReq.log(level, data, logger?)` | Send log notification (respects client's level filter) | `server.sendLoggingMessage(...)` from within handler |
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
## 11. Client Behavioral Changes
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.
## 11. Runtime-Specific JSON Schema Validators (Enhancement)
## 12. Runtime-Specific JSON Schema Validators (Enhancement)
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
@ -390,7 +420,7 @@ new McpServer({ name: 'server', version: '1.0.0' }, {});
Access validators via `_shims` export: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
## 12. Migration Steps (apply in this order)
## 13. Migration Steps (apply in this order)
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport` → `NodeStreamableHTTPServerTransport`

View file

@ -156,7 +156,7 @@ const transport = new StreamableHTTPClientTransport(url, {
});
// Reading headers in a request handler
const sessionId = extra.requestInfo?.headers.get('mcp-session-id');
const sessionId = ctx.http?.req?.headers.get('mcp-session-id');
```
### `McpServer.tool()`, `.prompt()`, `.resource()` removed
@ -381,6 +381,83 @@ import { JSONRPCError, ResourceReference, isJSONRPCError } from '@modelcontextpr
import { JSONRPCErrorResponse, ResourceTemplateReference, isJSONRPCErrorResponse } from '@modelcontextprotocol/core';
```
### Request handler context types
The `RequestHandlerExtra` type has been replaced with a structured context type hierarchy using nested groups:
| v1 | v2 |
|----|-----|
| `RequestHandlerExtra` (flat, all fields) | `ServerContext` (server handlers) or `ClientContext` (client handlers) |
| `extra` parameter name | `ctx` parameter name |
| `extra.signal` | `ctx.mcpReq.signal` |
| `extra.requestId` | `ctx.mcpReq.id` |
| `extra._meta` | `ctx.mcpReq._meta` |
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
| `extra.authInfo` | `ctx.http?.authInfo` |
| `extra.requestInfo` | `ctx.http?.req` (only on `ServerContext`) |
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only on `ServerContext`) |
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only on `ServerContext`) |
| `extra.sessionId` | `ctx.sessionId` |
| `extra.taskStore` | `ctx.task?.store` |
| `extra.taskId` | `ctx.task?.id` |
| `extra.taskRequestedTtl` | `ctx.task?.requestedTtl` |
**Before (v1):**
```typescript
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
const headers = extra.requestInfo?.headers;
const taskStore = extra.taskStore;
await extra.sendNotification({ method: 'notifications/progress', params: { progressToken: 'abc', progress: 50, total: 100 } });
return { content: [{ type: 'text', text: 'result' }] };
});
```
**After (v2):**
```typescript
server.setRequestHandler('tools/call', async (request, ctx) => {
const headers = ctx.http?.req?.headers;
const taskStore = ctx.task?.store;
await ctx.mcpReq.notify({ method: 'notifications/progress', params: { progressToken: 'abc', progress: 50, total: 100 } });
return { content: [{ type: 'text', text: 'result' }] };
});
```
Context fields are organized into 4 groups:
- **`mcpReq`** — request-level concerns: `id`, `method`, `_meta`, `signal`, `send()`, `notify()`, plus server-only `log()`, `elicitInput()`, and `requestSampling()`
- **`http?`** — HTTP transport concerns (undefined for stdio): `authInfo`, plus server-only `req`, `closeSSE`, `closeStandaloneSSE`
- **`task?`** — task lifecycle: `id`, `store`, `requestedTtl`
`BaseContext` is the common base type shared by both `ServerContext` and `ClientContext`. `ServerContext` extends each group with server-specific additions via type intersection.
`ServerContext` also provides convenience methods for common server→client operations:
```typescript
server.setRequestHandler('tools/call', async (request, ctx) => {
// Send a log message (respects client's log level filter)
await ctx.mcpReq.log('info', 'Processing tool call', 'my-logger');
// Request client to sample an LLM
const samplingResult = await ctx.mcpReq.requestSampling({
messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }],
maxTokens: 100,
});
// Elicit user input via a form
const elicitResult = await ctx.mcpReq.elicitInput({
message: 'Please provide details',
requestedSchema: { type: 'object', properties: { name: { type: 'string' } } },
});
return { content: [{ type: 'text', text: 'done' }] };
});
```
These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, and `server.elicitInput()` from within handlers.
### Error hierarchy refactoring
The SDK now distinguishes between two types of errors: