Gap
Java is at consumer/provider LLM parity with Python after #1221 (claim-path) and #1222 (model override + model_params), except streaming production:
- D (core): a Java
@MeshTool cannot PRODUCE a native MCP stream. Python has Stream[str] return / stream_type="text" driving notifications/progress. Java can only CONSUME streams and produce A2A SSE.
- C1 (LLM):
@MeshLlmProvider registers no streaming tool tagged ai.mcpmesh.stream, so the already-shipped Java consumer MeshLlmAgentProxy.streamGenerate()/stream() is dead code against a Java provider.
Deferred from the Wave 2 parity work to 2.7 because it needs a deliberate transport design (below), not a rush.
The hard blocker
The Java MCP server uses the stateless transport (HttpServletStatelessServerTransport). Its tool handler returns a single CallToolResult and never receives a server-exchange object, so it structurally cannot emit notifications/progress. progressNotification() exists only on the stateful/session-based server's McpSyncServerExchange/McpAsyncServerExchange, which Java mesh does not use. So a streaming producer must emit the frames outside the bundled MCP SDK stateless transport.
The exact wire format a producer must emit (consumer contract)
The existing Java consumer (McpHttpClient.streamTool) reads:
- Request: JSON-RPC
tools/call with params._meta.progressToken = UUID, Accept: application/json, text/event-stream.
- Response:
Content-Type: text/event-stream (consumer hard-fails otherwise).
- Per chunk: SSE
data: = {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"<uuid>","progress":<i>,"message":"<chunk>"}}.
- Terminate: final SSE event = the JSON-RPC response with
id matching the request (result content discarded).
This is byte-identical to Python FastMCP's ctx.report_progress(i, None, message=chunk) — which is why a Java consumer already reads a Python producer (uc18 tc05). Note: this is NOT the same as the existing MeshSse (data:<chunk> / [DONE]) browser format — emitting that would silently produce a zero-chunk stream.
Two viable approaches
- A — parallel SSE-progress inbound path: a small streaming controller/servlet next to the stateless servlet that writes the exact
notifications/progress SSE frames for ai.mcpmesh.stream-tagged tools only. Localized blast radius (non-streaming tools untouched); duplicates transport framing and must stay byte-compatible with the consumer parser.
- B — switch to the stateful/session-based MCP server: native progress notifications via the exchange object, but changes the core server model for ALL tools (large blast radius, regression risk to every non-streaming Java path).
Scope when picked up
- D (effort L): return-type detection on
@MeshTool (Flow.Publisher<String> canonical, Flux<String> convenience); stamp ai.mcpmesh.stream tag; the chunk-emitting driver.
- C1 (effort M, depends on D): register a second
@MeshLlmProvider tool tagged ai.mcpmesh.stream streaming Spring AI ChatModel.stream(...) Flux chunks via D.
- Tests: unit (return-type detection; SSE-framing golden fixture shared with the consumer parser) + integration (uc18 Java-producer mirror of tc05; a streaming
@MeshLlmProvider case).
- Out of scope for v1: structured output over streaming (Python buffers HINT mode); Reactor
Flux can follow Flow.Publisher support.
Key files: McpHttpClient.java (consumer contract), MeshToolWrapper.java (detection/driver hook), MeshMcpServerConfiguration.java (one-shot transport blocker), MeshSse.java (SSE mechanics template — wrong wire format), MeshLlmProviderProcessor.java (C1 registration), MeshLlmAgentProxy.java (consumer dead-code today).
Gap
Java is at consumer/provider LLM parity with Python after #1221 (claim-path) and #1222 (model override + model_params), except streaming production:
@MeshToolcannot PRODUCE a native MCP stream. Python hasStream[str]return /stream_type="text"drivingnotifications/progress. Java can only CONSUME streams and produce A2A SSE.@MeshLlmProviderregisters no streaming tool taggedai.mcpmesh.stream, so the already-shipped Java consumerMeshLlmAgentProxy.streamGenerate()/stream()is dead code against a Java provider.Deferred from the Wave 2 parity work to 2.7 because it needs a deliberate transport design (below), not a rush.
The hard blocker
The Java MCP server uses the stateless transport (
HttpServletStatelessServerTransport). Its tool handler returns a singleCallToolResultand never receives a server-exchange object, so it structurally cannot emitnotifications/progress.progressNotification()exists only on the stateful/session-based server'sMcpSyncServerExchange/McpAsyncServerExchange, which Java mesh does not use. So a streaming producer must emit the frames outside the bundled MCP SDK stateless transport.The exact wire format a producer must emit (consumer contract)
The existing Java consumer (
McpHttpClient.streamTool) reads:tools/callwithparams._meta.progressToken= UUID,Accept: application/json, text/event-stream.Content-Type: text/event-stream(consumer hard-fails otherwise).data:={"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"<uuid>","progress":<i>,"message":"<chunk>"}}.idmatching the request (result content discarded).This is byte-identical to Python FastMCP's
ctx.report_progress(i, None, message=chunk)— which is why a Java consumer already reads a Python producer (uc18 tc05). Note: this is NOT the same as the existingMeshSse(data:<chunk>/[DONE]) browser format — emitting that would silently produce a zero-chunk stream.Two viable approaches
notifications/progressSSE frames forai.mcpmesh.stream-tagged tools only. Localized blast radius (non-streaming tools untouched); duplicates transport framing and must stay byte-compatible with the consumer parser.Scope when picked up
@MeshTool(Flow.Publisher<String>canonical,Flux<String>convenience); stampai.mcpmesh.streamtag; the chunk-emitting driver.@MeshLlmProvidertool taggedai.mcpmesh.streamstreaming Spring AIChatModel.stream(...)Fluxchunks via D.@MeshLlmProvidercase).Fluxcan followFlow.Publishersupport.Key files:
McpHttpClient.java(consumer contract),MeshToolWrapper.java(detection/driver hook),MeshMcpServerConfiguration.java(one-shot transport blocker),MeshSse.java(SSE mechanics template — wrong wire format),MeshLlmProviderProcessor.java(C1 registration),MeshLlmAgentProxy.java(consumer dead-code today).