v0.2.1 — post-v0.2 hardening + README promo polish
Post-v0.2 hardening pass closing ten correctness issues from two rounds of Copilot review on the v0.2 PR wave (#22, #25, #26, #29), plus a small wire-level rename for naming consistency (meta.body_inclusion.inline_cap_bytes → inline_body_cap_bytes) — taken now while there are 0 npm installs to migrate. README also reorganised for promo readiness (per-client install snippets, body_mode and server_info sections, concrete token-savings table on real APIs).
Fixed
body_too_large_for_inlinenow actually surfaces a usablecache_id. Previous code returned the error beforecache.put, anderror.detailomittedcache_identirely, so the recovery hint ("usehttp_readwith the returned cache_id") was a lie. Entry is now cached with a placeholderbody_inclusionandcache_idis spliced into the error detail.- Cache-on-error narrowed to
body_too_large_for_inlineonly. First pass cached on every!resolution.okbranch and stamped a "cap exceeded" reason on every error'sbody_inclusion, which was wrong forinvalid_input(e.g.body_mode: 'inline'on a binary response). Other resolution errors now surface as-is — no spurious cache entry, nocache_idin detail. decodeBase64rejects malformed input loudly.Buffer.from(s, 'base64')does not throw — it silently strips invalid characters. Strict alphabet/length validation (^[A-Za-z0-9+/]*={0,2}$+ length divisible by 4 +typeofguard) added before decoding, so genuinely-badcontent_base64returnsinvalid_inputinstead of a degraded buffer.decodeBase64pre-decode size guard. Encoded length is checked againstceil(cap/3)*4 + slackBEFOREBuffer.fromallocates — a 1 GB syntactically-valid base64 string would otherwise OOM before the post-decode cap check.- Path guard on multipart files tightened. Schema-bypass payloads like
{content_base64: "...", path: null}previously reachedrealpathSync(null)and crashed; now the guard usestypeof file.path !== 'string'and falls through to the inline branch. inferNextStepHintsno longer emits invalid jq for non-identifier keys. Keys with hyphens, leading digits, or spaces produced parse errors when interpolated into.fooor{foo}shorthand. Identifier-safe filter added; non-identifier keys are skipped from projection shorthand or rendered with bracket-quoted form."weird-key"for path access. Bracket-quoted form strips the entire ASCII control range (\x00-\x1Fplus\x7F), not just CR/LF/TAB/NUL.- Multipart
oneOfvariants gainadditionalProperties: false. Closes the schema-level hole where{path, content_base64}and similar mixtures could match a variant unchanged. writeWithBackpressureracesdrainagainsterror/close. Awaiting onlydrainmeant a disk-full / EPERM during backpressure left the request hung forever. Three-way race with cleanup of unused listeners.- Redirect 301/302/303 method downgrade now drops ALL
content-*headers, not justcontent-typeandcontent-length. Closescontent-encoding/content-language/content-md5leaks across the redirect.
Changed
meta.body_inclusion.inline_cap_bytes→inline_body_cap_bytes(and same key inerror.detailonbody_too_large_for_inline). Now matches the env var (MAGPIE_INLINE_BODY_CAP) and theserver_info.effective_limits.inline_body_cap_bytesfield. Wire-level rename — safe right now (0 installs).- README reorganised. Per-client install snippets (Claude Desktop, Claude Code, VS Code
.vscode/mcp.json, Cursor, Cline, Windsurf, Docker) replace the v0.1 TL;DR. Newbody_modemodes table,server_infodebug callout,content_base64upload example, full env-var refresh, and a "How much context does this actually save?" table with approximate savings on GitHub Issues / Stripe Charges / OpenWeather forecast. - npm-downloads badge added to the badge row.
Spec
-
MAX_RESPONSE_BYTES→MAGPIE_MAX_RESPONSE_BYTESin §6 and §9. -
body_inclusionshape example:inline_cap_bytes→inline_body_cap_bytesto match the wire format. -
Literal
|escaped inside the markdown table cell for thenext_step_hintsObject-with-array-fields sample. -
Run-modes matrix entry for
multipart.files[].content_base64reads "works (zero-volume Docker)" instead of "n/a", consistent with the §8.2 copy that promotes Docker for the inline-payload path. -
Comment fix in
src/core/paths.tsdescribing why root='/' needs the short-circuit.Full changelog: CHANGELOG.md