You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MCP 2026-07-28: resources, prompts, subscriptions/listen and stdio - #1665
Second R6b delivery for #1640, after #1664. It completes the MCP 2026-07-28 server side, apart from the client: modern resources and prompts, subscriptions/listen, and 2026-07-28 over stdio.
Modern resources and prompts.
McpServerAdapter.getModernResources() and getModernResourceTemplates() add resources served to 2026-07-28 clients only. Each item uses a small new record, McpModernResource or McpModernResourceTemplate, pairing the SDK's modern definition with its McpAsyncResource read handler, which may answer input_required.
getModernPrompts() takes the SDK's McpAsyncPrompt.
These are merged with the bridged 2025-11-25 lists. Exact URIs are matched first, then templates.
A URI, URI template or prompt name used twice across both lists fails start(), as tool names already did.
subscriptions/listen.
getChanges() returns the SDK's McpChangeBroadcaster. Applications publish tools, prompts and resources list changes, and resource updates, through it.
A listen stream is acknowledged, then carries the changes the client asked for.
Listen streams run on their own worker pool:
their limit is setMaxListenStreams(), default 64, and a stream beyond it gets 503;
they have no request timeout, only heartbeats;
they don't use the call limit's slots.
stop() first ends the SDK's listen streams gracefully. Each client gets the listen result, and the adapter waits at most 2 s, capped by the request timeout. Anything still running is then cut short as before. A disconnect also ends a stream.
Stdio.
StdioServerHelper reads each message's _meta protocol version. A 2026-07-28 message goes to the modern exchange with the headers it would have had over HTTP, and the HTTP header checks are skipped. Mcp-Param headers count as not mirrored, as on the SDK's own stdio transport.
Streamed messages (progress, then the result) are written as separate lines as they come. A listen stream stays open next to other calls.
notifications/cancelled naming an in-flight 2026-07-28 request aborts it, with or without _meta, and the request gets no response.
A 2026-07-28 request runs without a stdio-level cap or timeout: the adapter's call limit, listen limit and request timeout apply. A request that is refused, too large or broken is answered with an error carrying its id, and the connector stays up.
Only a valid notifications/cancelled notification cancels. A request that names that method is answered. Marking a request cancelled and writing its lines share a lock, so no line of the request follows its cancellation.
2025-11-25 stdio traffic is unchanged.
API additions:
McpServerAdapter: getModernResources(), getModernResourceTemplates(), getModernPrompts(), getChanges(), and get/setMaxListenStreams();
the records McpModernResource and McpModernResourceTemplate;
McpProtocol.DEFAULT_MAX_LISTEN_STREAMS.
Tests.
McpModernServerTestCase has 4 more tests:
modern resources (exact and template) and prompts served next to the bridged ones;
duplicate URI, template and prompt failing the start;
a listen stream acknowledged, carrying a published change while a call still gets through with a call limit of 1, then ending with its result, and nothing after it, on stop();
the listen limit refusing a second stream.
McpModernStdioTestCase is new, over pipes:
discovery and calls;
progress and result as separate lines;
a cancel without _meta ending the handler with no response;
a listen stream carrying a change next to a call;
a request named notifications/cancelled answered without cancelling anything;
the adapter's listen limit applying over stdio, above the default of 64;
an invalid id answered with a null id.
An independent review found, and this PR fixes:
stop() cut listen streams short with an error instead of letting them end with their result;
a possible leak of cancelled requests;
a streamed message near the size limit tearing down the stdio connector;
the 30 s stdio timeout killing the connector on a long 2026-07-28 call;
cancellations without _meta being ignored.
Copilot's review then found, and this PR fixes:
the stdio listen limit ignoring setMaxListenStreams();
a request named notifications/cancelled being taken as a cancellation;
a reply that could still be written after its cancellation;
a streamed message of exactly the size limit being rejected;
workers and responses piling up when a stdio client stops reading. A 2026-07-28 request now takes a pending slot first, and keeps it until its response or error is written. There are as many slots as the adapter's listen limit plus the default call limit. A request arriving while every slot is held is refused at once, so the reader keeps reading cancellations;
stop() appending an error after a listen stream's result. A worker now frees its slot only after signalling the call's end;
an id that is neither a string nor a number being echoed in an error. Such a request is answered with a null id.
A test inherited from #1664 sometimes failed on CI: it called again before the cancelled call's worker had freed its slot. It now waits for the slot.
mvn -pl org.restlet.ext.mcp verify: 68 tests, coverage and SpotBugs pass.
…stdio
Modern-only resources, resource templates and prompts are served next to
the bridged ones, with duplicates across both lists failing the start.
subscriptions/listen streams carry the changes published through
getChanges(), on their own limit and without time limit; stop() first
ends them with their result. Over stdio, a message whose _meta names
2026-07-28 goes to the modern exchange: streamed messages are written as
separate lines, and notifications/cancelled aborts the request it names
without a response.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Extends the experimental MCP 2026-07-28 server support in org.restlet.ext.mcp, building on the earlier HTTP delivery while retaining legacy 2025-11-25 handling.
Changes:
Adds modern resources, resource templates and prompts with duplicate-registration checks.
Adds subscription streams with separate admission limits and graceful shutdown.
Adds modern stdio routing, streamed responses and cancellation handling, with HTTP and pipe-based tests.
2026-07-28 requests over stdio run without a stdio-level cap, so the
adapter's call and listen limits govern; an error it answers without the
request id, having refused the request unread, gets it. Only a valid
notifications/cancelled notification cancels; a request naming that
method is answered. Marking a request cancelled and writing its frames
share a lock, so no frame follows a cancellation. A streamed message of
exactly the message size is accepted. The tests waiting for a freed call
slot now wait for its worker to free it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Critical issues remain: shutdown can emit both a listen result and an error for the same request ID, and blocked stdout writes can cause unbounded worker and response-buffer accumulation despite call limits.
… signal
2026-07-28 requests over stdio now take a pending slot before a worker:
the adapter's listen limit plus the default call limit. A client that
stops reading the output stops being read in turn, instead of piling up
workers and responses. A worker frees its slot only after signalling the
call's end, so stop() no longer appends an error after a listen stream's
result; the stop test checks that nothing follows the result. Also
splits the stdio helper's longer methods and tidies the tests for Sonar.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Critical admission and cleanup flaws can block cancellation and let fallback error writers bypass the pending-output bound, while invalid request IDs can produce malformed JSON-RPC responses.
A modern request with an object, array, or boolean id can be rejected by the adapter's size check before SDK validation, after which refuse() echoes the invalid ID in its error response. JSON-RPC response IDs must be strings, numbers, or null. Validate IDs before registration and correlation, and answer malformed requests with a null-ID error rather than copying invalid values.
… written
A 2026-07-28 request over stdio arriving while every pending slot is
held is now refused at once, instead of blocking the reader, so
cancellations are still read. A failed request keeps its slot and its
in-flight registration until the error answering it is written. An id
that is neither a string nor a number is not echoed: such a request is
answered with a null id. Also splits the stdio helper's stop() for
Sonar.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Active issues cause sequential sends to be rejected before admission permits are released and allow out-of-range cancellation IDs to match and abort a different request.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second R6b delivery for #1640, after #1664. It completes the MCP 2026-07-28 server side, apart from the client: modern resources and prompts,
subscriptions/listen, and 2026-07-28 over stdio.Modern resources and prompts.
McpServerAdapter.getModernResources()andgetModernResourceTemplates()add resources served to 2026-07-28 clients only. Each item uses a small new record,McpModernResourceorMcpModernResourceTemplate, pairing the SDK's modern definition with itsMcpAsyncResourceread handler, which may answerinput_required.getModernPrompts()takes the SDK'sMcpAsyncPrompt.start(), as tool names already did.subscriptions/listen.getChanges()returns the SDK'sMcpChangeBroadcaster. Applications publish tools, prompts and resources list changes, and resource updates, through it.setMaxListenStreams(), default 64, and a stream beyond it gets 503;stop()first ends the SDK's listen streams gracefully. Each client gets the listen result, and the adapter waits at most 2 s, capped by the request timeout. Anything still running is then cut short as before. A disconnect also ends a stream.Stdio.
StdioServerHelperreads each message's_metaprotocol version. A 2026-07-28 message goes to the modern exchange with the headers it would have had over HTTP, and the HTTP header checks are skipped.Mcp-Paramheaders count as not mirrored, as on the SDK's own stdio transport.notifications/cancellednaming an in-flight 2026-07-28 request aborts it, with or without_meta, and the request gets no response.notifications/cancellednotification cancels. A request that names that method is answered. Marking a request cancelled and writing its lines share a lock, so no line of the request follows its cancellation.API additions:
McpServerAdapter:getModernResources(),getModernResourceTemplates(),getModernPrompts(),getChanges(), andget/setMaxListenStreams();McpModernResourceandMcpModernResourceTemplate;McpProtocol.DEFAULT_MAX_LISTEN_STREAMS.Tests.
McpModernServerTestCasehas 4 more tests:stop();McpModernStdioTestCaseis new, over pipes:_metaending the handler with no response;notifications/cancelledanswered without cancelling anything;An independent review found, and this PR fixes:
stop()cut listen streams short with an error instead of letting them end with their result;_metabeing ignored.Copilot's review then found, and this PR fixes:
setMaxListenStreams();notifications/cancelledbeing taken as a cancellation;stop()appending an error after a listen stream's result. A worker now frees its slot only after signalling the call's end;A test inherited from #1664 sometimes failed on CI: it called again before the cancelled call's worker had freed its slot. It now waits for the slot.
mvn -pl org.restlet.ext.mcp verify: 68 tests, coverage and SpotBugs pass.🤖 Generated with Claude Code