Skip to content

Commit 945bb02

Browse files
fix(local-mcp): don't drop relay requests fired before session assignment
broadcastEvent silently returned when this.session was still null, but the MCP relay server starts listening (and the client subprocess can hit it with its initialize handshake) before newSession() resolves and assigns this.session. The earliest relay request for every configured server - almost always the only initialize attempt an MCP client makes - was lost at the source instead of reaching the desktop.
1 parent a19be3d commit 945bb02

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

packages/agent/src/server/agent-server.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,56 @@ describe("AgentServer HTTP Mode", () => {
12401240
});
12411241
});
12421242

1243+
describe("broadcastEvent", () => {
1244+
function exposeBroadcastEvent(testServer: AgentServer) {
1245+
return testServer as unknown as {
1246+
eventStreamSender: {
1247+
enqueue: ReturnType<typeof vi.fn>;
1248+
stop: ReturnType<typeof vi.fn>;
1249+
} | null;
1250+
pendingEvents: Record<string, unknown>[];
1251+
session: unknown;
1252+
broadcastEvent(event: Record<string, unknown>): void;
1253+
};
1254+
}
1255+
1256+
it("enqueues and buffers events raised before a session is assigned", () => {
1257+
// Regression: an MCP relay request can fire the instant the client
1258+
// subprocess starts, ahead of session assignment. broadcastEvent must
1259+
// not silently drop it.
1260+
const testServer = exposeBroadcastEvent(createServer());
1261+
testServer.eventStreamSender = {
1262+
enqueue: vi.fn(),
1263+
stop: vi.fn(async () => {}),
1264+
};
1265+
testServer.session = null;
1266+
1267+
const event = {
1268+
type: "mcp_request",
1269+
requestId: "req-1",
1270+
server: "slack",
1271+
};
1272+
testServer.broadcastEvent(event);
1273+
1274+
expect(testServer.eventStreamSender.enqueue).toHaveBeenCalledWith(event);
1275+
expect(testServer.pendingEvents).toEqual([event]);
1276+
});
1277+
1278+
it("buffers events with no event stream sender configured and no session", () => {
1279+
const testServer = exposeBroadcastEvent(createServer());
1280+
testServer.session = null;
1281+
1282+
const event = {
1283+
type: "mcp_request",
1284+
requestId: "req-1",
1285+
server: "slack",
1286+
};
1287+
expect(() => testServer.broadcastEvent(event)).not.toThrow();
1288+
1289+
expect(testServer.pendingEvents).toEqual([event]);
1290+
});
1291+
});
1292+
12431293
describe("GET /events", () => {
12441294
it("returns 401 without authorization header", async () => {
12451295
await createServer().start();

packages/agent/src/server/agent-server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4263,14 +4263,14 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
42634263
}
42644264

42654265
private broadcastEvent(event: Record<string, unknown>): void {
4266-
if (!this.session) return;
4267-
42684266
this.eventStreamSender?.enqueue(event);
42694267

42704268
if (this.session?.sseController) {
42714269
this.sendSseEvent(this.session.sseController, event);
42724270
} else {
4273-
// Buffer events during initialization (sseController not yet attached)
4271+
// Buffers events raised before a session exists yet (e.g. an MCP relay
4272+
// request fired the instant the client subprocess starts, ahead of
4273+
// `this.session` assignment) or before its SSE controller attaches.
42744274
this.pendingEvents.push(event);
42754275
}
42764276
}

0 commit comments

Comments
 (0)