Part of #408.
Why
send() (bin/unified-mcp-server.mjs:110) writes every response to process.stdout. That is correct for stdio, where one process serves one client, and impossible for HTTP, where one process serves many clients concurrently and each response must go back down its own request.
This is the prerequisite for the HTTP transport — nothing user-visible changes.
What
Carry the response sink per-request with AsyncLocalStorage:
const responseSink = new AsyncLocalStorage();
function send(obj) {
const sink = responseSink.getStore();
if (sink) sink(obj);
else process.stdout.write(JSON.stringify(obj) + '\n');
}
Chosen over threading a sink argument through handleRequest() because all 15 response call sites funnel through send/sendResult/sendError — the async-local approach leaves every one of them untouched, so the diff is three functions rather than fifteen call sites plus a signature change.
Acceptance
Part of #408.
Why
send()(bin/unified-mcp-server.mjs:110) writes every response toprocess.stdout. That is correct for stdio, where one process serves one client, and impossible for HTTP, where one process serves many clients concurrently and each response must go back down its own request.This is the prerequisite for the HTTP transport — nothing user-visible changes.
What
Carry the response sink per-request with
AsyncLocalStorage:Chosen over threading a
sinkargument throughhandleRequest()because all 15 response call sites funnel throughsend/sendResult/sendError— the async-local approach leaves every one of them untouched, so the diff is three functions rather than fifteen call sites plus a signature change.Acceptance
send()falls back to stdout when no sink is set (i.e. stdio mode)handleRequestcalls with different sinks do not cross responses