Summary
engram_recall accepts a tags parameter, but the filter is never applied. Requests return memories that do not carry the requested tag, with no error.
What happens
src/memory/memory-query.service.ts (around line 145):
let filterTags = dto.filter?.tags ? [...dto.filter.tags] : undefined;
The server reads tags from the nested filter object. The MCP client @openengram/mcp exposes tags as a top-level parameter of engram_recall and sends it that way. The server finds no filter, sets filterTags to undefined, and applies no filter at all — silently, since from the server's point of view the request simply arrived without a filter.
Reproduce
With 11 memories, exactly one tagged aipmo:
- MCP:
engram_recall({query: "...", tags: ["aipmo"]}) → 9 results, only one of which carries the tag.
- REST:
POST /v1/memories/query with {"query":"...","filter":{"tags":["aipmo"]},"limit":20} → exactly 1 result, correctly filtered.
So the server-side filter (Prisma hasEvery, AND semantics) works correctly. Only the client/server contract is mismatched.
Suggested fix
Either nest tags under filter in the MCP client's request body, or accept a top-level tags on the server and merge it into filter.tags. A validation error for unknown top-level fields would also have surfaced this immediately.
Related
The layer parameter has a similar mismatch: the MCP client's enum is SESSION | SEMANTIC | CORE | META, while the API accepts IDENTITY | PROJECT | SESSION | TASK | INSIGHT. Only SESSION overlaps, so any explicit layer either fails client-side validation or is rejected by the server with a 400. In practice layer cannot be set through MCP at all. Happy to file that separately if preferred.
Summary
engram_recallaccepts atagsparameter, but the filter is never applied. Requests return memories that do not carry the requested tag, with no error.What happens
src/memory/memory-query.service.ts(around line 145):The server reads tags from the nested
filterobject. The MCP client@openengram/mcpexposestagsas a top-level parameter ofengram_recalland sends it that way. The server finds nofilter, setsfilterTagstoundefined, and applies no filter at all — silently, since from the server's point of view the request simply arrived without a filter.Reproduce
With 11 memories, exactly one tagged
aipmo:engram_recall({query: "...", tags: ["aipmo"]})→ 9 results, only one of which carries the tag.POST /v1/memories/querywith{"query":"...","filter":{"tags":["aipmo"]},"limit":20}→ exactly 1 result, correctly filtered.So the server-side filter (Prisma
hasEvery, AND semantics) works correctly. Only the client/server contract is mismatched.Suggested fix
Either nest
tagsunderfilterin the MCP client's request body, or accept a top-leveltagson the server and merge it intofilter.tags. A validation error for unknown top-level fields would also have surfaced this immediately.Related
The
layerparameter has a similar mismatch: the MCP client's enum isSESSION | SEMANTIC | CORE | META, while the API acceptsIDENTITY | PROJECT | SESSION | TASK | INSIGHT. OnlySESSIONoverlaps, so any explicit layer either fails client-side validation or is rejected by the server with a 400. In practicelayercannot be set through MCP at all. Happy to file that separately if preferred.