diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts index 6026f715..ae1f4f36 100644 --- a/src/mcp/standalone.ts +++ b/src/mcp/standalone.ts @@ -2,7 +2,7 @@ import { InMemoryKV } from "./in-memory-kv.js"; import { createStdioTransport } from "./transport.js"; -import { getVisibleTools } from "./tools-registry.js"; +import { getAllTools } from "./tools-registry.js"; import { getStandalonePersistPath } from "../config.js"; import { VERSION } from "../version.js"; import { generateId } from "../state/schema.js"; @@ -354,6 +354,57 @@ export async function handleToolCall( return handleLocal(validated, kvInstance); } +export async function handleToolsList(): Promise<{ tools: unknown[] }> { + const debug = process.env["AGENTMEMORY_DEBUG"] === "1" || process.env["AGENTMEMORY_DEBUG"] === "true"; + const handle = await resolveHandle(); + announceMode(handle); + if (debug) { + process.stderr.write( + `[@agentmemory/mcp] tools/list: handle.mode=${handle.mode}${handle.mode === "proxy" ? ` baseUrl=${handle.baseUrl}` : ""}\n`, + ); + } + if (handle.mode === "proxy") { + try { + const remote = (await handle.call("/agentmemory/mcp/tools", { + method: "GET", + })) as { tools?: unknown } | null; + if (debug) { + const shape = remote === null + ? "null" + : typeof remote !== "object" + ? typeof remote + : `keys=${Object.keys(remote as object).join(",")} toolsType=${Array.isArray((remote as { tools?: unknown }).tools) ? `array(len=${((remote as { tools: unknown[] }).tools).length})` : typeof (remote as { tools?: unknown }).tools}`; + process.stderr.write( + `[@agentmemory/mcp] tools/list: remote response shape: ${shape}\n`, + ); + } + if (remote && Array.isArray(remote.tools)) { + if (debug) { + process.stderr.write( + `[@agentmemory/mcp] tools/list: returning ${remote.tools.length} tools from server\n`, + ); + } + return { tools: remote.tools }; + } + process.stderr.write( + `[@agentmemory/mcp] tools/list: server returned unexpected shape (no .tools array); falling back to local IMPLEMENTED_TOOLS list. Set AGENTMEMORY_DEBUG=1 to inspect response.\n`, + ); + } catch (err) { + process.stderr.write( + `[@agentmemory/mcp] tools/list proxy failed: ${err instanceof Error ? err.message : String(err)}; falling back to local list\n`, + ); + invalidateHandle(); + } + } + const fallback = getAllTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name)); + if (debug) { + process.stderr.write( + `[@agentmemory/mcp] tools/list: returning ${fallback.length} local fallback tools (${fallback.map((t) => t.name).join(",")})\n`, + ); + } + return { tools: fallback }; +} + const transport = createStdioTransport(async (method, params) => { switch (method) { case "initialize": @@ -369,56 +420,8 @@ const transport = createStdioTransport(async (method, params) => { case "notifications/initialized": return {}; - case "tools/list": { - const debug = process.env["AGENTMEMORY_DEBUG"] === "1" || process.env["AGENTMEMORY_DEBUG"] === "true"; - const handle = await resolveHandle(); - announceMode(handle); - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: handle.mode=${handle.mode}${handle.mode === "proxy" ? ` baseUrl=${handle.baseUrl}` : ""}\n`, - ); - } - if (handle.mode === "proxy") { - try { - const remote = (await handle.call("/agentmemory/mcp/tools", { - method: "GET", - })) as { tools?: unknown } | null; - if (debug) { - const shape = remote === null - ? "null" - : typeof remote !== "object" - ? typeof remote - : `keys=${Object.keys(remote as object).join(",")} toolsType=${Array.isArray((remote as { tools?: unknown }).tools) ? `array(len=${((remote as { tools: unknown[] }).tools).length})` : typeof (remote as { tools?: unknown }).tools}`; - process.stderr.write( - `[@agentmemory/mcp] tools/list: remote response shape: ${shape}\n`, - ); - } - if (remote && Array.isArray(remote.tools)) { - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: returning ${remote.tools.length} tools from server\n`, - ); - } - return { tools: remote.tools }; - } - process.stderr.write( - `[@agentmemory/mcp] tools/list: server returned unexpected shape (no .tools array); falling back to local IMPLEMENTED_TOOLS list. Set AGENTMEMORY_DEBUG=1 to inspect response.\n`, - ); - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] tools/list proxy failed: ${err instanceof Error ? err.message : String(err)}; falling back to local list\n`, - ); - invalidateHandle(); - } - } - const fallback = getVisibleTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name)); - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: returning ${fallback.length} local fallback tools (${fallback.map((t) => t.name).join(",")})\n`, - ); - } - return { tools: fallback }; - } + case "tools/list": + return handleToolsList(); case "tools/call": { const toolName = params.name as string; diff --git a/test/mcp-standalone-proxy.test.ts b/test/mcp-standalone-proxy.test.ts index debc7eb9..6522691d 100644 --- a/test/mcp-standalone-proxy.test.ts +++ b/test/mcp-standalone-proxy.test.ts @@ -247,6 +247,32 @@ describe("@agentmemory/mcp standalone — server proxy (issue #159)", () => { expect(joined).toMatch(/AGENTMEMORY_FORCE_PROXY/); }); + it("local fallback tools/list returns all 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS env (#234)", async () => { + const { handleToolsList } = await import("../src/mcp/standalone.js"); + installFetch(() => { + throw new Error("ECONNREFUSED"); + }); + delete process.env["AGENTMEMORY_TOOLS"]; + const before = await handleToolsList(); + const beforeTools = before.tools as Array<{ name: string }>; + expect(beforeTools.map((t) => t.name).sort()).toEqual([ + "memory_audit", + "memory_export", + "memory_governance_delete", + "memory_recall", + "memory_save", + "memory_sessions", + "memory_smart_search", + ]); + expect(beforeTools).toHaveLength(7); + + resetHandleForTests(); + process.env["AGENTMEMORY_TOOLS"] = "core"; + const core = await handleToolsList(); + expect((core.tools as unknown[]).length).toBe(7); + delete process.env["AGENTMEMORY_TOOLS"]; + }); + it("AGENTMEMORY_PROBE_TIMEOUT_MS overrides the default probe timeout", async () => { process.env["AGENTMEMORY_PROBE_TIMEOUT_MS"] = "50"; let probeStarted = 0;