Bug Summary
The MCP mem_save tool handler returns success (with a valid observation ID) but does not write the observation parameter body to the SQLite content column. Every save via MCP results in an empty content field. The CLI engram save command writes content correctly — the bug is isolated to the MCP JSON-RPC code path.
Version
engram 1.15.11 (Homebrew, macOS arm64)
Previously reproduced on 1.15.8 as well. Upgrading to 1.15.11 did not fix it.
Steps to Reproduce
- Start an MCP session:
engram mcp --tools=agent
- Send a
mem_save tool call with an observation parameter containing text
- Tool returns success:
{"id": 103, "result": "Memory saved: \"Title\" (manual)"}
- Check SQLite:
sqlite3 ~/.engram/engram.db "SELECT id, length(content) FROM observations WHERE id = 103;"
- Result:
103|0 — content is empty
Expected Behavior
The observation parameter value should be written to the content column in the observations table.
Actual Behavior
content column is empty (length 0)
title column IS written correctly
type, project, scope, sync_id — all written correctly
- Only
content is silently dropped
Impact
- Data loss: All observations saved via MCP have empty bodies
- Cloud sync blocked: sync_mutations reference observations with missing
content field, causing engram doctor to report sync_mutation_required_fields as blocked
- Search degraded: FTS index has no content to match against, so
mem_search can't find observations by body text
In our case, this caused 65+ broken sync mutations and 10 irrecoverably lost observations before we noticed (the tool was returning success, so there was no indication of failure).
CLI vs MCP Comparison
| Path |
Title written? |
Content written? |
engram save "text" --project X |
Yes |
Yes |
MCP mem_save tool |
Yes |
No |
Workaround
We built a Claude Code PostToolUse hook that fires after every mem_save call, reads back the observation from SQLite, and if content is empty, auto-repairs by writing the original observation text from the MCP tool_input directly into the database and fixing the corresponding sync_mutation payload.
Environment
- macOS 15 (Darwin 25.3.0, arm64)
- engram 1.15.11 installed via
brew install gentleman-programming/tap/engram
- MCP server started as:
engram mcp --tools=agent
- Claude Code (Anthropic CLI) as the MCP client
Bug Summary
The MCP
mem_savetool handler returns success (with a valid observation ID) but does not write theobservationparameter body to the SQLitecontentcolumn. Every save via MCP results in an emptycontentfield. The CLIengram savecommand writes content correctly — the bug is isolated to the MCP JSON-RPC code path.Version
Previously reproduced on 1.15.8 as well. Upgrading to 1.15.11 did not fix it.
Steps to Reproduce
engram mcp --tools=agentmem_savetool call with anobservationparameter containing text{"id": 103, "result": "Memory saved: \"Title\" (manual)"}sqlite3 ~/.engram/engram.db "SELECT id, length(content) FROM observations WHERE id = 103;"103|0— content is emptyExpected Behavior
The
observationparameter value should be written to thecontentcolumn in theobservationstable.Actual Behavior
contentcolumn is empty (length 0)titlecolumn IS written correctlytype,project,scope,sync_id— all written correctlycontentis silently droppedImpact
contentfield, causingengram doctorto reportsync_mutation_required_fieldsas blockedmem_searchcan't find observations by body textIn our case, this caused 65+ broken sync mutations and 10 irrecoverably lost observations before we noticed (the tool was returning success, so there was no indication of failure).
CLI vs MCP Comparison
engram save "text" --project Xmem_savetoolWorkaround
We built a Claude Code PostToolUse hook that fires after every
mem_savecall, reads back the observation from SQLite, and if content is empty, auto-repairs by writing the originalobservationtext from the MCPtool_inputdirectly into the database and fixing the corresponding sync_mutation payload.Environment
brew install gentleman-programming/tap/engramengram mcp --tools=agent