v0.2.0 — agent-UX bundle
Agent-UX bundle. Breaks the public API on one parameter (include_body → body_mode). Zero migration cost in practice (no installed v0.1.x users this early), so we took the rename window before usage solidifies. Adds three orthogonal capabilities (head mode preview, next_step_hints, server_info debug tool) plus the content_base64 multipart payload mode that closes remote-MCP file asymmetry. Pre-bundle hardening pass closes 16 issues from the Copilot audit (header injection, cache_id randomness, symlink-resolved path validation, redirect method downgrade, more).
Breaking
include_bodyis removed. Usebody_mode: "auto" | "schema" | "head" | "inline"(default"auto"). Sending the legacy field returnserror.kind = "unsupported_field"with a migration hint. Mapping:true → "inline",false → "schema","auto" → "auto".meta.body_included: booleanis replaced bymeta.body_inclusionwithresolved_mode,inline_threshold_bytes,head_preview_threshold_bytes,head_preview_items,head_preview_string_chars,inline_cap_bytes, optionalreason. Lets agents introspect whatautoactually picked.
Added
body_mode: "head"— schema +body_previewwith arrays/strings truncated and sibling_truncatedmarkers.autoupgrades through head betweenMAGPIE_INLINE_THRESHOLD_BYTESandMAGPIE_HEAD_PREVIEW_THRESHOLD.next_step_hints— advisory jq-mask suggestions inferred from top-level shape (per spec §5.6). Server proposes; agent picks; server doesn't run them.server_infotool — new 4th MCP tool. No params. Returnsversion,runtime(npx/docker/unknown),cwd,files_root, and a 15-fieldeffective_limitsobject. Use it when a path is rejected unexpectedly or to confirm runtime.multipart.files[].content_base64— alternative file payload mode. The bytes travel inline in the JSON-RPC frame, capped byMAGPIE_MAX_INLINE_FILE_BYTES(default 10 MB). Recommended for remote-MCP scenarios where path semantics differ between agent and server.MAGPIE_FILES_ROOTdoes not apply (no path).- New error kinds —
body_too_large_for_inline(withcache_idindetailso agent can pivot tohttp_read),unsupported_field. - New env vars —
MAGPIE_HEAD_PREVIEW_THRESHOLD(64 KB),MAGPIE_HEAD_PREVIEW_ITEMS(5),MAGPIE_HEAD_PREVIEW_STRING(200),MAGPIE_INLINE_BODY_CAP(256 KB),MAGPIE_MAX_INLINE_FILE_BYTES(10 MB).MAGPIE_AUTO_INCLUDE_BODY_BYTESis renamed toMAGPIE_INLINE_THRESHOLD_BYTES(same default 8192).
Hardening (pre-bundle Copilot audit pass)
- Multipart header injection — CR/LF/NUL in user-controlled header positions (field name, filename, content_type) is now rejected with
invalid_input; backslash and double-quote in name/filename are RFC 7578 escaped. Validation eager (fails fast onbuildMultipartcall). cache_id— nowcrypto.randomBytes(16)hex (128 bits) instead ofMath.random().- Symlink escape —
ensureUnderRootresolves throughrealpathSyncfirst (lexical fallback for non-existent paths) so symlinks can't escapeMAGPIE_FILES_ROOT. isUnderRootroot='/' — priorrr + sepproduced'//'and rejected every absolute path; short-circuit added.- Redirect 301/302/303 — downgrade unsafe methods to GET, drop body and content-* headers (RFC 7231 §6.4). 307/308 preserve.
- Header case-sensitivity — incoming headers normalised to lowercase to avoid duplicate Content-Type when caller passes mixed case.
- download_to backpressure —
stream.writereturn value honoured (await'drain'); error listener attached. - download_to partial cleanup — best-effort
unlinkof partial file onbody_too_largeor stream error. - jq timeout detection —
JqTimeoutErrorclass instead of substring sentinel match. - jq output_mode='first' empty output — returns
null(notundefined). renderSchemaexhaustiveness — switch gainsdefaultwithnever-typed exhaustiveness guard.schema/pathsroot depth overflow — uses(root).???instead of stray-leading-dot.???.http_readbinary cast guard — surfaces a clear error instead of crashing when the original request useddownload_to(body=null).pathEnvwhitespace — trims beforepath.resolve.- Version drift — server version now injected at build time from
package.jsonvia tsupdefine, not a hardcoded literal.