McpServerSession never sends a response when a request handler's Mono completes empty — violates JSON-RPC 2.0's one-response-per-request contract
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 68/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Tranquilla
- Stack tecnologico
- java
- Ambito
- api, backend-api-design
Direzione di ricerca
Inizia in McpServerSession.handleIncomingRequest e handle, quindi esamina il percorso equivalente di gestione delle richieste in McpStatelessAsyncServer. Segui un Mono vuoto da un McpRequestHandler personalizzato attraverso la creazione della risposta e transport::sendMessage, quindi verifica con la riproduzione di Empty-Mono descritta che ogni richiesta produca esattamente una risposta JSON-RPC.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
MCP rides on JSON-RPC 2.0, which requires that every Request carrying an
id receives exactly one Response (a result or an error). In
McpServerSession, if the Mono<T> returned by a registered
McpRequestHandler completes empty — no onNext, just onComplete —
the session never sends any response at all for that request. Not a
result, not an error: nothing. The client is left waiting indefinitely.
This is a protocol-conformance gap in the SDK's own dispatch code, independent
of any specific request handler's correctness — any handler that can
legitimately or accidentally produce an empty Mono breaks the contract
for its caller, silently.
Filed alongside spring-ai-community/mcp-annotations#113
(https://github.com/spring-ai-community/mcp-annotations/issues/113), which
documents the concrete case that surfaced this: a @McpTool method
returning a bare Mono<T> that completes empty (e.g. a reactive
repository's get(id) completing empty when nothing matches — a very
common Reactor idiom). That issue is about the annotation-callback layer
converting a tool's empty Mono<T> into an empty Mono<CallToolResult>.
This issue is about the fact that this SDK's own session layer has the
identical gap, so even if the annotation layer is fixed, any other
McpRequestHandler implementation (custom or third-party, not just
tools/call) can trigger the same silent hang here.
Versions
Reproduced in:
- io.modelcontextprotocol.sdk:mcp-core 0.18.2
- io.modelcontextprotocol.sdk:mcp-core 2.0.0 (the relevant code is
unchanged between these two versions, so this likely affectsmaintoo)
Root cause
McpServerSession.handleIncomingRequest:
resultMono = this.exchangeSink.asMono()
.flatMap(exchange -> handler.handle(copyExchange(exchange, transportContext), request.params()));
return resultMono
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null))
.onErrorResume(error -> {
// ... builds and returns an error JSONRPCResponse
});
.map() only runs on emission. If handler.handle(...) returns a Mono<T>
that completes empty, this method's own Mono<JSONRPCResponse> is also
empty — no exception, no error branch taken, just silently empty.
McpServerSession.handle:
else if (message instanceof McpSchema.JSONRPCRequest request) {
return handleIncomingRequest(request, transportContext).onErrorResume(error -> {
// ... sends an error response
}).flatMap(this.transport::sendMessage);
}
.flatMap never invokes its function for an empty source, so
this.transport.sendMessage(...) is never called for this request. No
bytes go out over the wire for that request, ever. The client's pending
call just sits unanswered until (if) it enforces its own timeout.
Same shape appears in McpStatelessAsyncServer's equivalent request
handling path.
Reproduction
Any custom McpRequestHandler<T> (registered via
McpAsyncServer/McpStatelessAsyncServer) whose handle(...) method
returns a Mono<T> that can complete empty will reproduce this — it does
not require going through the annotation-based tool support. Concretely,
the annotation-callback path in mcp-annotations#113 hits it via a
@McpTool method returning Mono.empty().
Suggested fix
handleIncomingRequest (and the stateless equivalent) should guarantee
resultMono never reaches the final .map()/.onErrorResume() chain in
an empty state — e.g. a switchIfEmpty(...) that converts an unexpectedly
empty result into an explicit JSON-RPC error response
(McpSchema.ErrorCodes.INTERNAL_ERROR, or a dedicated code). This would
make the guarantee "every request gets exactly one response" hold at the
SDK level regardless of what any individual McpRequestHandler
implementation does — the same principle JSON-RPC 2.0 itself requires.
Workaround
At the application layer, we avoid returning a Mono<T> that can complete
empty from any MCP-registered handler — converting the empty case to an
explicit error/exception instead, since the .onErrorResume branches in
both this method and .handle(...) already work correctly; only the
empty-completion path is broken.
- Lingua principale
- Java
- Stelle
- 3.7k
- Fork
- 1.1k
- Merge medio
- 1g 15h
- PR unite (30g)
- 9
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di modelcontextprotocol/java-sdk
-
area/transport bug P2
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
modelcontextprotocol/java-sdk#1136 ·
-
area/client bug P2
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelcontextprotocol/java-sdk#1124 · 1 commento ·
-
ServerCapabilities.logging is added unconditionally, overriding the caller's explicit capabilities Apertabug P2 ready for work
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
modelcontextprotocol/java-sdk#1086 · 1 commento ·
-
enhancement good first issue P3
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
modelcontextprotocol/java-sdk#1067 ·
-
bug P2 ready for work
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
modelcontextprotocol/java-sdk#898 · 1 commento ·
Tutte le issue di modelcontextprotocol/java-sdk
Issue simili
-
Bug Java Platform: Java
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
getsentry/sentry-java#6138 · 1 commento ·
-
bug needs triage p2
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
GoogleCloudPlatform/DataflowTemplates#4273 · 1 commento ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
-
bug needs triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100