Skip to content

fix(mcp): preserve recall format option#489

Closed
honor2030 wants to merge 1 commit into
rohitg00:mainfrom
honor2030:fix/mcp-recall-format
Closed

fix(mcp): preserve recall format option#489
honor2030 wants to merge 1 commit into
rohitg00:mainfrom
honor2030:fix/mcp-recall-format

Conversation

@honor2030
Copy link
Copy Markdown
Contributor

@honor2030 honor2030 commented May 18, 2026

Summary

  • Preserve the optional format argument when the standalone MCP shim validates memory_recall / memory_smart_search calls.
  • Forward the validated format through the proxy smart-search request and keep invalid values rejected at the boundary.

Test Plan

  • RED: npm test -- test/mcp-standalone.test.ts failed before the implementation when format was omitted from the proxied body.
  • GREEN: npm test → 91 files / 1009 tests passed.
  • GREEN: git diff --check

Closes #440

Summary by CodeRabbit

Release Notes

  • New Features
    • Added format customization to memory search operations, allowing users to choose between "compact" and "full" result formats for greater control over search output detail levels.
    • Format option defaults to "compact" when not specified.
    • Format preference is consistently applied across all search pathways.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

@honor2030 is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR adds support for an optional format search parameter to the MCP memory search tools, enabling callers to request full or compact response payloads. The change introduces a new type, updates argument validation, forwards the parameter through both proxy and local fallback execution paths, and verifies behavior with two new tests.

Changes

Format Parameter Support for Memory Search

Layer / File(s) Summary
Format type and argument validation
src/mcp/standalone.ts
Introduces SearchFormat type and parseSearchFormat helper to normalize and validate the format argument. Extends the shared Validated tool-arguments interface with an optional format field, and parses the argument during tool validation for memory_recall and memory_smart_search.
Proxy request format forwarding
src/mcp/standalone.ts
Updates the proxy execution path to conditionally include the parsed format field in the POST request body to /agentmemory/smart-search, passing it through from the MCP tool call to the underlying search service.
Local fallback format handling
src/mcp/standalone.ts
Updates the in-process fallback to default format to "compact" when not provided, derive the response mode field from the chosen format, and include both format and mode in the text response payload alongside results.
Test coverage for format parameter
test/mcp-standalone.test.ts
Adds two test cases: one verifies the local fallback preserves format and sets mode correctly, and another stubs the livez probe to assert the proxy path forwards format: "full" in POST request bodies for both memory_recall and memory_smart_search.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly Related PRs

  • rohitg00/agentmemory#150: Both PRs add support for a format argument in the memory_recall/smart-search retrieval flow (main PR updates standalone parsing/proxy/fallback response payloads, while retrieved PR extends MCP server/tool payloads to validate and propagate format).
  • rohitg00/agentmemory#122: Both PRs modify src/mcp/standalone.ts's handleToolCall tool-handling path for memory tools—fix: persist memories to disk immediately after memory_save #122 by changing handler KV injection/persist behavior, and the main PR by extending memory_recall/memory_smart_search argument parsing and response/proxy forwarding to include the new format field.
  • rohitg00/agentmemory#161: The main PR's updates to memory_recall/memory_smart_search in src/mcp/standalone.ts to forward a new format argument to the /agentmemory/smart-search proxy path build directly on the retrieved PR's standalone REST-proxy routing and fallback behavior for the same tools.

Poem

🐰 A format so fine, now both full and compact,
Through proxy and fallback, the search tools attract,
No more buried content—the verbosity flows,
From MCP to memory, wherever it goes!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR partially addresses issue #440 by preserving the format parameter in validation and proxy forwarding, but does not fully resolve all three defects (separate endpoints and expandIds extraction remain incomplete). Complete the fix by implementing separate handlers for memory_recall and memory_smart_search, routing them to correct endpoints (/agentmemory/search vs /agentmemory/smart-search) and extracting expandIds for smart-search.
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 (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: preserving the format option in MCP memory_recall calls, which is the core fix being applied.
Out of Scope Changes check ✅ Passed All changes are directly scoped to preserving the format parameter in memory_recall/memory_smart_search validation and proxy forwarding, with corresponding test coverage added.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mcp/standalone.ts (1)

241-246: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recall local fallback default should not be hardcoded to compact.

Line 245 defaults both memory_recall and memory_smart_search to "compact". For recall semantics, defaulting to full content is expected.

Suggested fix
-      const format = v.format ?? "compact";
+      const format =
+        v.format ?? (v.tool === "memory_recall" ? "full" : "compact");
🤖 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 `@src/mcp/standalone.ts` around lines 241 - 246, The default format is
incorrectly hardcoded to "compact" for both "memory_recall" and
"memory_smart_search"; update the logic in the switch branch so that when
handling the "memory_recall" case you default format to "full" (e.g., set format
= v.format ?? "full") while preserving "compact" as the default for
"memory_smart_search" (keep format = v.format ?? "compact" for that case);
locate and modify the code that assigns the variable format inside the case
block that handles the "memory_recall" and "memory_smart_search" branches so it
uses the operation type to choose the appropriate default.
🤖 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 `@src/mcp/standalone.ts`:
- Around line 67-80: The parseSearchFormat function currently returns undefined
for invalid inputs and doesn't accept the "narrative" format; update the
SearchFormat union to include "narrative" (or create a separate
RecallSearchFormat if domain-specific) and change parseSearchFormat to validate
strictly at the boundary by rejecting invalid values (e.g., throw an Error or
return a Result/Error object) instead of returning undefined; keep the existing
trimming/lowercasing and normalize "compact", "full", and the new "narrative"
values, and ensure callers of parseSearchFormat (search code paths that
currently treat undefined as “no-op”) are updated to handle/reject the parse
error accordingly (reference: SearchFormat type and parseSearchFormat function).
- Around line 172-181: The switch branch handling "memory_recall" incorrectly
calls the smart-search endpoint; update the "memory_recall" case to call
"/agentmemory/search" (leave "memory_smart_search" calling
"/agentmemory/smart-search"), i.e., change the handle.call target in the
"memory_recall" branch from "/agentmemory/smart-search" to "/agentmemory/search"
while keeping the same body construction (query, limit, optional format) and
response handling around result to preserve behavior.

In `@test/mcp-standalone.test.ts`:
- Around line 241-269: The test currently only validates request bodies but not
endpoints, so add assertions on the first argument of each proxyFetch call to
ensure routing is correct; after calling handleToolCall for "memory_recall" and
"memory_smart_search" (functions referenced) inspect proxyFetch.mock.calls and
assert the URL/path (first call equals "/agentmemory/search" or full expected
URL for memory_recall and second call equals "/agentmemory/smart-search" for
memory_smart_search) before checking the RequestInit body, so the test fails if
the tools are routed to the wrong proxy endpoint.

---

Outside diff comments:
In `@src/mcp/standalone.ts`:
- Around line 241-246: The default format is incorrectly hardcoded to "compact"
for both "memory_recall" and "memory_smart_search"; update the logic in the
switch branch so that when handling the "memory_recall" case you default format
to "full" (e.g., set format = v.format ?? "full") while preserving "compact" as
the default for "memory_smart_search" (keep format = v.format ?? "compact" for
that case); locate and modify the code that assigns the variable format inside
the case block that handles the "memory_recall" and "memory_smart_search"
branches so it uses the operation type to choose the appropriate default.
🪄 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: e0d3f760-2dc5-44bb-98fd-adcf6697906d

📥 Commits

Reviewing files that changed from the base of the PR and between 9061da5 and 3c09a58.

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

Comment thread src/mcp/standalone.ts
Comment on lines +67 to +80
type SearchFormat = "compact" | "full";

function parseLimit(raw: unknown, fallback = DEFAULT_LIMIT): number {
if (typeof raw !== "number" && typeof raw !== "string") return fallback;
const n = Number(raw);
if (!Number.isFinite(n) || n <= 0) return fallback;
return Math.min(Math.floor(n), MAX_LIMIT);
}

function parseSearchFormat(raw: unknown): SearchFormat | undefined {
if (typeof raw !== "string") return undefined;
const format = raw.trim().toLowerCase();
return format === "compact" || format === "full" ? format : undefined;
}
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 | 🟠 Major | ⚡ Quick win

Enforce format at the boundary instead of silently dropping invalid values.

Line 130 currently accepts invalid format inputs by mapping them to undefined, which bypasses rejection. Also, the parser currently cannot represent "narrative" for recall requests.

Suggested fix
-type SearchFormat = "compact" | "full";
+type SearchFormat = "compact" | "full" | "narrative";

 function parseSearchFormat(raw: unknown): SearchFormat | undefined {
-  if (typeof raw !== "string") return undefined;
+  if (raw === undefined) return undefined;
+  if (typeof raw !== "string") return undefined;
   const format = raw.trim().toLowerCase();
-  return format === "compact" || format === "full" ? format : undefined;
+  return format === "compact" || format === "full" || format === "narrative"
+    ? format
+    : undefined;
 }

@@
-      v.format = parseSearchFormat(args["format"]);
+      v.format = parseSearchFormat(args["format"]);
+      if (args["format"] !== undefined && !v.format) {
+        throw new Error('format must be one of: "full", "compact", "narrative"');
+      }

Also applies to: 130-131

🤖 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 `@src/mcp/standalone.ts` around lines 67 - 80, The parseSearchFormat function
currently returns undefined for invalid inputs and doesn't accept the
"narrative" format; update the SearchFormat union to include "narrative" (or
create a separate RecallSearchFormat if domain-specific) and change
parseSearchFormat to validate strictly at the boundary by rejecting invalid
values (e.g., throw an Error or return a Result/Error object) instead of
returning undefined; keep the existing trimming/lowercasing and normalize
"compact", "full", and the new "narrative" values, and ensure callers of
parseSearchFormat (search code paths that currently treat undefined as “no-op”)
are updated to handle/reject the parse error accordingly (reference:
SearchFormat type and parseSearchFormat function).

Comment thread src/mcp/standalone.ts
Comment on lines 172 to +181
case "memory_recall":
case "memory_smart_search": {
const body: { query?: string; limit?: number; format?: SearchFormat } = {
query: v.query,
limit: v.limit,
};
if (v.format) body.format = v.format;
const result = await handle.call("/agentmemory/smart-search", {
method: "POST",
body: JSON.stringify({ query: v.query, limit: v.limit }),
body: JSON.stringify(body),
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 | 🟠 Major | 🏗️ Heavy lift

memory_recall is still proxied to the smart-search endpoint.

Line 179 routes both tools to /agentmemory/smart-search; this keeps recall on compact-search semantics and prevents dedicated recall behavior from /agentmemory/search.

Suggested fix
-    case "memory_recall":
-    case "memory_smart_search": {
+    case "memory_recall":
+    case "memory_smart_search": {
+      const endpoint =
+        v.tool === "memory_recall"
+          ? "/agentmemory/search"
+          : "/agentmemory/smart-search";
       const body: { query?: string; limit?: number; format?: SearchFormat } = {
         query: v.query,
         limit: v.limit,
       };
       if (v.format) body.format = v.format;
-      const result = await handle.call("/agentmemory/smart-search", {
+      const result = await handle.call(endpoint, {
         method: "POST",
         body: JSON.stringify(body),
       });
🤖 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 `@src/mcp/standalone.ts` around lines 172 - 181, The switch branch handling
"memory_recall" incorrectly calls the smart-search endpoint; update the
"memory_recall" case to call "/agentmemory/search" (leave "memory_smart_search"
calling "/agentmemory/smart-search"), i.e., change the handle.call target in the
"memory_recall" branch from "/agentmemory/smart-search" to "/agentmemory/search"
while keeping the same body construction (query, limit, optional format) and
response handling around result to preserve behavior.

Comment on lines +241 to +269
it("memory_recall and memory_smart_search forward safe format to smart-search proxy (#440)", async () => {
const proxyProbe = vi.fn(async () => ({ ok: true, status: 200 }));
setLivezProbe(proxyProbe);

const proxyFetch = vi.fn(async () =>
new Response(JSON.stringify({ results: [] }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
(globalThis as { fetch: typeof fetch }).fetch = proxyFetch as unknown as typeof fetch;

for (const toolName of ["memory_recall", "memory_smart_search"]) {
resetHandleForTests();
setLivezProbe(proxyProbe);
await handleToolCall(
toolName,
{ query: "typescript", limit: 3, format: "full" },
new InMemoryKV(),
);
}

expect(proxyFetch).toHaveBeenCalledTimes(2);
for (const [, init] of proxyFetch.mock.calls) {
expect((init as RequestInit).method).toBe("POST");
const body = JSON.parse((init as RequestInit).body as string);
expect(body).toEqual({ query: "typescript", limit: 3, format: "full" });
}
});
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

Add endpoint assertions so this test catches recall routing regressions.

This test verifies request body only; it won’t fail if memory_recall is sent to the wrong endpoint. Assert URL/path per call (/agentmemory/search vs /agentmemory/smart-search) to lock the contract.

Suggested assertion addition
     expect(proxyFetch).toHaveBeenCalledTimes(2);
+    expect(String(proxyFetch.mock.calls[0][0])).toContain("/agentmemory/search");
+    expect(String(proxyFetch.mock.calls[1][0])).toContain("/agentmemory/smart-search");
     for (const [, init] of proxyFetch.mock.calls) {
       expect((init as RequestInit).method).toBe("POST");
       const body = JSON.parse((init as RequestInit).body as string);
       expect(body).toEqual({ query: "typescript", limit: 3, format: "full" });
     }
📝 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("memory_recall and memory_smart_search forward safe format to smart-search proxy (#440)", async () => {
const proxyProbe = vi.fn(async () => ({ ok: true, status: 200 }));
setLivezProbe(proxyProbe);
const proxyFetch = vi.fn(async () =>
new Response(JSON.stringify({ results: [] }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
(globalThis as { fetch: typeof fetch }).fetch = proxyFetch as unknown as typeof fetch;
for (const toolName of ["memory_recall", "memory_smart_search"]) {
resetHandleForTests();
setLivezProbe(proxyProbe);
await handleToolCall(
toolName,
{ query: "typescript", limit: 3, format: "full" },
new InMemoryKV(),
);
}
expect(proxyFetch).toHaveBeenCalledTimes(2);
for (const [, init] of proxyFetch.mock.calls) {
expect((init as RequestInit).method).toBe("POST");
const body = JSON.parse((init as RequestInit).body as string);
expect(body).toEqual({ query: "typescript", limit: 3, format: "full" });
}
});
it("memory_recall and memory_smart_search forward safe format to smart-search proxy (`#440`)", async () => {
const proxyProbe = vi.fn(async () => ({ ok: true, status: 200 }));
setLivezProbe(proxyProbe);
const proxyFetch = vi.fn(async () =>
new Response(JSON.stringify({ results: [] }), {
status: 200,
headers: { "content-type": "application/json" },
}),
);
(globalThis as { fetch: typeof fetch }).fetch = proxyFetch as unknown as typeof fetch;
for (const toolName of ["memory_recall", "memory_smart_search"]) {
resetHandleForTests();
setLivezProbe(proxyProbe);
await handleToolCall(
toolName,
{ query: "typescript", limit: 3, format: "full" },
new InMemoryKV(),
);
}
expect(proxyFetch).toHaveBeenCalledTimes(2);
expect(String(proxyFetch.mock.calls[0][0])).toContain("/agentmemory/search");
expect(String(proxyFetch.mock.calls[1][0])).toContain("/agentmemory/smart-search");
for (const [, init] of proxyFetch.mock.calls) {
expect((init as RequestInit).method).toBe("POST");
const body = JSON.parse((init as RequestInit).body as string);
expect(body).toEqual({ query: "typescript", limit: 3, format: "full" });
}
});
🤖 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.test.ts` around lines 241 - 269, The test currently only
validates request bodies but not endpoints, so add assertions on the first
argument of each proxyFetch call to ensure routing is correct; after calling
handleToolCall for "memory_recall" and "memory_smart_search" (functions
referenced) inspect proxyFetch.mock.calls and assert the URL/path (first call
equals "/agentmemory/search" or full expected URL for memory_recall and second
call equals "/agentmemory/smart-search" for memory_smart_search) before checking
the RequestInit body, so the test fails if the tools are routed to the wrong
proxy endpoint.

@rohitg00
Copy link
Copy Markdown
Owner

Closed as superseded by #516 (merged today), which is the more complete fix:

  • Routes memory_recall through /agentmemory/search (this PR still routed through /smart-search)
  • Forwards both format and token_budget (this PR handled only format)
  • Default format: 'full' matching the schema (this PR defaulted to 'compact')

Both PRs target the same file + functions, so they're mutually exclusive. Thanks @honor2030 for surfacing #440 — leaving it open as a comment trail in case anyone else hits the issue and lands here from search.

@rohitg00 rohitg00 closed this May 19, 2026
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.

MCP memory_recall is aliased to smart-search and drops format param - full content unreachable via MCP

2 participants