Skip to content

refactor: replace RequestHandlerExtra with structured context types#1467

Merged
felixweinberger merged 7 commits intomainfrom
fweinberger/simplify-context
Feb 3, 2026
Merged

refactor: replace RequestHandlerExtra with structured context types#1467
felixweinberger merged 7 commits intomainfrom
fweinberger/simplify-context

Conversation

@felixweinberger
Copy link
Contributor

@felixweinberger felixweinberger commented Feb 3, 2026

Replace the flat RequestHandlerExtra type with structured context types that group related fields into mcpReq, http, and task sub-objects.

Motivation and Context

The current RequestHandlerExtra is a flat bag of properties mixing MCP protocol metadata, HTTP transport info, task state, and notification capabilities. This refactor:

  1. Separates concerns (protocol vs transport vs task state) via nested groups
  2. Splits context by role: ServerContext adds convenience methods (log, elicitInput, requestSampling) and HTTP fields; ClientContext stays minimal
  3. Makes buildContext() abstract on Protocol, letting Server/Client enrich the base context
  4. Enables future non-breaking additions via TypeScript intersection merging

How Has This Been Tested?

  • pnpm typecheck:all — all packages type-check
  • pnpm test:all — all tests pass (430 core, 37 server, 360 integration)
  • pnpm lint:all — clean

Breaking Changes

  • RequestHandlerExtra type removed entirely
  • Handler parameter renamed extractx
  • Property access paths changed (see mapping below)
  • CreateTaskRequestHandlerExtraCreateTaskServerContext
  • TaskRequestHandlerExtraTaskServerContext

Migration mapping

Old New
extra.requestId ctx.mcpReq.id
extra.signal ctx.mcpReq.signal
extra._meta ctx.mcpReq._meta
extra.sendRequest(r, s, o) ctx.mcpReq.send(r, s, o)
extra.sendNotification(n) ctx.mcpReq.notify(n)
extra.sessionId ctx.sessionId
extra.authInfo ctx.http?.authInfo
extra.requestInfo ctx.http?.req
extra.closeSSEStream ctx.http?.closeSSE
extra.closeStandaloneSSEStream ctx.http?.closeStandaloneSSE
extra.taskId ctx.task?.id
extra.taskStore ctx.task?.store
extra.taskRequestedTtl ctx.task?.requestedTtl

New convenience methods on ServerContext.mcpReq

Method Replaces
ctx.mcpReq.log(level, data, logger?) server.sendLoggingMessage(...) from within handlers
ctx.mcpReq.elicitInput(params, options?) server.elicitInput(...) from within handlers
ctx.mcpReq.requestSampling(params, options?) server.createMessage(...) from within handlers

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The ContextT type parameter on Protocol and the buildContext() abstract method provide a clean extension point. ServerContext intersects BaseContext with server-specific fields; ClientContext is currently identical to BaseContext but can be extended independently.

@changeset-bot
Copy link

changeset-bot bot commented Feb 3, 2026

⚠️ No Changeset found

Latest commit: 649efbb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 3, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1467

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1467

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1467

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1467

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1467

commit: 649efbb

@felixweinberger felixweinberger force-pushed the fweinberger/simplify-context branch 2 times, most recently from b891efc to 91816a7 Compare February 3, 2026 16:19
SendRequestT extends Request,
SendNotificationT extends Notification,
SendResultT extends Result,
ContextT extends BaseContext<SendRequestT, SendNotificationT>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important change: because both Client and Server implement Protocol but we now want to have different shapes for their Contexts, this is a natural fit for adding another type param to the Protocol class as it's something that systematically differs between the two classes.

I debated a lot with myself whether adding another Generic param is justified, but in this case, with this goal (making Server and Client contexts clearly distinct) I think it is in fact justified and actually makes the code cleaner.

The nice side effect is that it allows us to remove a bunch of as unknown as Bla stuff in protocol because the setRequestHandler and fallbackRequestHandler are now neatly typed with this.

@felixweinberger felixweinberger force-pushed the fweinberger/simplify-context branch from c972bac to dc8d098 Compare February 3, 2026 18:24
@felixweinberger felixweinberger marked this pull request as ready for review February 3, 2026 18:54
@felixweinberger felixweinberger requested a review from a team as a code owner February 3, 2026 18:54
@felixweinberger felixweinberger force-pushed the fweinberger/simplify-context branch from 9c72932 to a31b3a7 Compare February 3, 2026 19:43
- Change buildContext from concrete default to abstract method on Protocol,
  forcing subclasses to explicitly implement context construction
- Add buildContext stubs to all inline Protocol subclasses in tests
- Add migration docs for RequestHandlerExtra → context type hierarchy
- Update CLAUDE.md architecture section to reflect ctx parameter and
  BaseContext/ServerContext/ClientContext types
- Add 4th type parameter ContextT to Protocol class for proper type
  variance (removes all 'as unknown as typeof handler' casts)
- Add convenience methods to ServerContext: log(), elicitInput(),
  requestSampling() that delegate to Server methods
- Update Server and Client to pass their context types as ContextT
- Update all test Protocol subclasses with explicit BaseContext param
- Document new methods in migration guides and CLAUDE.md
…fication)

Restructure BaseContext and ServerContext from flat to nested:
- mcpReq: id, method (new), _meta, signal, send (was sendRequest),
  + server: elicitInput, requestSampling
- http?: authInfo, + server: req (was requestInfo), closeSSE, closeStandaloneSSE
- notification: send (was sendNotification), + server: log
- sessionId and task stay top-level (unchanged)

Update all access sites across production code, tests, examples,
conformance server, and documentation.
…mcpReq.log

All request-scoped operations now live under mcpReq:
send(), notify(), log(), elicitInput(), requestSampling().
The notification group is removed.
ttl: extra.taskRequestedTtl
if (request.params.task && ctx.task?.store) {
const createdTask = await ctx.task!.store.createTask({
ttl: ctx.task!.requestedTtl
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this assertion needed, shouldn't be?

ttl: extra.taskRequestedTtl
if (request.params.task && ctx.task?.store) {
const createdTask = await ctx.task!.store.createTask({
ttl: ctx.task!.requestedTtl
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird to have a ! here

async getTask(_args, extra) {
const task = await extra.taskStore.getTask(extra.taskId);
async getTask(_args, ctx) {
const task = await ctx.task!.store.getTask(ctx.task!.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here also

mattzcarey
mattzcarey previously approved these changes Feb 3, 2026
Copy link
Contributor

@mattzcarey mattzcarey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not blocking this. go for it

@KKonstantinov
Copy link
Contributor

YES!!

- Fix Server.buildContext() to not create http object for non-HTTP
  transports (was always truthy due to spread pattern)
- Fix migration docs using extra.requestInfo in v2 examples
- Fix stale 'extra' reference in createToolExecutor JSDoc
- Update examples to use ctx.mcpReq.log() instead of
  server.sendLoggingMessage()
- Add tests for ctx.mcpReq.log(), elicitInput(), requestSampling()
  convenience methods
@felixweinberger felixweinberger force-pushed the fweinberger/simplify-context branch from 361861b to 649efbb Compare February 3, 2026 20:33
@felixweinberger felixweinberger merged commit 7bb79eb into main Feb 3, 2026
14 checks passed
@felixweinberger felixweinberger deleted the fweinberger/simplify-context branch February 3, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants