Skip to content

Commit 327f49a

Browse files
author
scotthuang
committed
fix(test): stabilize bundle MCP catalog timeout shard tests
1 parent 3e0ed16 commit 327f49a

1 file changed

Lines changed: 91 additions & 81 deletions

File tree

src/agents/agent-bundle-mcp-runtime.test.ts

Lines changed: 91 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -661,100 +661,110 @@ describe("session MCP runtime", () => {
661661
expect(activeLeases).toBe(0);
662662
});
663663

664-
it("keeps MCP tools/list responses that exceed the connection timeout but finish within the internal catalog timeout", async () => {
665-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "bundle-mcp-slow-listtools-"));
666-
const serverPath = path.join(tempDir, "slow-list-tools.mjs");
667-
const logPath = path.join(tempDir, "server.log");
668-
testing.setBundleMcpCatalogListTimeoutMsForTest(700);
669-
await writeListToolsMcpServer({
670-
filePath: serverPath,
671-
logPath,
672-
delayMs: 250,
673-
});
664+
describe.sequential("bundle MCP catalog list timeouts", () => {
665+
it("keeps MCP tools/list responses that exceed the connection timeout but finish within the internal catalog timeout", async () => {
666+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "bundle-mcp-slow-listtools-"));
667+
const serverPath = path.join(tempDir, "slow-list-tools.mjs");
668+
const logPath = path.join(tempDir, "server.log");
669+
const connectionTimeoutMs = 400;
670+
const delayMs = 600;
671+
testing.setBundleMcpCatalogListTimeoutMsForTest(2_000);
672+
await writeListToolsMcpServer({
673+
filePath: serverPath,
674+
logPath,
675+
delayMs,
676+
});
674677

675-
const runtime = await getOrCreateSessionMcpRuntime({
676-
sessionId: "session-slow-listtools-server-timeout",
677-
sessionKey: "agent:test:session-slow-listtools-server-timeout",
678-
workspaceDir: "/workspace",
679-
cfg: {
680-
mcp: {
681-
servers: {
682-
slowListTools: {
683-
command: process.execPath,
684-
args: [serverPath],
685-
connectionTimeoutMs: 150,
678+
const runtime = await getOrCreateSessionMcpRuntime({
679+
sessionId: "session-slow-listtools-server-timeout",
680+
sessionKey: "agent:test:session-slow-listtools-server-timeout",
681+
workspaceDir: "/workspace",
682+
cfg: {
683+
mcp: {
684+
servers: {
685+
slowListTools: {
686+
command: process.execPath,
687+
args: [serverPath],
688+
connectionTimeoutMs,
689+
},
686690
},
687691
},
688692
},
689-
},
690-
});
693+
});
691694

692-
try {
693-
const catalog = await runtime.getCatalog();
695+
try {
696+
const catalog = await runtime.getCatalog();
694697

695-
expect(catalog.tools.map((tool) => tool.toolName)).toEqual(["slow_tool"]);
696-
expect(catalog.servers.slowListTools).toMatchObject({
697-
serverName: "slowListTools",
698-
toolCount: 1,
699-
});
700-
await expect(fs.readFile(logPath, "utf8")).resolves.toContain("delay tools/list 250");
701-
} finally {
702-
await runtime.dispose();
703-
await fs.rm(tempDir, { recursive: true, force: true });
704-
}
705-
});
698+
expect(catalog.tools.map((tool) => tool.toolName)).toEqual(["slow_tool"]);
699+
expect(catalog.servers.slowListTools).toMatchObject({
700+
serverName: "slowListTools",
701+
toolCount: 1,
702+
});
703+
await waitForFileText(
704+
logPath,
705+
`delay tools/list ${delayMs}`,
706+
LIST_TOOLS_SERVER_LOG_TIMEOUT_MS,
707+
);
708+
} finally {
709+
testing.setBundleMcpCatalogListTimeoutMsForTest();
710+
await runtime.dispose();
711+
await fs.rm(tempDir, { recursive: true, force: true });
712+
}
713+
});
706714

707-
it("times out default-config hung bundle MCP tools/list using the internal catalog timeout", async () => {
708-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "bundle-mcp-listtools-timeout-"));
709-
const serverPath = path.join(tempDir, "hanging-list-tools.mjs");
710-
const logPath = path.join(tempDir, "server.log");
711-
testing.setBundleMcpCatalogListTimeoutMsForTest(100);
712-
await writeListToolsMcpServer({ filePath: serverPath, logPath, hang: true });
715+
it("times out default-config hung bundle MCP tools/list using the internal catalog timeout", async () => {
716+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "bundle-mcp-listtools-timeout-"));
717+
const serverPath = path.join(tempDir, "hanging-list-tools.mjs");
718+
const logPath = path.join(tempDir, "server.log");
719+
testing.setBundleMcpCatalogListTimeoutMsForTest(100);
720+
await writeListToolsMcpServer({ filePath: serverPath, logPath, hang: true });
713721

714-
const runtime = await getOrCreateSessionMcpRuntime({
715-
sessionId: "session-listtools-server-timeout",
716-
sessionKey: "agent:test:session-listtools-server-timeout",
717-
workspaceDir: "/workspace",
718-
cfg: {
719-
mcp: {
720-
servers: {
721-
hangingListTools: {
722-
command: process.execPath,
723-
args: [serverPath],
722+
const runtime = await getOrCreateSessionMcpRuntime({
723+
sessionId: "session-listtools-server-timeout",
724+
sessionKey: "agent:test:session-listtools-server-timeout",
725+
workspaceDir: "/workspace",
726+
cfg: {
727+
mcp: {
728+
servers: {
729+
hangingListTools: {
730+
command: process.execPath,
731+
args: [serverPath],
732+
},
724733
},
725734
},
726735
},
727-
},
728-
});
729-
const catalogResult = runtime.getCatalog().then(
730-
(catalog) => ({ status: "resolved" as const, catalog }),
731-
(error: unknown) => ({ status: "rejected" as const, error }),
732-
);
733-
734-
try {
735-
await waitForFileText(logPath, "recv tools/list", LIST_TOOLS_SERVER_LOG_TIMEOUT_MS);
736-
const result = await Promise.race([
737-
catalogResult,
738-
new Promise<{ status: "pending" }>((resolve) => {
739-
setTimeout(() => resolve({ status: "pending" }), LIST_TOOLS_TEST_DEADLINE_MS);
740-
}),
741-
]);
736+
});
737+
const catalogResult = runtime.getCatalog().then(
738+
(catalog) => ({ status: "resolved" as const, catalog }),
739+
(error: unknown) => ({ status: "rejected" as const, error }),
740+
);
742741

743-
expect(result.status).toBe("resolved");
744-
if (result.status === "resolved") {
745-
expect(result.catalog.tools).toEqual([]);
746-
expect(result.catalog.servers).toEqual({});
742+
try {
743+
await waitForFileText(logPath, "recv tools/list", LIST_TOOLS_SERVER_LOG_TIMEOUT_MS);
744+
const result = await Promise.race([
745+
catalogResult,
746+
new Promise<{ status: "pending" }>((resolve) => {
747+
setTimeout(() => resolve({ status: "pending" }), LIST_TOOLS_TEST_DEADLINE_MS);
748+
}),
749+
]);
750+
751+
expect(result.status).toBe("resolved");
752+
if (result.status === "resolved") {
753+
expect(result.catalog.tools).toEqual([]);
754+
expect(result.catalog.servers).toEqual({});
755+
}
756+
} finally {
757+
testing.setBundleMcpCatalogListTimeoutMsForTest();
758+
await runtime.dispose();
759+
await Promise.race([
760+
catalogResult,
761+
new Promise((resolve) => {
762+
setTimeout(resolve, 1000);
763+
}),
764+
]);
765+
await fs.rm(tempDir, { recursive: true, force: true });
747766
}
748-
} finally {
749-
await runtime.dispose();
750-
await Promise.race([
751-
catalogResult,
752-
new Promise((resolve) => {
753-
setTimeout(resolve, 1000);
754-
}),
755-
]);
756-
await fs.rm(tempDir, { recursive: true, force: true });
757-
}
767+
});
758768
});
759769

760770
it("records diagnostics when tools/list returns an invalid tool schema", async () => {

0 commit comments

Comments
 (0)