feat(kv)!: #192 KV MCP LLM ergonomics — created signal, batch, size, value_path, JSON docs#193
Merged
StefanSteiner merged 20 commits intoJul 11, 2026
Conversation
…au#192) Design and 13-task TDD implementation plan for the five issue-tableau#192 KV-store MCP improvements plus two adjacent LLM-ergonomics gaps: - insert-vs-overwrite signal + set_if_absent write guard (no silent data loss) - server-side value_path for kv_set - byte-size reporting + soft 1 MiB warning - kv_set_many atomic batch write + values flag on kv_list - JSON-in-TEXT query docs + ::numeric truncation gotcha - PermissionDenied I/O-error fidelity + corrected misleading JSON-error suggestion Planning docs only; no code changes. The 0.6.1 -> 0.7.0 bump is carried by the feat!: commits the plan produces, per release-please.
Apply the 6 confirmed adversarial-review findings from the plan-vet phase: - F1: fold the breaking-change MCP caller fix (server.rs:3136 Ok(())->Ok(_)) into Task 1's commit + add cargo build -p hyperdb-mcp gate and README refresh (Step 7b), so hyperdb-mcp compiles through Tasks 2-7 instead of only at Task 8. - F2: revert the silent require_writable true->false flip in the kv_size (Task 9) and kv_list (Task 11) rewrites, restoring reader parity with kv_get/kv_list_stores. - F3: correct all three Error::server test calls to the real 4-arg signature server(Option<String>, impl Into<String>, Option<String>, Option<String>). - F4: Task 13 Step 3 CHANGELOG range 8-26 -> 8-53 so the existing hyperdb-mcp ### Fixed bullets are carried into the replacement, not duplicated/orphaned. - F5: sync spec 4 total_bytes to the implemented all-submitted upper-bound semantics (BatchGuardOutcome returns counts only, not which keys were written). - F6: README refresh gap folded into Task 1 Step 7b; premise corrected. No feature-scope change; these make the plan compilable and internally consistent.
Mirror the sync return-type changes (SetOutcome/BatchSetOutcome) in the async twin to maintain the sync/async lockstep invariant (AGENTS.md). Changes: - AsyncKvStore::upsert now returns Result<bool> (created) - AsyncKvStore::set returns Result<SetOutcome> - AsyncKvStore::set_as returns Result<SetOutcome> - AsyncKvStore::set_batch returns Result<BatchSetOutcome> - Import SetOutcome/BatchSetOutcome from crate::kv_store - Update existing test calls to ignore outcomes with `let _` - Add async_set_reports_created_and_batch_outcome test All async KV tests pass (5/5) and sync tests remain green (14/14).
Restores sync/async twin lockstep (AGENTS.md rule). The sync KvStore received `set_if_absent` (iteration 3) and `set_batch_if_absent` (iteration 5), but AsyncKvStore was missing both. This adds: - `AsyncKvStore::set_if_absent` (line 195): conditional-INSERT guard (INSERT ... WHERE NOT EXISTS), mirroring the sync version. - `AsyncKvStore::set_batch_if_absent` (line 464): transaction-wrapped batch insert that skips existing keys, returning BatchGuardOutcome (written/skipped counts). Both methods use the same SQL patterns as their sync twins. All KV tests (sync + async) pass. Fixes CRITICAL-001 (sync/async drift). Closes iteration 6 (plan Task 6).
…lue warning Two critical test gaps identified in iteration 8 review: 1. kv_set_reports_created_and_bytes: Added missing assertion for second.value_bytes = 2. Test name promised both created and value_bytes checks but only verified created=false on the overwrite path. 2. New test kv_set_large_value_warns: Verifies kv_set returns a warning when value_bytes exceeds KV_SOFT_SIZE_WARN_BYTES (1_048_576). Boundary check confirms exactly 1MB triggers no warning, 1MB+1 does. Validates warning text contains "1048576" and mentions "soft limit"/"recommended". All 15 KV tests pass; full workspace clippy+fmt green. Refs: tableau#192 iteration 8 blocking review findings ITER8-001, ITER8-002
…th/overwrite/values
Per-crate CHANGELOG.md bullets only — release-please owns the root CHANGELOG.md, all Cargo.toml versions, and .release-please-manifest.json. The 0.6.1 → 0.7.0 bump is carried by the `feat!:` commits in this branch. hyperdb-api: BREAKING — set/set_as/set_batch return SetOutcome/BatchSetOutcome instead of Result<()>; added set_if_absent, set_batch_if_absent, byte_size, entries (sync + async twins). hyperdb-mcp: added kv_set_many tool, value_path + overwrite on kv_set, values flag on kv_list, byte reporting on kv_set/kv_size; fixed PermissionDenied I/O-error mapping and misleading JSON-error suggestion (now advises ::json cast instead of statement split).
Reject a value_path file larger than 64 MiB against its metadata BEFORE reading it into memory, so a stray path to a multi-gigabyte file returns INVALID_ARGUMENT instead of OOMing the server by slurping the whole thing into a single KV TEXT value. The soft 1 MiB warning still fires post-write for smaller-but-large values; this adds a hard, enforced ceiling for the file-backed path. Pure check_value_path_size helper is unit-tested at the boundary (at-cap passes, cap+1 rejects) without materializing a huge fixture.
…sweep Final-sweep review reconciliation — doc-only, no behavior change: - hyperdb-mcp CHANGELOG: kv_set_many under overwrite:false returns `created` = keys newly inserted, NOT the literal `created: 0` the bullet claimed (the code returns o.written; kv_set_many_guard test asserts created==1). Also note total_bytes is an upper bound. - hyperdb-api CHANGELOG: `let _ = set(...)?;` already compiles unchanged — it is NOT a breaking case; name the genuinely-breaking shapes instead. Drop the hardcoded "0.7.0" that presumed the version release-please will assign. - set_if_absent rustdoc (sync + async twins): qualify "no check-then-write race" with the cross-process caveat (no unique constraint on (store_name,key); exact within one process/the serialized MCP daemon), mirroring kv_pop's wording. - readme.rs: note the 64 MiB value_path cap and total_bytes upper-bound semantics for the LLM.
Merged
StefanSteiner
added a commit
that referenced
this pull request
Jul 11, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>0.7.0</summary> ## [0.7.0](v0.6.1...v0.7.0) (2026-07-11) ### ⚠ BREAKING CHANGES * **kv:** #192 KV MCP LLM ergonomics — created signal, batch, size, value_path, JSON docs ([#193](#193)) * **kv:** async set/set_as/set_batch return SetOutcome/BatchSetOutcome * **kv:** async twin — reshape AsyncKvStore set/set_as/set_batch * **kv:** sync set/set_as/set_batch return SetOutcome/BatchSetOutcome * commits the plan produces, per release-please. ### Features * commits the plan produces, per release-please. ([f6e5789](f6e5789)) * **kv:** [#192](#192) KV MCP LLM ergonomics — created signal, batch, size, value_path, JSON docs ([#193](#193)) ([52a3ba0](52a3ba0)) * **kv:** add async set_if_absent/byte_size/entries/set_batch_if_absent ([a166813](a166813)) * **kv:** add set_if_absent and set_batch_if_absent to AsyncKvStore ([d116b12](d116b12)) * **kv:** add sync KvStore::byte_size and entries ([aea9ea5](aea9ea5)) * **kv:** add sync KvStore::set_batch_if_absent atomic guard ([4a88aa3](4a88aa3)) * **kv:** add sync KvStore::set_if_absent write guard ([4f1ca85](4f1ca85)) * **kv:** async set/set_as/set_batch return SetOutcome/BatchSetOutcome ([6af0649](6af0649)) * **kv:** async twin — reshape AsyncKvStore set/set_as/set_batch ([89a7767](89a7767)) * **kv:** sync set/set_as/set_batch return SetOutcome/BatchSetOutcome ([5aeab78](5aeab78)) * **mcp:** add kv_set_many atomic batch write tool ([34f15f7](34f15f7)) * **mcp:** add values flag to kv_list for whole-store reads ([f0b3527](f0b3527)) * **mcp:** kv_set reports created/value_bytes, adds overwrite guard + value_path ([fd89084](fd89084)) * **mcp:** kv_size reports total value bytes ([0967c05](0967c05)) ### Bug Fixes * **mcp:** [#192](#192) cap kv_set value_path file size before reading ([0fda8ec](0fda8ec)) * **mcp:** preserve PermissionDenied and steer JSON errors to ::json cast ([e1acbcd](e1acbcd)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the HyperDB MCP KV store dramatically more ergonomic for LLMs, closing
issue #192 — five dogfooding-driven improvements plus two adjacent API gaps
they exposed. The throughline: an LLM using the KV store should get a clear
signal on every write, batch efficiently, size a store before reading it, load
a file's contents without inlining, and never be surprised by a silent
clobber.
Closes #192.
Motivation
Dogfooding the M2 KV tools (#185) surfaced concrete friction:
kv_setgave nosignal whether a write created a key or overwrote one (silent data loss
risk), there was no batch write (N tool calls for N keys), no way to size a
store before pulling it, no way to load a file server-side, and the JSON-query
path was under-documented. This PR addresses all five, and reshapes the
underlying
hyperdb-apiKV write surface so the "created vs overwritten"signal is available to Rust callers too.
What's in it
hyperdb-api(public API) — the created/overwritten signalKvStore::set/set_as/set_batch(and theAsyncKvStoretwins) now return
SetOutcome { created }/BatchSetOutcome { created, overwritten }instead ofResult<()>. Callers that ignored the result(
set("k","v")?;,let _ = set(...)?;) still compile unchanged; onlycallers that named the unit return must adapt. Under pre-1.0 semver this
is the minor slot, driving the 0.7.0 bump.
set_if_absent/set_batch_if_absentguarded writes(
INSERT ... WHERE NOT EXISTS; returnsBatchGuardOutcome { written, skipped }for the batch),byte_size(SUM(OCTET_LENGTH(value))), andentries(all(key, value)pairs, sorted). Sync and async twins inlockstep.
SetOutcome,BatchSetOutcome,BatchGuardOutcomere-exported from
hyperdb_api.hyperdb-mcp(KV tool surface)kv_set{stored, created, value_bytes};overwrite:falseskips clobbers ({stored:false, existed:true})kv_setvalue_path:<abs path>stores a file's contents server-side (exactly one ofvalue/value_path); files over 64 MiB rejected before readingkv_set_manyentriesarray; all keys validated up front;{stored, created, overwritten, total_bytes}(skippedunderoverwrite:false)kv_size{size, bytes}(total value bytes)kv_listvalues:truereturns{entries:[{key,value}]}— eliminates N×kv_getget_readme::json-cast querying, the::numericscale-0 truncation gotcha, and all new paramsDesign notes
_hyperdb_kv_storehas no unique constraint, soset_if_absentuses a singleINSERT ... SELECT ... WHERE NOT EXISTS— race-free within a connection/process (the MCP daemon serializes engine access);
the rustdoc documents the cross-process caveat honestly.
kv_set_manyruns inside one transaction: an invalid key aborts the wholebatch without writing anything.
total_bytescounts all submitted values —an upper bound on bytes persisted when keys are skipped or duplicated.
value_pathreads any path the server process can read (no sandbox — matchesthe existing
load_fileposture); the 64 MiB cap is checked viastd::fs::metadatabefore reading, so an oversized file is rejectedwithout allocating.
Test plan
cargo fmt --all --check— cleancargo clippy --workspace --all-targets --all-features -- -D warnings— 0 warningscargo test --workspace --exclude hyperdb-api-node --exclude hyperdb-bootstrap— 1179 passed / 0 failed (114 suites), incl. new sync/async KV outcome tests,kv_set_many,value_pathsize-cap unit tests, andkv_list values:truehyperdb-mcp/tests/readme_tests.rsstructural tool-name coverage — greenhyperdb-mcpexercising the new KV signals.Changelog
Per-crate
## [Unreleased]bullets added tohyperdb-api/CHANGELOG.mdandhyperdb-mcp/CHANGELOG.md. Workspace version bump + root CHANGELOG are left torelease-please.