lowlevel Server: widen on_* return types for InputRequiredResult; add subscriptions/listen slot#2967
Conversation
… subscriptions/listen slot - on_call_tool / on_get_prompt / on_read_resource return types widened to include InputRequiredResult, matching MONOLITH_RESULTS in types/methods.py. Covariant widening; existing handlers returning the concrete type still type-check. - on_subscriptions_listen kwarg + _spec_requests row added so the 2026 subscriptions/listen method has a dispatch slot. - InputRequiredResult: model_validator enforces at-least-one of input_requests / request_state (spec MUST).
…locate validator test The discriminator-dump test was building a bare InputRequiredResult(), which the at-least-one-of validator now (correctly) rejects. Construct the minimal valid instance instead. Move the validator test alongside it in tests/test_types.py and drop the separate file.
There was a problem hiding this comment.
Looks good — the test conflict from my earlier comment is resolved, and the typing changes line up with MONOLITH_RESULTS.
Extended reasoning...
Overview
This PR is a small, type-focused change for the 2026-07-28 protocol revision: it widens three on_* handler return annotations in the lowlevel Server constructor to admit InputRequiredResult, adds an on_subscriptions_listen constructor kwarg plus its _spec_requests dispatch row, and adds a model_validator on InputRequiredResult enforcing the spec's at-least-one-of input_requests/request_state requirement, with accompanying tests in tests/test_types.py.
Consistency checks
The widened return types (tools/call, prompts/get, resources/read) match the unions already declared in MONOLITH_RESULTS in src/mcp/types/methods.py, and the new subscriptions/listen slot's EmptyResult return matches the surface and monolith result maps for that method. The widening is covariant, so existing handlers typed with the narrower result still type-check. I also checked for other call sites constructing a bare InputRequiredResult() that the new validator would break — the only one was the old discriminator test, which this revision updates (the issue I flagged in my earlier review), and tests/types/test_wire_frames.py constructs the model with both fields so it remains valid.
Security risks
None identified. The change does not touch auth, transport security, or input handling beyond adding a stricter validation rule on an outbound result model; it adds no new code paths that process untrusted data.
Level of scrutiny
Moderate. Constructor signatures and a dispatch-table row in the lowlevel server are visible API surface, but the changes are mechanical, follow the existing row/kwarg pattern exactly, and are runtime-inert except for the validator, which is spec-mandated and matches the typescript/csharp SDK behaviour. The previously broken test is now fixed and a new validator test is co-located alongside it. I could not execute the test suite in this environment (no project venv available), but the test changes are simple enough to verify by inspection.
Type-only changes to the lowlevel
Serverconstructor for the 2026-07-28 protocol revision.Changes
Return-type widening — three
on_*handlers can now returnInputRequiredResultin addition to their concrete result type, matchingMONOLITH_RESULTSinsrc/mcp/types/methods.py:on_call_tool→CallToolResult | InputRequiredResulton_get_prompt→GetPromptResult | InputRequiredResulton_read_resource→ReadResourceResult | InputRequiredResultThese are the three methods whose request params extend
InputResponseRequestParams. Widening is covariant, so existing handlers typed-> CallToolResultetc. still type-check unchanged.New dispatch slot —
on_subscriptions_listenkwarg +_spec_requestsrow, sosubscriptions/listen(the one new-in-2026 client→server request method) reaches a registered handler instead ofMETHOD_NOT_FOUND.InputRequiredResultvalidator —model_validator(mode="after")enforcing the spec's at-least-one-ofinput_requests/request_staterequirement (matches the typescript-sdk and csharp-sdk behaviour).Not in this PR
tasks/*— moved to theio.modelcontextprotocol/tasksextension; not core 2026-07-28.on_discoveroverride —server/discoverstays the auto-derived built-in.get_capabilities()—ServerCapabilitieshas nosubscriptionsfield; nothing to set.Part of #2891.
AI Disclaimer