LuoYushi7 opened a new issue, #4758:
URL: https://github.com/apache/rocketmq-dashboard/issues/4758

   ### Summary
   
   `rmqctl mcp-stdio` bridges a newline-delimited stdio MCP client to a Studio
   MCP session over Streamable HTTP. It can only forward client-to-server
   requests and server-to-client notifications today. Two frame shapes have no
   path through the proxy, so a stdio MCP client and the Studio server cannot
   complete any exchange that is not a plain client→server request.
   
   Raised from #4638. Posting the design first, per the contribution guide's
   "discuss before coding" rule for this track.
   
   ### Current state
   
   * `stdioProxy.handleEvent` (`rmqctl/cmd/mcp_stdio.go`) sends **every** stdin
     line to `session.SendMessage` and prints whatever comes back on stdout.
   * `studio.decodeMCPMessage` (`rmqctl/internal/studio/mcp_message.go`) accepts
     only frames that carry a `method`. A JSON-RPC **response** carries `id` 
plus
     `result`/`error` and no `method`, so it fails with
     `invalid MCP JSON-RPC message: missing method`, which the proxy surfaces to
     the caller as `-32603 Internal error`.
   * The session exposes `SetNotificationHandler` (`mcp_http.go`) but no handler
     for a **server-initiated request**, and no way to post a client response
     back to the server.
   
   Net effect: a server request (`sampling/createMessage`, `roots/list`) is
   answered by the transport with `-32601 method not found` instead of reaching
   the stdio client, and a client response never reaches the server, so the
   server-side call waits for its timeout.
   
   ### Proposed design
   
   **1. Classify stdin frames before forwarding.**
   
   Extend the existing decode step to a three-way classification instead of
   accepting only frames with a `method`:
   
   | Frame | Shape | Action |
   |---|---|---|
   | request | `method` + `id` | `SendMessage` → stdout (today's behaviour) |
   | notification | `method`, no `id` | `SendMessage` → no stdout (today's 
behaviour) |
   | response | `id` + `result`/`error`, no `method` | `SendResponse` → no 
stdout |
   
   Only the response case is new. Classifying in one place keeps stdin order and
   the existing concurrency limits intact.
   
   **2. Add a response path to the session.**
   
   Add `SendResponse(ctx, payload) error` to `MCPClientSession`, and to the
   `mcpSession` interface the proxy depends on (with the test fake extended to
   match). It posts the frame to the same Streamable HTTP endpoint as a request,
   but expects no JSON-RPC reply — the same shape as `SendNotification`, so it 
can
   reuse `sendWithReconnect` for session-terminated recovery.
   
   **3. Add a server-request handler that forwards to stdout.**
   
   Register a handler on the transport alongside the existing notification
   handler. On a server request the proxy writes the frame to stdout so the 
stdio
   client can answer it. The answer comes back on stdin and is routed by (1) and
   (2). Requests need to be tracked by id so a response can be matched, and an
   in-flight server request must not be counted against `maxConcurrentCalls`,
   which bounds client-originated calls only.
   
   ### Open questions
   
   1. **Should a server request be forwarded while the initialize handshake is
      still in flight?** The proxy deliberately preserves stdin order until
      `initialized` is sent. Server requests can only arrive after that point in
      practice, but I would rather state the ordering rule than assume it.
   2. **What should happen to a response whose id matches no tracked request?**
      Dropping it silently is cheap but hides a client bug; reporting it on 
stderr
      is observable but noisy. I lean towards a bounded stderr diagnostic.
   3. **Is adding `SendResponse` to the session the right layer**, or should the
      transport expose it? The session already owns reconnect and timeout
      behaviour, so putting it there keeps that policy in one place.
   4. **Do we need a bound on tracked server requests?** A server that opens
      requests and never reads the answers would otherwise grow the map.
   
   ### Tests I would add
   
   * A response frame from stdin reaches the session response path instead of
     being sent as a request, and does not print to stdout.
   * A server-initiated request is forwarded to stdout verbatim.
   * A client answer to a forwarded request reaches the server response path
     matched by id.
   * Existing behaviour is unchanged: request/notification forwarding, the
     initialize-order gate, and `maxConcurrentCalls`.
   
   Happy to adjust the shape before I write any code — in particular on
   questions 2 and 4, which change observable behaviour.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to