Skip to content

Java streaming producer: @MeshTool Stream + @MeshLlmProvider streaming tool (parity, 2.7) #1223

Description

@dhyansraj

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:

  1. Request: JSON-RPC tools/call with params._meta.progressToken = UUID, Accept: application/json, text/event-stream.
  2. Response: Content-Type: text/event-stream (consumer hard-fails otherwise).
  3. Per chunk: SSE data: = {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"<uuid>","progress":<i>,"message":"<chunk>"}}.
  4. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions