diff --git a/src/agents/pi-bundle-mcp-runtime.test.ts b/src/agents/pi-bundle-mcp-runtime.test.ts index dafaedc1062..798d8d144bf 100644 --- a/src/agents/pi-bundle-mcp-runtime.test.ts +++ b/src/agents/pi-bundle-mcp-runtime.test.ts @@ -162,6 +162,25 @@ describe("session MCP runtime", () => { expect(activeLeases).toBe(0); }); + it("releases a runtime lease when catalog materialization fails", async () => { + let activeLeases = 0; + const runtime = { + ...makeRuntime([{ toolName: "bundle_probe", description: "Bundle MCP probe" }]), + acquireLease: () => { + activeLeases += 1; + return () => { + activeLeases -= 1; + }; + }, + getCatalog: async () => { + throw new Error("catalog failed"); + }, + }; + + await expect(materializeBundleMcpToolsForRun({ runtime })).rejects.toThrow("catalog failed"); + expect(activeLeases).toBe(0); + }); + it("reuses repeated materialization and recreates after explicit disposal", async () => { const created: SessionMcpRuntime[] = []; const disposed: string[] = []; diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index 2befcd7602e..a7ff53b0c6a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -20,6 +20,7 @@ import { resolveAttemptFsWorkspaceOnly, resolveEmbeddedAgentStreamFn, resolveUnknownToolGuardThreshold, + shouldCreateBundleMcpRuntimeForAttempt, resolvePromptBuildHookResult, resolvePromptModeForSession, shouldStripBootstrapFromEmbeddedContext, @@ -72,6 +73,34 @@ describe("applyEmbeddedAttemptToolsAllow", () => { }); }); +describe("shouldCreateBundleMcpRuntimeForAttempt", () => { + it("skips bundle MCP when tools are disabled or unavailable", () => { + expect(shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: false })).toBe(false); + expect(shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true, disableTools: true })).toBe( + false, + ); + }); + + it("creates bundle MCP only when the allowlist can reach bundle MCP tool names", () => { + expect(shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true })).toBe(true); + expect(shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true, toolsAllow: [] })).toBe( + true, + ); + expect( + shouldCreateBundleMcpRuntimeForAttempt({ + toolsEnabled: true, + toolsAllow: ["memory_search", "memory_get"], + }), + ).toBe(false); + expect( + shouldCreateBundleMcpRuntimeForAttempt({ + toolsEnabled: true, + toolsAllow: ["strict__strict_probe"], + }), + ).toBe(true); + }); +}); + describe("resolvePromptBuildHookResult", () => { function createLegacyOnlyHookRunner() { return { diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 15005d1604e..1b9cdcc5fee 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -466,7 +466,7 @@ export function applyEmbeddedAttemptToolsAllow( return tools.filter((tool) => allowSet.has(tool.name)); } -function shouldCreateBundleMcpRuntimeForAttempt(params: { +export function shouldCreateBundleMcpRuntimeForAttempt(params: { toolsEnabled: boolean; disableTools?: boolean; toolsAllow?: string[];