Skip to content

fix(mcp): local fallback exposes all 7 IMPLEMENTED_TOOLS, not 4 (#234)#283

Merged
rohitg00 merged 1 commit into
mainfrom
fix/local-fallback-tool-count
May 11, 2026
Merged

fix(mcp): local fallback exposes all 7 IMPLEMENTED_TOOLS, not 4 (#234)#283
rohitg00 merged 1 commit into
mainfrom
fix/local-fallback-tool-count

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented May 11, 2026

Reproduction

When no agentmemory server is reachable on localhost:3111, the @agentmemory/mcp shim showed exactly 4 tools in MCP clients (Cursor, Roo Code, etc.):

memory_recall, memory_save, memory_sessions, memory_smart_search

Should be 7 — those four plus memory_export, memory_audit, memory_governance_delete. The InMemoryKV that backs local mode implements all seven operations; only four were advertised.

Reproduced live via stdio against a downed server before this PR:

$ {
    printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
    printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
    printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
    sleep 1
  } | node dist/standalone.mjs 2>/dev/null | grep -o '"name":"memory_[^"]*"' | sort -u
"name":"memory_recall"
"name":"memory_save"
"name":"memory_sessions"
"name":"memory_smart_search"

Same four tools showing in @rohitghumare's Cursor against a downed server, matching the bug @jcalfee originally reported in #234.

Root cause

The local-fallback branch of tools/list in src/mcp/standalone.ts intersected the shim's IMPLEMENTED_TOOLS (7) with getVisibleTools():

const fallback = getVisibleTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name));

getVisibleTools() honors the shim process's AGENTMEMORY_TOOLS env var. Default unset → returns 8 ESSENTIAL_TOOLS (the server's default "core" tier). Intersection:

ESSENTIAL_TOOLS (8):
  memory_save, memory_recall, memory_consolidate, memory_smart_search,
  memory_sessions, memory_diagnose, memory_lesson_save, memory_reflect

IMPLEMENTED_TOOLS (7):
  memory_save, memory_recall, memory_smart_search, memory_sessions,
  memory_export, memory_audit, memory_governance_delete

ESSENTIAL_TOOLS ∩ IMPLEMENTED_TOOLS = 4:
  memory_save, memory_recall, memory_smart_search, memory_sessions

Exactly the four tools that showed up.

The intersection was meaningless from the start: the shim dispatches by IMPLEMENTED_TOOLS, not by ESSENTIAL_TOOLS. The shim's local-mode capabilities are fixed by its in-memory KV, not by an env knob that's only meant to filter the server's catalog.

Fix

Switch the filter source from getVisibleTools() to getAllTools():

const fallback = getAllTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name));

IMPLEMENTED_TOOLS.has(t.name) still picks the right 7 names, but now from the unfiltered universe instead of an env-filtered subset.

Also refactored the tools/list handler out of the inline createStdioTransport callback into an exported handleToolsList() for direct testability. Added a regression test asserting the local-fallback path returns exactly the 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS=core or unset.

Verified

  • Live stdio repro post-fix: returns all 7 names (memory_save, memory_recall, memory_smart_search, memory_sessions, memory_export, memory_audit, memory_governance_delete).
  • npm test — 860 / 860 (859 baseline + 1 regression test).
  • npm run build — tsdown clean.

Next

Cut v0.9.8 after this lands and publish. v0.9.7's local-mode count was wrong; v0.9.8 restores the documented 7-tool local fallback.

Summary by CodeRabbit

  • Bug Fixes

    • Improved tool listing reliability with better fallback support when remote services are unreachable.
  • Tests

    • Added test coverage for tool listing in offline scenarios.

Review Change Stack

Reported live in Cursor: agentmemory MCP server shows exactly four
tools — memory_recall, memory_save, memory_sessions,
memory_smart_search — when no agentmemory server is running on
localhost:3111. Should be seven: those four plus memory_export,
memory_audit, memory_governance_delete (the full InMemoryKV-backed
local capability set the shim ships).

Root cause: the local-fallback branch of tools/list in
src/mcp/standalone.ts intersected the shim's IMPLEMENTED_TOOLS set
(7) with getVisibleTools(), which honors the shim's own
AGENTMEMORY_TOOLS env. Default env is unset, so getVisibleTools()
returns the 8 ESSENTIAL_TOOLS (the server's default "core" tier).
The 4 visible tools are exactly that intersection:

    ESSENTIAL_TOOLS  ∩  IMPLEMENTED_TOOLS  =
    {memory_save, memory_recall, memory_smart_search, memory_sessions}

The intersection was meaningless: the shim doesn't dispatch by
ESSENTIAL_TOOLS, it dispatches by IMPLEMENTED_TOOLS. Whatever
AGENTMEMORY_TOOLS the SERVER honored shouldn't shape the SHIM's
local-fallback list — the shim's local capabilities are fixed by
its in-memory KV, not by an env knob.

Fix: switch the filter source from getVisibleTools() to
getAllTools(). The filter through IMPLEMENTED_TOOLS still picks the
right 7 names, but now from the unfiltered universe of tool
definitions instead of the env-filtered subset.

Refactored the tools/list handler out of the inline createStdioTransport
callback into an exported handleToolsList() so it's directly testable.
Added a regression test that asserts the local-fallback path returns
exactly the 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS=core
(default) or unset.

Verified live via stdio against a downed server: shim now returns
all 7 names. Verified the previous bug repro (4 names) is gone.

860 / 860 tests pass on the branch (859 baseline + 1 regression test).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 11, 2026 1:32pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

The PR centralizes MCP tools/list handling by extracting logic into a new exported handleToolsList() function. It replaces getVisibleTools with getAllTools for tool enumeration, implements proxy-mode fallback behavior with debug logging and handle invalidation, wires the dispatcher to the new function, and validates the local fallback path with a new test.

Changes

MCP tools/list Handler Centralization

Layer / File(s) Summary
Import Update
src/mcp/standalone.ts
Replace tools-registry import from getVisibleTools to getAllTools for local tool enumeration source.
Handler Implementation
src/mcp/standalone.ts
Implement exported handleToolsList() that proxies to /agentmemory/mcp/tools in proxy mode with debug logging, falls back locally on error or invalid response (invalidating handle on failure), and filters enumerated tools by IMPLEMENTED_TOOLS.
Dispatcher Integration
src/mcp/standalone.ts
Update stdio transport tools/list dispatcher case to delegate to handleToolsList() instead of containing logic inline.
Fallback Path Test
test/mcp-standalone-proxy.test.ts
Add test asserting that when server is unreachable, handleToolsList returns all 7 IMPLEMENTED_TOOLS locally regardless of AGENTMEMORY_TOOLS environment variable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • rohitg00/agentmemory#234: This PR directly addresses the tools/list handling bug by extracting handleToolsList() to centralize proxy vs. local fallback logic and switch to getAllTools for complete tool enumeration.

Possibly related PRs

  • rohitg00/agentmemory#272: Both centralize proxy vs. local-fallback logic in src/mcp/standalone.ts for tools/list handling.
  • rohitg00/agentmemory#270: Both modify tools/list behavior to proxy /agentmemory/mcp/tools and fall back to local IMPLEMENTED_TOOLS enumeration.
  • rohitg00/agentmemory#161: Both add proxy-aware MCP handling in src/mcp/standalone.ts with proxying of MCP tool endpoints and fallback infrastructure.

Poem

🐰 A tools list refactored clean,
Now centralized and lean,
Proxy tries, then fallback glows,
All seven tools it knows,
Handle dancing to and fro! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the main fix: changing local fallback to expose all 7 IMPLEMENTED_TOOLS instead of 4, which directly matches the bug fix and refactoring described in the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/local-fallback-tool-count

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/mcp-standalone-proxy.test.ts`:
- Around line 250-274: The test leaks process.env["AGENTMEMORY_TOOLS"] if an
assertion fails; wrap the environment mutation and cleanup around the
handleToolsList calls in a try/finally so AGENTMEMORY_TOOLS is always restored:
save the original value into a variable, set/delete
process.env["AGENTMEMORY_TOOLS"] as needed before calling handleToolsList (and
resetHandleForTests()/installFetch() usage stays the same), then restore the
original value in a finally block to guarantee isolation for subsequent tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8b16a17-bee7-4dce-9ad4-695e0a537b5e

📥 Commits

Reviewing files that changed from the base of the PR and between 1813d08 and 4d786f0.

📒 Files selected for processing (2)
  • src/mcp/standalone.ts
  • test/mcp-standalone-proxy.test.ts

Comment on lines +250 to +274
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"];
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure AGENTMEMORY_TOOLS is restored via finally for test isolation.

process.env["AGENTMEMORY_TOOLS"] is only cleaned up at the end of the happy path. If an assertion throws earlier, this can leak into later tests and create flakiness.

💡 Suggested patch
 it("local fallback tools/list returns all 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS env (`#234`)", async () => {
+  const previousToolsEnv = process.env["AGENTMEMORY_TOOLS"];
   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"];
+  try {
+    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);
+  } finally {
+    if (previousToolsEnv === undefined) {
+      delete process.env["AGENTMEMORY_TOOLS"];
+    } else {
+      process.env["AGENTMEMORY_TOOLS"] = previousToolsEnv;
+    }
+  }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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("local fallback tools/list returns all 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS env (`#234`)", async () => {
const previousToolsEnv = process.env["AGENTMEMORY_TOOLS"];
const { handleToolsList } = await import("../src/mcp/standalone.js");
installFetch(() => {
throw new Error("ECONNREFUSED");
});
try {
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);
} finally {
if (previousToolsEnv === undefined) {
delete process.env["AGENTMEMORY_TOOLS"];
} else {
process.env["AGENTMEMORY_TOOLS"] = previousToolsEnv;
}
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/mcp-standalone-proxy.test.ts` around lines 250 - 274, The test leaks
process.env["AGENTMEMORY_TOOLS"] if an assertion fails; wrap the environment
mutation and cleanup around the handleToolsList calls in a try/finally so
AGENTMEMORY_TOOLS is always restored: save the original value into a variable,
set/delete process.env["AGENTMEMORY_TOOLS"] as needed before calling
handleToolsList (and resetHandleForTests()/installFetch() usage stays the same),
then restore the original value in a finally block to guarantee isolation for
subsequent tests.

@rohitg00 rohitg00 merged commit 0a6c399 into main May 11, 2026
5 checks passed
@rohitg00 rohitg00 deleted the fix/local-fallback-tool-count branch May 11, 2026 13:46
rohitg00 added a commit that referenced this pull request May 11, 2026
)

Single-issue release: PR #283 fixed the local-mode tools/list path
in @agentmemory/mcp to expose all 7 IMPLEMENTED_TOOLS instead of the
4-tool intersection ESSENTIAL_TOOLS ∩ IMPLEMENTED_TOOLS that bug
manifested as in MCP clients (Cursor / Roo Code / others) when no
agentmemory server was reachable on localhost:3111.

Bumping 0.9.7 -> 0.9.8 across the 8 standard files (package.json,
packages/mcp/package.json, plugin/.claude-plugin/plugin.json,
src/version.ts, src/types.ts ExportData literal,
src/functions/export-import.ts supportedVersions, the export
round-trip test expectation, and CHANGELOG.md).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant