Repository navigation
fix(mcp): preserve recall format option #489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,13 +64,21 @@ function normalizeList(value: unknown): string[] { | |
|
|
||
| const DEFAULT_LIMIT = 10; | ||
| const MAX_LIMIT = 100; | ||
| 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; | ||
| } | ||
|
|
||
| function textResponse(payload: unknown, pretty = false): { | ||
| content: Array<{ type: string; text: string }>; | ||
| } { | ||
|
|
@@ -89,6 +97,7 @@ interface Validated { | |
| files?: string[]; | ||
| query?: string; | ||
| limit?: number; | ||
| format?: SearchFormat; | ||
| memoryIds?: string[]; | ||
| reason?: string; | ||
| } | ||
|
|
@@ -118,6 +127,7 @@ function validate(toolName: string, args: Record<string, unknown>): Validated { | |
| } | ||
| v.query = query.trim(); | ||
| v.limit = parseLimit(args["limit"]); | ||
| v.format = parseSearchFormat(args["format"]); | ||
| return v; | ||
| } | ||
| case "memory_sessions": { | ||
|
|
@@ -161,9 +171,14 @@ async function handleProxy( | |
| } | ||
| 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), | ||
|
Comment on lines
172
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 179 routes both tools to 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 |
||
| }); | ||
| return textResponse(result, true); | ||
| } | ||
|
|
@@ -227,6 +242,7 @@ async function handleLocal( | |
| case "memory_smart_search": { | ||
| const query = (v.query || "").toLowerCase(); | ||
| const limit = v.limit ?? DEFAULT_LIMIT; | ||
| const format = v.format ?? "compact"; | ||
| const all = | ||
| await kvInstance.list<Record<string, unknown>>("mem:memories"); | ||
| const results = all | ||
|
|
@@ -244,7 +260,7 @@ async function handleLocal( | |
| return query.split(/\s+/).every((word) => text.includes(word)); | ||
| }) | ||
| .slice(0, limit); | ||
| return textResponse({ mode: "compact", results }, true); | ||
| return textResponse({ mode: format, format, results }, true); | ||
| } | ||
|
|
||
| case "memory_sessions": { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -222,6 +222,52 @@ describe("handleToolCall", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(parsed.results[0].content).toBe("TypeScript is great"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("memory_recall preserves a safe format option in local fallback (#440)", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const kv = new InMemoryKV(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await handleToolCall("memory_save", { content: "TypeScript is great" }, kv); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await handleToolCall( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "memory_recall", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { query: "typescript", format: "full" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kv, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsed = JSON.parse(result.content[0].text); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(parsed.format).toBe("full"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(parsed.mode).toBe("full"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(parsed.results[0].content).toBe("TypeScript is great"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+241
to
+269
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add endpoint assertions so this test catches recall routing regressions. This test verifies request body only; it won’t fail if 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("memory_save accepts concepts/files as arrays (plugin skill format, #139)", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const kv = new InMemoryKV(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await handleToolCall( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce
formatat the boundary instead of silently dropping invalid values.Line 130 currently accepts invalid
formatinputs by mapping them toundefined, which bypasses rejection. Also, the parser currently cannot represent"narrative"for recall requests.Suggested fix
Also applies to: 130-131
🤖 Prompt for AI Agents