bhuvan-somisetty opened a new issue, #13987:
URL: https://github.com/apache/apisix/issues/13987
### Current Behavior
In `apisix/plugins/mcp-bridge.lua`, the event loop
(`ctx.mcp_bridge_proc_event_loop`) reads stdout and stderr lines from the
spawned process via `proc:stdout_read_line()` and `proc:stderr_read_line()`:
```lua
local stdout_partial, stderr_partial, need_exit
while not worker_exiting() do
repeat
local line, _
line, _, stdout_partial = proc:stdout_read_line()
if line then
local ok, err = server.transport:send(
stdout_partial and stdout_partial .. line or line
)
if not ok then
core.log.info("session ", server.session_id,
" exit, failed to send response message: ",
err)
need_exit = true
break
end
stdout_partial = nil -- luacheck: ignore
end
until not line
```
According to `ngx.pipe` semantics for `proc:stdout_read_line()`:
- On a full line read: returns `line, nil, nil` (or `line, nil`).
- On read timeout mid-line: returns `nil, "timeout", partial_data`.
When `proc:stdout_read_line()` times out mid-line (timeouts are configured
to 100ms via `proc:set_timeouts(nil, 100, 100)`):
1. `line` is `nil`, and `stdout_partial` is assigned the partial chunk.
2. Because `line` is `nil`, `if line then` is skipped and `until not line`
terminates the `repeat` loop.
3. On the subsequent iteration of `while not worker_exiting() do`,
`proc:stdout_read_line()` is invoked again:
`line, _, stdout_partial = proc:stdout_read_line()`
4. When the remaining portion of the line arrives, `proc:stdout_read_line()`
returns `line = remaining_line`, `err = nil`, and the 3rd return value is `nil`.
5. Lua's multiple assignment immediately overwrites `stdout_partial` with
`nil` (the 3rd return value of `proc:stdout_read_line()`).
6. `server.transport:send(stdout_partial and stdout_partial .. line or
line)` evaluates `stdout_partial` as `nil`, sending only `line` (the trailing
fragment). The buffered partial chunk is permanently lost, causing
truncated/corrupted JSON-RPC payloads to be sent to the MCP client.
Furthermore:
- If a line spans multiple timeout intervals, each timeout overwrites
`stdout_partial` instead of appending to it (`stdout_partial = ...` instead of
`stdout_partial = (stdout_partial or "") .. ...`).
- The same issue affects `proc:stderr_read_line()` and `stderr_partial` at
lines 109–126.
### Expected Behavior
Partial line buffers should be retained and accumulated across read
timeouts, and only cleared after successfully concatenating with the completed
line and dispatching the message to the transport.
### Error Logs
When a spawned MCP process produces output in chunks across read timeouts,
the client receives invalid/truncated JSON messages (e.g. missing opening
attributes or invalid JSON-RPC structures) and fails to parse the response.
### Steps to Reproduce
1. Configure a route with the `mcp-bridge` plugin executing a command that
yields JSON-RPC responses in chunks or with pauses longer than 100ms (the pipe
read timeout).
2. Connect an MCP client over SSE and invoke a command or tool.
3. Observe that whenever `proc:stdout_read_line()` experiences a read
timeout before receiving the newline delimiter, the initial chunk is
overwritten on the subsequent read, and only the trailing portion is sent to
the client.
### Environment
- APISIX version (run `apisix version`): 3.19.0 (master @ 271e799)
- Operating system (run `uname -a`): Linux / POSIX (all platforms supporting
`ngx.pipe`)
- OpenResty / Nginx version (run `openresty -V` or `nginx -V`): OpenResty
1.25+
- etcd version, if relevant: N/A
- APISIX Dashboard version, if relevant: N/A
- Plugin runner version, for issues related to plugin runners: N/A
- LuaRocks version, for installation issues: N/A
--
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]