Skip to content

fix(manage_frontmatter): type value input as a union so clients serialize structured values correctly#69

Open
tommygents wants to merge 1 commit into
cyanheads:mainfrom
tommygents:fix/frontmatter-value-typed-union
Open

fix(manage_frontmatter): type value input as a union so clients serialize structured values correctly#69
tommygents wants to merge 1 commit into
cyanheads:mainfrom
tommygents:fix/frontmatter-value-typed-union

Conversation

@tommygents
Copy link
Copy Markdown

Summary

The set operation's value input is typed z.unknown(), so the rendered JSON Schema for the field carries no type. MCP clients that infer wire serialization from the schema (Claude Code does) then transmit non-string values as their JSON-string form; the handler JSON.stringifys again before PATCHing, producing a double-encoded result:

  • number 42 → stored as "42" (type lost)
  • array ["a","b"] → stored as the string '["a","b"]'
  • objects likewise; strings are unaffected (stringify+parse is identity for a bare string)

This is the unresolved tail of #20 / #21 / #54. Those closed cleanly for the unit suite — but the unit tests call input.parse() with native JS values, which bypasses the client transport where the mis-serialization actually happens, so they structurally can't catch it.

Verification

Reproduced and fixed end-to-end against a live Obsidian Local REST API using the Claude Code MCP client (server v3.1.5, Windows 11). Pre-fix: 42"42", ["alpha","beta"]'["alpha","beta"]'. Post-fix: number→42, array→a real YAML list, boolean→true, string→clean.

Fix

Replace z.unknown() with an explicit z.union of the documented JSON types, so the rendered schema instructs clients to send real typed values rather than pre-stringified strings. No caller-contract change, and the existing native-value tests stay green.

(#54 proposed z.string() + server-side JSON.parse; the union achieves the same correctness without changing the caller contract.)

Adds round-trip coverage in the set handler for string / boolean / array / object values.

…ialize structured values correctly

The `set` value input was typed `z.unknown()`, so the rendered JSON Schema
for the field carries no `type`. MCP clients that infer wire serialization
from the schema (e.g. Claude Code) then send non-string values as their
JSON-string form; the handler stringifies again before PATCHing, yielding a
double-encoded result — `42` stored as `"42"` (type lost), `["a","b"]` stored
as the string `'["a","b"]'`. Strings are unaffected because stringify+parse is
identity for a bare string. This is the unresolved tail of cyanheads#20/cyanheads#21/cyanheads#54.

The existing unit tests can't catch this: they call `input.parse()` with native
JS values, bypassing the client transport where the mis-serialization happens.
Verified end-to-end against a live Obsidian Local REST API with the Claude Code
MCP client — pre-fix: number/array/object double-encode; post-fix: all JSON
types round-trip correctly.

Fix: replace `z.unknown()` with an explicit `z.union` of the documented JSON
types so the rendered schema instructs clients to send real typed values. No
caller-contract change. Adds round-trip coverage for string/boolean/array/object
in the set handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant