You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
memory: create_entities / create_relations / add_observations are marked non-destructive but drop data they didn't touch #5058
create_entities, create_relations and add_observations in the memory server are annotated destructiveHint: false (the MCP spec reads this as "performs only additive updates"). But every call rewrites the whole JSONL file from the parsed graph, so anything the current schema doesn't model is silently dropped, including content belonging to records the call never touched:
extra fields on entities or relations (for example a createdAt field written by an earlier version or another client)
record types other than entity/relation
malformed or truncated lines, and entities that fail schema validation
The tool response only reports what was added, so the client is never told that data was removed.
Start the server with MEMORY_FILE_PATH pointing at it.
Call create_entities with an unrelated new entity, e.g. {"entities":[{"name":"Eve","entityType":"person","observations":[]}]}.
The file no longer contains Alice's createdAt field or the meta line.
I reproduced this end to end, including with a file written by the official server at commit 1bd3734 (Sept 2025), which stored extra client-supplied fields that the current version then drops.
Why it matters
MCP clients relax confirmation for tools that declare destructiveHint: false, so these calls can be auto-approved. Because the reference servers are widely copied, the label also propagates.
Possible fixes
Round-trip lines the server doesn't model unchanged (preserve unknown fields and record types), or
refuse to write, with an error, when the file contains content the schema can't represent, or
change the annotation to destructiveHint: true and document that a write can prune unmodelled content.
Found during an audit of MCP tool annotations (AI-assisted source review, then reproduced locally). Happy to send a PR for option 1 or 2 if that's useful.
Taking this. Since every one of these calls rewrites the whole JSONL store (and drops anything the current schema doesn't model), the honest fix is annotating them destructiveHint: true - the alternative is making the writes actually additive, which is a much bigger change to the server's storage model. I'll open a PR flipping the annotations on create_entities, create_relations and add_observations, with a test asserting the write-capable tools aren't marked non-destructive.
Update: the repo's new contribution policy accepts issues, not outside pull requests, so I won't open one. The fix I had ready is small: flip destructiveHint to true on create_entities, create_relations and add_observations (each call already rewrites the whole JSONL store via loadGraph/saveGraph), document the pruning behavior in the README, plus a vitest regression test asserting the annotations. Happy to share my working or the prompt if useful.
Thanks both. I checked v2/main: loadGraphWithUnreadable now writes malformed lines, invalid entries and unknown record types back unchanged, which fixes the meta line part of this.
Extra fields on records that do parse are still dropped, though. saveGraph serialises only name, entityType and observations (and from, to, relationType), and EntitySchema is a plain z.object, which strips unknown keys. So in the repro above, Alice's createdAt is still lost on v2.
Keeping unknown keys on valid records (.passthrough(), or z.looseObject in Zod 4) and writing them back in saveGraph would close it, and then destructiveHint: false would be accurate. Happy to write a prompt for that change plus a regression test, per the contribution policy.
content nobody touched, silently dropped on an ordinary write. for a memory server that is the worst kind of bug, because memory you half trust is worse than memory you do not have.
this is the failure I built a memory server for. it writes everything to an append only log on your machine, every write gets a receipt, old records are never rewritten, and a replay command re-derives the state from the log so a silent drop shows up as a mismatch.
it is free, it runs on your machine, and it is part of vestige.
npx @vestige/init
it wires the vestige memory server into the coding tools it finds. nothing changes about how you work, the store just sits underneath.
Additional bounded reproduction on current v2/main at 38569bd5762d3e4ef07f8062b01325121fa59d06: the remaining loss affects valid entity and relation extension fields, including when no logical graph update is requested.
A particularly small trigger is create_entities with {"entities": []}. It returns an empty successful result, but the next disk snapshot loses an existing entity's createdAt and nested metadata. The unknown meta record survives, confirming the partial fix described above. The same empty-call behavior reproduced for create_relations and add_observations; duplicate additive calls also prune the surviving records' extensions.
I prototyped storage-only retention locally, without widening the public Zod schemas or returning file-only metadata to clients. Per CONTRIBUTING, I am sharing the exact prompt below, not the generated diff or an outside PR.
15 new protocol/file cases via the repository's existing in-memory client harness: six normal mutations, three duplicate additive calls, three empty additive calls, and three read-only controls. Baseline: 12 failed / 3 passed; local prototype: 15 passed. Fresh-context repeats gave the same outcomes; no test retries were configured.
Assertions cover surviving entity/relation extensions (nested arrays, zero/false/null), intended updates/deletes, unchanged public tool results, preservation of malformed/invalid/unknown/primitive lines, and reload through a new server/client instance.
One real stdio example, rebuilt from each source: empty create_entities, close the process, start a new process and read again. Baseline loses the fields; prototype retains them; the public graph remains canonical on both.
Memory workspace validate: 199 passed / 1 skipped, including the new cases. The existing POSIX read-only-file test skips under uid 0. Format, lint, typecheck and build passed. The per-file coverage command also passed; index.ts reports 98.78% lines / 98.44% branches.
The tests used only synthetic temporary JSONL; no LLM, account or external graph/memory service was used. Full-root local gate, an LLM client, remote CI, approval and merge were not run. An initial npm configuration error occurred before tests started and was excluded from the comparison. The local prototype is a frozen tree (996f5c4d08bac17f21d168b3ec10b4f638f58e6c), not an upstream commit or readiness/approval claim.
AI disclosure: Codex authored and executed the supplemental tests/prototype; no manual reproduction by the account holder is claimed.
Exact local experiment prompt
Prototype a narrowly scoped fix for modelcontextprotocol/servers issue #5058 on v2/main at 38569bd5762d3e4ef07f8062b01325121fa59d06. This is an external-contributor local experiment; do not push, open a PR, assign issues, change the project board, sign a CLA/DCO, or publish code. Follow the issues-and-prompts contribution policy.
Valid persisted entity/relation records can contain additional JSON members written by an older client. An unrelated write, even an empty or duplicate additive call, must retain those members on surviving records. Keep the public EntitySchema/RelationSchema and input/output behavior unchanged: unknown fields must not start appearing in tool results or newly accepted caller input. Retain existing malformed/invalid/unknown-record handling, locking, atomic replacement and error behavior. Explicit deletes must still remove the requested entities, relations or observations.
Explore a storage-only implementation: associate the original validated JSON record with the parsed canonical object, using a manager-owned weak association rather than adding enumerable metadata to public objects. When saving a surviving object, retain the associated original members and overwrite all canonical fields with the current canonical values. Fresh objects without an original record serialize exactly as before. Do not broaden public Zod schemas as a shortcut. Explain this boundary in the code.
First add protocol-level regressions through src/memory/__tests__/helpers.ts, using actual temporary JSONL files. Cover the six mutation tools, duplicate and empty calls for create_entities/create_relations/add_observations, and read_graph/search_nodes/open_nodes controls. Seed extension members on entities and relations, including nested arrays, false, zero and null, plus malformed JSON, an invalid entity, an unknown record type and a primitive line. Assert surviving extension members, intended canonical updates/deletions, opaque-line preservation, unchanged public responses, and reload through a new server/client instance. Save raw observations before assertions for this local experiment. Run the same tests on baseline and prototype with unchanged dependencies, then repeat in fresh contexts without test retries. Add one stdio durability example with actual process restart; do not replace native in-process coverage with spawned tests.
Run the memory workspace's validation and per-file coverage without lowering gates or suppressing errors. Record exact source/lock/build hashes, commands and actual exit statuses. Clearly separate local prototype evidence from full-root gate, LLM-client, remote CI, approval, merge and release work, which are outside this experiment. Share findings and this exact prompt in the existing issue, not the generated source or a diff.
Severity 4: Malformed lines and unknown record types now survive a write, but extra fields on valid entities and relations are still stripped on load and dropped from the file by any write, even an empty create_entities call, while create_entities, create_relations and add_observations still declare destructiveHint false.
Urgency 3: idle more than 90 days, or wanted soon
Bonuses: +1 bug label
Still present on v2 (checked by reading the current code).
Board: Servers V2 (#43), Status Incoming, awaiting maintainer review (no milestone yet). Part of the backlog triage in #4875.
Fixed by #5292 (merged to v2/main): create_entities, create_relations and add_observations now round-trip unmodelled fields on valid entity and relation records, so their writes are additive and destructiveHint: false is accurate. Ships in v2.2.0.
Activity
Mrun25 commentedon Oct 8, 2026
Taking this. Since every one of these calls rewrites the whole JSONL store (and drops anything the current schema doesn't model), the honest fix is annotating them
destructiveHint: true- the alternative is making the writes actually additive, which is a much bigger change to the server's storage model. I'll open a PR flipping the annotations on create_entities, create_relations and add_observations, with a test asserting the write-capable tools aren't marked non-destructive.Mrun25 commentedon Oct 8, 2026
Update: the repo's new contribution policy accepts issues, not outside pull requests, so I won't open one. The fix I had ready is small: flip destructiveHint to true on create_entities, create_relations and add_observations (each call already rewrites the whole JSONL store via loadGraph/saveGraph), document the pruning behavior in the README, plus a vitest regression test asserting the annotations. Happy to share my working or the prompt if useful.
sattyamjjain commentedon Oct 8, 2026
ranausmanai commentedon Oct 8, 2026
Thanks both. I checked
v2/main:loadGraphWithUnreadablenow writes malformed lines, invalid entries and unknown record types back unchanged, which fixes themetaline part of this.Extra fields on records that do parse are still dropped, though.
saveGraphserialises onlyname,entityTypeandobservations(andfrom,to,relationType), andEntitySchemais a plainz.object, which strips unknown keys. So in the repro above, Alice'screatedAtis still lost on v2.Keeping unknown keys on valid records (
.passthrough(), orz.looseObjectin Zod 4) and writing them back insaveGraphwould close it, and thendestructiveHint: falsewould be accurate. Happy to write a prompt for that change plus a regression test, per the contribution policy.samvallad33 commentedon Oct 8, 2026
content nobody touched, silently dropped on an ordinary write. for a memory server that is the worst kind of bug, because memory you half trust is worse than memory you do not have.
this is the failure I built a memory server for. it writes everything to an append only log on your machine, every write gets a receipt, old records are never rewritten, and a replay command re-derives the state from the log so a silent drop shows up as a mismatch.
it is free, it runs on your machine, and it is part of vestige.
npx @vestige/initit wires the vestige memory server into the coding tools it finds. nothing changes about how you work, the store just sits underneath.
safal207 commentedon Oct 16, 2026
Additional bounded reproduction on current
v2/mainat38569bd5762d3e4ef07f8062b01325121fa59d06: the remaining loss affects valid entity and relation extension fields, including when no logical graph update is requested.A particularly small trigger is
create_entitieswith{"entities": []}. It returns an empty successful result, but the next disk snapshot loses an existing entity'screatedAtand nested metadata. The unknownmetarecord survives, confirming the partial fix described above. The same empty-call behavior reproduced forcreate_relationsandadd_observations; duplicate additive calls also prune the surviving records' extensions.I prototyped storage-only retention locally, without widening the public Zod schemas or returning file-only metadata to clients. Per CONTRIBUTING, I am sharing the exact prompt below, not the generated diff or an outside PR.
Validation (Linux, Node 24.19.0, memory-server 1.0.0, SDK client/server 2.3.1, Zod 4.4.3; unchanged npm lockfile):
create_entities, close the process, start a new process and read again. Baseline loses the fields; prototype retains them; the public graph remains canonical on both.validate: 199 passed / 1 skipped, including the new cases. The existing POSIX read-only-file test skips under uid 0. Format, lint, typecheck and build passed. The per-file coverage command also passed; index.ts reports 98.78% lines / 98.44% branches.The tests used only synthetic temporary JSONL; no LLM, account or external graph/memory service was used. Full-root local gate, an LLM client, remote CI, approval and merge were not run. An initial npm configuration error occurred before tests started and was excluded from the comparison. The local prototype is a frozen tree (
996f5c4d08bac17f21d168b3ec10b4f638f58e6c), not an upstream commit or readiness/approval claim.AI disclosure: Codex authored and executed the supplemental tests/prototype; no manual reproduction by the account holder is claimed.
Exact local experiment prompt
cliffhall commentedon Oct 19, 2026
Triage: Priority Medium (total 8)
buglabelStill present on v2 (checked by reading the current code).
Board: Servers V2 (#43), Status
Incoming, awaiting maintainer review (no milestone yet). Part of the backlog triage in #4875.cliffhall commentedon Oct 27, 2026
Fixed by #5292 (merged to v2/main): create_entities, create_relations and add_observations now round-trip unmodelled fields on valid entity and relation records, so their writes are additive and
destructiveHint: falseis accurate. Ships in v2.2.0.fix(memory): keep unmodelled fields on stored records across writes