zipnative-mcp
An offline MCP server that creates, inspects, verifies, extracts, modifies, canonicalises and merges ZIP archives safely — no network, ever.
Inspect (
inspect_zip): one-call report — sizes, counts, methods, determinism verdict, diagnostics; usable as a CI gate viacheck/assert.List entries (
list_zip_entries): paged central-directory inventory with filtering (names/prefix/glob) and paging, nothing decompressed.Read one entry (
read_zip_entry): random access by name or index — base64/UTF-8 data, byte ranges, raw compressed payload, or a non-throwing verify.Verify (
verify_zip): deep per-entry CRC/size/local-header check in one call; returns a report, neverisErrorfor archive problems.Extract securely (
extract_zip): secure-by-default extraction (anti zip-slip, symlinks, bombs, duplicates) inline or into the sandbox, with dry-run plans.Forward-scan (
scan_zip_forward): bounded-memory walk of truncated/unseekable streams (local headers only, non-authoritative).Sanitize paths (
sanitize_entry_paths): apply the traversal gate to any list of names.Create (
create_zip): reproducible ZIPs from text/base64/sandbox files — canonical order, UTC dates, deterministic mode, worker pool, Zip64, SHA-256 proofs.Modify (
modify_zip): add/replace/remove/rename/setComment without recompressing; append (remnant) or compact (real delete).Canonicalize (
canonicalize_zip): rewrite foreign archives to the canonical deterministic form without recompression.Merge (
merge_zip): combine up to 32 archives without recompression, with CRC/size verification and conflict policies.CRC-32 (
compute_crc32): checksum of text, base64 or a sandbox file, with seeding and expected-value comparison.Inflate raw (
inflate_raw): decompress raw DEFLATE with a mandatorymaxOutputbound.Describe engine (
describe_engine): offline preflight of versions, codecs, limits, ceilings, caps, error and diagnostic codes.Draft issue (
draft_governance_issue): locally produce a HITL-compliant GitHub issue draft for a human to submit.Prompts & resources: 7 MCP prompts plus
zipnative://output/{+path}resources over sandbox files; stdio or loopback HTTP with optional bearer token.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@zipnative-mcplist the files in downloads/archive.zip without extracting it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
zipnative-mcp
MCP server for ZIP archives — deterministic creation, inspection and listing without extracting, random-access entry reads (in memory or by byte ranges for sandbox archives larger than memory), secure-by-default extraction, one-call verification, forward scanning of truncated streams, incremental modification, canonicalisation and merging without recompression, CRC-32 and raw-DEFLATE inflation — 15 tools and 7 prompts on the zipnative engine (zero-dependency, ISO/IEC 21320-1 conformance validated in CI), for Claude Desktop, Cursor, ChatGPT and any MCP client. No network, ever.
✨ Features
zipnative-mcp 1.1.0 (on zipnative 1.1.0) exposes 15 tools to any MCP host, listed here in tools/list order. The consumer contract — catalogue, decision tree, token economy, recipes and the complete error table — is docs/AGENT_CONTRACT.md.
Tool | What it does | Read-only |
| ONE-call archive report: sizes, entry / file / directory counts, Zip64, comment, compressed vs uncompressed totals, per-method counts, encrypted / symlink / data-descriptor / Zip64 / cp437 / duplicate / unsafe-name counts, date range, a determinism verdict (taken from the engine's | ✓ |
| Paged central-directory inventory, nothing decompressed — every entry as a full row (sizes, CRC-32, method, timestamp, flags, Unix mode, symlink, Zip64, offsets, extra fields, the sanitized path an extraction would use, raw name bytes; opt-in extended timestamps and Unix owner ids). | ✓ |
| ONE entry by | ✓ |
| Deep verification in one call — the engine's | ✓ |
| Secure by default: zip-slip / device names, symlinks, duplicate paths, declared-size and ratio bombs, overlapping entries and central/local divergence refused with frozen | |
| Walk local headers in stream order with bounded memory — the only tool that works on a truncated download or a cut / unseekable stream that starts at a local header, and the only one whose result is NOT authoritative ( | ✓ |
| The engine's single traversal gate over a list of names: the safe | ✓ |
| Write a ZIP from inline text / base64 / sandbox files (up to 100 000 entries — Zip64 auto-promotes past 65 535). Reproducible by default (canonical order, DOS-epoch timestamps written in UTC, UTF-8 names); | |
| add / replace / remove / rename / setComment without recompressing anything. | |
| Make ANY archive reproducible: the engine's | |
| 1 to 32 source archives into one without recompressing anything ( | |
| The ZIP checksum (IEEE 802.3 CRC-32, the engine's slice-by-8) of text, base64 or a sandbox file streamed in 1 MiB chunks; | ✓ |
| Raw DEFLATE (RFC 1951) through the engine's resumable inflater with a mandatory | |
| Offline preflight: versions, deflate tiers, runtime codecs (with | ✓ |
| Draft a governance-compliant GitHub issue locally for a human to review and submit — never submits, no network, no GitHub write path. |
What every tool guarantees:
🔐 Secure by default — every engine guard is on; a relaxation is an explicit, named input (
rejectTraversal,rejectSymlinks,onDuplicate,limits) that skips, never emits an unsafe path or materialises a link. Overlaps, central/local divergence and Zip64 spoofing have no opt-out at all.🌐 No network, ever — the server has no network code path: no telemetry, no GitHub, no URL from any argument, no remote byte-range source, no operator-configurable endpoint. The only filesystem boundary is
ZIPNATIVE_MCP_OUTPUT_DIR.🔁 Reproducible by default, deterministic on request —
create_zipemits canonical order, DOS-epoch timestamps and UTF-8 names unless you opt out, and every DOS date is written and read in UTC (dosTimeMode: 'utc'), so a pinned date gives the same bytes in Paris and on a UTC runner;compression.deterministic: truepins the pure-TypeScript encoder for the same SHA-256 on every runtime and is the only setting under whichsummary.deterministicistrue;canonicalize_zipbrings any foreign archive into the canonical form;parallelis byte-identical.🧭 Frozen
ZIP_*error codes, verbatim — the engine's 39-code vocabulary reaches you unchanged in_meta.error.code(with the entry name, the limit that fired, both CRCs, …), plus the 17 wrapper codes, each with a remedy. Branch on the code, never on the message.🪙 Token-frugal projections — the read tools accept
verbosity: 'summary'andfields: […]; a produced archive arrives as an embeddedresourceblock, never instructuredContent(the block carries the bytes inbloband repeats them in itsdata:URI, sooutputMode: 'file'— aresource_link— is the token-frugal choice for anything sizeable).⏹ Cancellable, with progress — the request's cancellation signal and, when the client sends a
progressToken, a progress sink reach every tool whose work scales with its input exceptverify_zip(zipnative'sverifyZipis synchronous and takes no signal: a verify runs to completion and returns its report). A cancelled call is never cached and a partial output file is removed; over MCP the SDK sends no response to a cancelled request (CANCELLEDis the in-process code).📏 ISO/IEC 21320-1 conformance gate — every archive the tools write is validated clause by clause by an engine-independent validator (veraZIP) on Linux and Windows in CI and again before publish.
All archive-producing tools (create_zip, modify_zip, canonicalize_zip, merge_zip; inflate_raw for raw bytes) support two output modes:
base64(default) — the archive is returned as an embeddedresourcecontent block: the bytes are inresource.blob, andresource.urirepeats them as adata:application/zip;base64,…URI (unchanged since 1.0.0, so a client that decodes the URI keeps working) — the archive therefore travels twice in the block;structuredContentcarries{ mode, sizeBytes, summary, diagnostics, diagnosticCounts }, never the bytes.file— the archive is streamed into a sandboxed directory configured viaZIPNATIVE_MCP_OUTPUT_DIR(≤ 4 GiB by default,ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTESmoves the ceiling; never overwritten) and the result carries aresource_link(the cheapest response;structuredContent.filePathand the text line give the file's absolute host path, as in 1.0.0). File I/O is disabled unless this variable is set; absolute paths, traversal, non-container extensions and NUL bytes are all rejected, and the real path of every file read and of every parent written must stay inside the sandbox (a planted symlink or junction isSECURITY_VIOLATIONon both sides). The same sandbox serveszipPath/sourcePathinputs, so acreate_zip → modify_zip → verify_zip → extract_zipchain never re-sends the bytes.
Token-frugal reads. The read tools (inspect_zip, list_zip_entries, read_zip_entry, verify_zip, scan_zip_forward, sanitize_entry_paths, describe_engine) and extract_zip accept two optional inputs:
verbosity: 'summary'— a compact scalar-only verdict (drops the rows and payloads). E.g.verify_zip→{ ok, error, entryCount, verifiedCount, failedCount, skippedCount, diagnosticCount };inspect_zipkeepsdeterministic,canonicalLayout,checksPassed.fields: ['a', 'b.c']— projects the structured result to named dot-paths (≤ 16); composes afterverbosity. Unmatched paths are omitted and reported in_meta.unmatchedFields(with_meta.availableFields); a path holding a__proto__,constructororprototypesegment isVALIDATION_ERROR.
Smallest "is this archive intact?" probe: { "zipBase64": "…", "verbosity": "summary", "fields": ["ok", "error", "failedCount"] } on verify_zip.
Archives larger than memory. inspect_zip, list_zip_entries and read_zip_entry take access: 'auto' | 'memory' | 'range'. A sandbox zipPath above the 1 GiB in-memory cap is opened by byte ranges ('auto', the default, or always with 'range'): the engine's openZipRange over the sandbox file's own handle reads the end record and the central directory, then only the entries you touch — never a URL, never a remote source. See docs/guides/LARGE_ARCHIVES.md.
The server also ships seven MCP prompts — secure_extraction, reproducible_archive, incremental_update, forensic_scan, verify_before_trust, governance_contract, draft_issue_workflow — and exposes every file in the sandbox as a zipnative://output/{+path} resource.
Why zipnative?
zipnative-mcp inherits every guarantee of the underlying engine:
Zero runtime dependencies in the engine — pure TypeScript, no native bindings, no
eval; one API across Node ≥ 22, browsers, Deno, Bun and Workers (this server adds only the MCP SDK and zod: three runtime dependencies in total).Safe by default — path traversal, symlinks, duplicate names, decompression bombs, overlapping entries, parser-differential smuggling and Zip64 spoofing are refused, not guessed at; every parser loop runs under a named, CWE-tagged, caller-configurable bound (the nine
ZipLimitskeys).Random access and streaming — read one entry from a multi-gigabyte archive without touching the rest, in memory or through a byte-range source; iterate unseekable streams with bounded memory.
Deterministic — a written determinism contract: reproducible layout always, byte-stable per environment by default, the same SHA-256 on every runtime under
compression.deterministic: true, and a canonical form any archive can be brought into without recompression.Incremental modification — replace, remove, add or rename entries without recompressing the untouched 99 % (append-only), compact for true deletion, transplant entries between archives.
A frozen 39-code error vocabulary and
verifyZip()— one call, a machine-readable report that never throws for archive problems.The first open clause-by-clause ISO/IEC 21320-1:2015 validator, blocking in CI.
What it will NOT do: no encryption or decryption (no AES in the engine 1.x — AES is an engine 2.0 candidate; ZipCrypto is broken), no other archive formats, no multi-disk archives, no archive repair, no filesystem I/O in the engine, no network access, ever.
Related MCP server: ZIP MCP Server
🚀 Installation
# Run directly with npx (recommended for MCP clients)
npx -y zipnative-mcp
# Or install globally
npm install -g zipnative-mcp
zipnative-mcpRequirements: Node.js ≥ 22. From source: git clone https://github.com/Nizoka/zipnative-mcp.git && cd zipnative-mcp && npm ci --ignore-scripts && npm run build && node dist/cli.js.
⚙️ Configuration
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"zipnative": {
"command": "npx",
"args": ["-y", "zipnative-mcp"],
"env": {
"ZIPNATIVE_MCP_OUTPUT_DIR": "/Users/you/Documents/mcp-archives"
}
}
}
}Claude Code
.mcp.json at the root of the project (the repository ships one that drives the built server from dist/cli.js):
{
"mcpServers": {
"zipnative": {
"type": "stdio",
"command": "npx",
"args": ["-y", "zipnative-mcp"],
"env": { "ZIPNATIVE_MCP_OUTPUT_DIR": "${CLAUDE_PROJECT_DIR}/.mcp-archives" }
}
}
}Cursor / Continue / Zed / Windsurf / Cline / Roo Code
Any MCP-compatible client that supports stdio servers will work. Use the same command + args + env triple. Example for Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"zipnative": {
"command": "npx",
"args": ["-y", "zipnative-mcp"],
"env": { "ZIPNATIVE_MCP_OUTPUT_DIR": "/Users/you/Documents/mcp-archives" }
}
}
}Windsurf / Cline / Roo Code use the same shape inside their respective MCP config files.
VS Code
.vscode/mcp.json:
{
"servers": {
"zipnative": {
"type": "stdio",
"command": "npx",
"args": ["-y", "zipnative-mcp"],
"env": { "ZIPNATIVE_MCP_OUTPUT_DIR": "${workspaceFolder}/.mcp-archives" }
}
}
}🌐 Supported AI Ecosystem & Clients
zipnative-mcp is designed for MCP-native environments and works with clients that support MCP over stdio or Streamable HTTP: Claude Desktop, Claude Code, Cursor, Continue, Zed, Windsurf, Cline, Roo Code, VS Code, ChatGPT (Streamable HTTP), the MCP Inspector, and any client built on the MCP TypeScript / Python SDKs. Because the server never opens a socket of its own, it is also safe to run inside air-gapped or egress-restricted hosts.
It belongs to the "native" family of servers with pdfnative-mcp (PDF) and pkinative-mcp (PKI): the same thin-wrapper architecture, sandbox, cache, projection and governance conventions. The same engine is on the command line as zipnative-cli.
🔌 MCP protocol compliance
The server is built on the MCP TypeScript SDK v2 (@modelcontextprotocol/server ^2.3.1) and speaks MCP 2026-07-28:
Stateless serving —
server/discoverreplaces the session handshake; every result carriesresultTypeand the_metaserverInfoenvelope. Over HTTP, 2026-07-28 clients sendMcp-Method/Mcp-Nameheaders and theMCP-Protocol-Versionheader with eachPOST /mcp(a 2026-07-28 request without it is refused by the SDK with 400 /-32020).Cache hints —
tools/listandprompts/listarepublicwith a 24 httlMs,server/discoverispublicfor 1 h, andresources/list/resources/templates/list/resources/readareprivatewithttlMs: 0(sandbox files are per-host user data).Protocol errors —
tools/callwith an unknown tool name is a JSON-RPC error (-32602,[UNKNOWN_TOOL] Unknown tool: …) rather than anisErrorresult, and an unknown or malformed resource URI is-32602(Invalid params,UNKNOWN_RESOURCE), as the specification requires;isError: trueis reserved for execution failures.Cancellation and progress —
notifications/cancelled(or an HTTP client that goes away) stops the work between chunks on every tool whose work scales with its input — the engine for the archive tools, the tool's own chunk loop forcompute_crc32andinflate_raw— nothing is cached and a partial output file is removed.verify_zipis the exception: zipnative'sverifyZipis synchronous and takes no signal (an engine limitation), so a verify runs to completion and its result is returned;sanitize_entry_paths,describe_engineanddraft_governance_issuedo no work worth cancelling. As the MCP cancellation rules require, the SDK sends no response to a cancelled request;CANCELLED(data.remedy: 'retry') is what the tool returns in-process (callToolDirect); the server writes nothing to stderr for it. A request carrying_meta.progressTokenreceivesnotifications/progresswhoseprogressis strictly increasing across the whole call, throttled to one every 200 ms plus the one that completes a known entry count; without a token, no notification is sent.Automatic legacy fallback — a client that opens with
initializeis served through the SDK's legacy path on both stdio and HTTP for every revision the SDK negotiates: 2024-10-07, 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25 (legacy) next to 2026-07-28 (modern). Nothing changes for existing hosts.tests/protocol-revisions.test.tsproves each legacy revision negotiates exactly that revision and serves the 15 tools;tests/stdio-modern.test.tsandtests/http-modern.test.tsprove the 2026-07-28 path on stdio and HTTP.HTTP —
POST /mcponly;GET/DELETE /mcpanswer 405 (no SSE resumability; the server is stateless). Bound to127.0.0.1with aHost/Originguard (foreign values → 403; theOriginport must equal the server port);ZIPNATIVE_MCP_HTTP_TOKENadds an opt-in bearer-token gate (401+WWW-Authenticatewithout it); both checks run before the request body is read; bodies up to 256 MiB are accepted (413 above); a 30 s headers timeout, a 300 s request timeout, a 5 s keep-alive timeout and at most 100 request headers; every response carriesX-Content-Type-Options: nosniffandCache-Control: no-store.stdio — the SDK's 10 MiB default frame cap is raised to 256 MiB so multi-MiB base64 archives fit in one
tools/call; as in every SDK release, a request sent beforeinitializeis dropped without a reply on the legacy path.Output schemas — every
structuredContentvalidates against the tool'soutputSchema(a 2026-07-28 MUST), includingverbosity: 'summary'andfieldsprojections: the read tools declare projectable schemas (all properties optional,additionalProperties: falsekept). Input schemas carry no$schemakeyword and no$refby policy (some hosts forwardinputSchemato function-calling APIs that reject unknown keywords).serverInfocarrieswebsiteUrl; the resource template iszipnative://output/{+path}.
The tools/call payload (content, structuredContent, isError) is identical between the 2026-07-28 path and the legacy path; tests/http-modern.test.ts asserts it, and tests/schema-conformance.test.ts validates structuredContent with the SDK's JSON Schema 2020-12 validator.
Client | Transport | Protocol negotiated |
Claude Desktop, Cursor, Continue, Zed, Windsurf, Cline | stdio | legacy |
ChatGPT and other Streamable HTTP hosts | HTTP | legacy stateless streamable HTTP — unchanged |
MCP 2026-07-28 clients (SDK v2 | stdio / HTTP |
|
Compatibility matrix — every negotiated revision, on both transports, with the test that proves it:
Revision | Era | stdio | HTTP | Proven by |
2024-10-07 | legacy | ✓ | ✓ |
|
2024-11-05 | legacy | ✓ | ✓ |
|
2025-03-26 | legacy | ✓ | ✓ |
|
2025-06-18 | legacy | ✓ | ✓ |
|
2025-11-25 | legacy | ✓ | ✓ |
|
2026-07-28 | modern ( | ✓ | ✓ (+ bearer token) |
|
Autonomy proof. tests/autonomy.test.ts replays one scripted agent scenario (S0 handshake → S1 tools/list → S2 describe_engine → S3 create_zip file / base64 / parallel → S4 verify_zip → S5 inspect_zip checks → S6 list_zip_entries → S7 read_zip_entry / inflate_raw / compute_crc32 → S8 modify_zip append then compact → S9 extract_zip dry run then files → S10 resources/* → S11 scan_zip_forward → S12 hostile refusals → S13 ceilings → S14 protocol errors → S15 draft_governance_issue → S16 prompts/get → S17 clean shutdown) against the built dist/cli.js on four transports — stdio legacy, stdio modern, HTTP legacy, HTTP modern with ZIPNATIVE_MCP_HTTP_TOKEN — under a sandbox, a cache directory, ZIPNATIVE_MCP_MAX_ENTRIES=5000 and ZIPNATIVE_MCP_WORKERS=2, asserting no interaction, no unexpected isError, a code and a remedy on every refusal, a cache hit over the wire, a JSON-only stdout and a clean exit. tests/cancellation.test.ts drives cancellation and progress over MCP. To replay the scenario by hand from Claude Code, Claude Desktop or the MCP Inspector, see docs/guides/LOCAL_TESTING.md § "Drive it from Claude Code / Claude Desktop / MCP Inspector" (the repository ships a ready .mcp.json).
Environment variables
All eight are declared in server.json and validated at startup; an invalid value refuses to start — never a silent fallback (a ZIPNATIVE_MCP_PORT outside 1–65535 or not an integer, a relative ZIPNATIVE_MCP_OUTPUT_DIR or ZIPNATIVE_MCP_CACHE_DIR, a malformed ceiling, a weak token). The four operator ceilings are read once, at boot; change any variable by restarting the server. The token is never logged; the startup log on stderr states which HTTP mode is active and the ceilings in force.
Variable | Default | Meaning |
| (unset) | Absolute path of the one sandbox directory: |
| (unset) | Absolute path (a relative value refuses to start) of an opt-in persistent SHA-256-keyed result cache (1 h TTL; 256 MiB, the oldest-written entries evicted first — a hit does not refresh an entry; plaintext at rest; key namespaced by tool API + package + engine version and the four operator ceilings, so a lowered ceiling never serves an earlier result). Never caches |
| (unset → stdio) | When set to a valid port (1–65535), serves Streamable HTTP on |
| (unset) | (secret) Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every |
|
| Operator ceiling for |
|
| Operator ceiling for |
|
| Operator ceiling for |
|
| Operator ceiling for every file written in |
The operator ceilings. The engine's nine named ZipLimits bounds can be raised per call through the limits fragment for trusted input only. Four of those keys are bounded by two of the ceilings above — ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES (maxEntryUncompressedSize, maxEntryCompressedSize, maxTotalUncompressedSize) and ZIPNATIVE_MCP_MAX_ENTRIES (maxEntries, and the scan_zip_forward maxEntries input); ZIPNATIVE_MCP_WORKERS bounds create_zip parallel.workers. A per-call value above its ceiling is refused with LIMIT_CEILING_EXCEEDED and data.env names the variable an operator would raise. The fourth ceiling, ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, bounds no input: it caps every file written in file mode (OUTPUT_TOO_LARGE). There is no "unbounded" and no ceiling a tool argument can move. describe_engine reports the defaults, the ceilings (ceilings.env lists the four variables) and every cap in force.
🛠 Tool reference
Every archive-consuming tool takes exactly one of zipBase64 (the raw archive as base64 exactly once; a data:…;base64, prefix is tolerated; decoded ≤ 128 MiB) or zipPath (a relative path inside the sandbox; container extensions only — .zip .jar .war .ear .docx .xlsx .pptx .odt .ods .odp .epub .vsix .nupkg .whl .apk .ipa .xpi .crx .kmz; ≤ 1 GiB buffered, larger through access: 'range' on the three random-access tools). Every engine-touching tool takes limits (the nine named ZipLimits bounds — maxEntries, maxEntryUncompressedSize, maxEntryCompressedSize, maxTotalUncompressedSize, maxCompressionRatio, maxNameBytes, maxExtraFieldBytes, maxCommentBytes, maxCentralDirectoryBytes; values above the operator ceilings → LIMIT_CEILING_EXCEEDED) and strict (escalate the first diagnostic to ZIP_STRICT_DIAGNOSTIC). The read tools take verbosity ('full' | 'summary') and fields (≤ 16 dot paths). Every archive tool's result carries diagnostics[], de-duplicated by code and entry and capped at 200 rows (diagnosticsTruncated: true when the cap bites), plus diagnosticCounts (exact per code) on every one of them but verify_zip, whose 1.0.0 shape is kept. Error results: content[0].text = "<tool> failed [CODE]: message", isError: true, _meta.error = { code, message, data? }. A cancelled call ends in CANCELLED in-process (over MCP the cancelled request gets no response); verify_zip cannot be interrupted once its verification starts. Full input/output schemas are advertised by tools/list; the complete error table is in docs/AGENT_CONTRACT.md.
Dates are UTC. Every engine call runs with dosTimeMode: 'utc'. A date input with a Z or an offset is converted to UTC; an offset-less date-time is read as the UTC wall clock (the DOS fields 1.0.0 wrote for it on a UTC host); lastModified and earliestDate / latestDate read back as UTC instants. A pinned date therefore gives the same bytes on every host, whatever its time zone (tests/reproducible-build.test.ts runs the built server under two time zones). On a non-UTC host this is a change from 1.0.0 on both sides: a Z / offset date writes different DOS fields than 1.0.0 wrote there (compression.deterministic: true bytes included — they equal 1.0.0's on a UTC host), and every archive's dates read back shifted by the zone offset (the DOS epoch reads 1980-01-01T00:00:00.000Z, where 1.0.0 in Paris reported 1979-12-31T23:00:00.000Z); an archive another tool wrote in local time reads shifted by its producer's offset.
inspect_zip
Read-only archive report in ONE call, the zipnative inspect --json shape: size, entry / file / directory counts, Zip64, comment (+ commentHex), compressed vs uncompressed totals and ratio, per-method counts, encrypted / symlink / data-descriptor / Zip64 / cp437 / duplicate / unsafe-name counts, earliest / latest date, prependedData / multipleEocd, a determinism verdict (epochTimestamps, canonicalOrder, utf8Flags, noDataDescriptors, canonicalLayout, deterministic) and every diagnostic. The sub-checks come from the engine's analyzeDeterminism(); the default deterministic keeps the 1.0.0 formula (epoch timestamps + canonical order + UTF-8 flags — a streamed archive is reproducible but not canonical; a pinned non-epoch date is reproducible but reports deterministic: false here — this is the structural verdict, distinct from create_zip.summary.deterministic), so no existing CI gate flips. determinismReport: true adds the engine's stricter canonical-form verdict (also Zip64-only extras and the constant version-made-by) with every offending entry and the rule it breaks (≤ 200 rows, counts exact). It opens the archive eagerly by default: overlapping entries, entries reaching into the central directory or past EOF, and Zip64 spoofing are refused with their ZIP_* code rather than summarised. A method / CRC / size divergence between the central directory and a local header is not an extent problem: verify_zip (localHeaderMatch) is the one-call gate for it, and read_zip_entry / extract_zip raise ZIP_CD_LFH_MISMATCH / ZIP_SIZE_MISMATCH when the entry is read. check / assert turn it into a CI gate.
Input | Type | Default | Notes |
| string | — | exactly one |
|
|
|
|
|
|
|
|
| string[] ≤ 18 | — |
|
| boolean |
| add |
| string | — | the date the archive was canonicalised with ( |
| object | — |
|
| shared fragments |
Outputs: the report above plus checks[] ({ check, ok, detail }) and checksPassed when check / assert was supplied; determinismReport on request; access: 'range' when the byte-range reader served the call; diagnostics[]. Summary: archiveBytes, entryCount, isZip64, compressedBytes, uncompressedBytes, encryptedCount, unsafeNameCount, duplicateNameCount, deterministic, canonicalLayout, diagnosticCount, checksPassed.
{
"zipBase64": "<base64 ZIP>",
"check": ["deterministic", "no-encryption", "safe-names", "no-symlinks"],
"assert": { "maxEntries": 5000, "maxRatio": 100, "has": ["manifest.json"] },
"fields": ["checksPassed", "checks"]
}(fields alone keeps the checks[] rows; verbosity: 'summary' would drop them and leave only checksPassed.)
Errors: VALIDATION_ERROR, INPUT_TOO_LARGE, INPUT_NOT_FOUND, SECURITY_VIOLATION, INVALID_EXTENSION, IO_ERROR, LIMIT_CEILING_EXCEEDED, ZIP_STRICT_DIAGNOSTIC, and the structural ZIP_* refusals (ZIP_EOCD_NOT_FOUND, ZIP_EOCD_INCONSISTENT, ZIP_CD_INCONSISTENT, ZIP_RECORD_TRUNCATED, ZIP_SIGNATURE_MISMATCH, ZIP_ZIP64_LOCATOR_MISSING, ZIP_ZIP64_EOCD_MISPLACED, ZIP_ZIP64_CONTRADICTION, ZIP_ENTRY_OVERLAP, ZIP_CD_LFH_MISMATCH, ZIP_VALUE_UNREPRESENTABLE, ZIP_UNSUPPORTED_MULTI_DISK, ZIP_LIMIT_EXCEEDED).
list_zip_entries
Read-only central-directory inventory without decompressing anything: every entry (duplicates included) in directory order as a full row — index, name, nameEncoding, isDirectory, isSymlink, method / methodName, compressedSize, uncompressedSize, ratio, crc32, lastModified (UTC), isEncrypted, usesZip64, usesDataDescriptor, unixMode, sanitizedPath (null = the extraction gate would refuse it), comment, decoded flags, versionMadeBy / versionNeeded, hostSystem, attributes, localHeaderOffset, DOS date / time, named extraFields, rawNameHex, commentHex. Paged so a 100 000-entry archive stays token-bounded. nameEncoding is 'utf-8' or 'cp437' (the engine's 'custom' only comes from a nameDecoder, which this server does not expose).
Input | Type | Default | Notes |
| string | — | exactly one |
|
|
| as |
| integer ≥ 0 |
| skip this many matching entries |
| integer 1–2000 |
|
|
| object | — |
|
| boolean |
| extra-field payloads as base64 (forensics) |
| boolean |
| each row's |
| boolean |
| each row's |
|
|
| |
| shared fragments |
Outputs: entryCount, matchedCount, offset, limit, returnedCount, hasMore, isZip64, entries[], access (byte-range reads only), diagnostics[]. Summary: the counts plus names (this page).
{ "zipBase64": "<base64 ZIP>", "filter": { "prefix": "src/", "glob": ["**/*.json"] }, "limit": 50, "fields": ["entries.name", "entries.uncompressedSize", "hasMore"] }Errors: as inspect_zip (structural refusals appear on validate: 'eager' or when the central directory itself is broken); an invalid glob (an inverted class range such as [z-a]) is VALIDATION_ERROR.
read_zip_entry
Read ONE entry without extracting the archive, by name (exact, case-sensitive; the last duplicate wins) or index (central-directory position — disambiguates duplicates). mode: 'data' (default) returns the decompressed content, CRC-verified, as dataBase64 or as text with encoding: 'utf8'; a range reads a byte window through the chunked stream (bounded memory, no CRC for a window). mode: 'raw' returns the compressed payload verbatim (zero-copy — feed it to inflate_raw). mode: 'verify' returns verification: { ok, crcMatch, sizeMatch, localHeaderMatch, skipped? } only — the engine's verdict, with the real local-header comparison even for an entry it cannot decode and skipped ∈ 'encrypted' | 'stream-only-codec' | 'unsupported-method'. Encrypted entries are refused in modes data and raw (ZIP_UNSUPPORTED_ENCRYPTION — no decryption in this engine). Over a byte-range reader (access: 'range', or a zipPath above 1 GiB), only the bytes of the entry you read are fetched; limits.maxEntryCompressedSize bounds one fetched payload.
Input | Type | Default | Notes |
| string | — | exactly one |
| string / integer ≥ 0 | — | exactly one |
|
|
| as |
|
|
| |
|
|
| mode |
| boolean |
| mode |
|
| — | mode |
| boolean |
| as |
| shared fragments |
Outputs: entry (the full row), mode, sizeBytes, crcVerified, encoding, dataBase64 / text + textLength, range: { offset, length, returned }, verification, access (byte-range reads only), diagnostics[]. Inline cap 16 MiB per entry (ENTRY_TOO_LARGE, data.remedy: 'range'): in mode: 'data' read a range window or use extract_zip file mode; a raw payload above the cap is not retrievable inline at all — the same remedies yield the decompressed content. An out-of-range index is ZIP_ENTRY_NOT_FOUND naming the valid range (data.remedy: 'list_zip_entries').
{ "zipBase64": "<base64 ZIP>", "name": "META-INF/MANIFEST.MF", "encoding": "utf8" }Errors: ZIP_ENTRY_NOT_FOUND, ENTRY_TOO_LARGE, ENTRY_NOT_UTF8, ZIP_UNSUPPORTED_ENCRYPTION, ZIP_UNSUPPORTED_METHOD, ZIP_CRC_MISMATCH, ZIP_SIZE_MISMATCH, ZIP_DEFLATE_CORRUPT, ZIP_DEFLATE_TRUNCATED, ZIP_INFLATE_OUTPUT_OVERFLOW, ZIP_CD_LFH_MISMATCH, ZIP_ENTRY_OVERLAP, ZIP_RECORD_TRUNCATED (a byte-range source that delivers fewer bytes than asked), plus the structural refusals and wrapper codes above.
verify_zip
Deep verification in one call — zipnative's verifyZip report verbatim: eager structural validation, then every entry's CRC-32 / size / local-header agreement. ok is true when the structure is valid AND every verifiable entry passed. This tool never fails for a problem with the archive: a structural refusal lands in error: { code: 'ZIP_*', message }; an unverifiable entry carries its skipped reason. The counts follow the engine's archive rule: an encrypted or stream-only-codec entry whose local header agrees with the central directory is skipped and does not fail the archive; an entry whose method has no codec here (skipped: 'unsupported-method' — anything but 0 store / 8 deflate) and an encrypted entry whose local header contradicts the central directory are failed (ok: false). Only caller mistakes (bad limits, bad input) are isError. Verify before you trust: run it on any archive from an untrusted source before extract_zip.
Input | Type | Default | Notes |
| string | — | exactly one |
|
|
| which per-entry rows to return |
| integer 1–100000 |
| cap on returned rows ( |
| shared fragments |
Outputs: ok, error (or null), entryCount, verifiedCount, failedCount, skippedCount, entries[] ({ name, ok, crcMatch, sizeMatch, localHeaderMatch, skipped? }), entriesTruncated, diagnostics[] (de-duplicated by code and entry, ≤ 200 rows, diagnosticsTruncated: true only when the cap bites; no diagnosticCounts, to keep the 1.0.0 shape). An entry stored with a method this server has no codec for (anything but 0 store / 8 deflate — Zstandard, LZMA, …) comes back with skipped: 'unsupported-method' and ok: false and counts in failedCount: relay it as "not checked", not as "corrupt" (merge_zip refuses such an entry under verify: true). The verification cannot be interrupted once it starts: zipnative's verifyZip is synchronous and takes no signal, so a cancellation that arrives during it is ignored and the call runs to completion.
{ "zipBase64": "<base64 ZIP>", "entries": "failed", "verbosity": "summary" }Errors (caller mistakes only): VALIDATION_ERROR, INPUT_TOO_LARGE, INPUT_NOT_FOUND, SECURITY_VIOLATION, INVALID_EXTENSION, IO_ERROR, LIMIT_CEILING_EXCEEDED, ZIP_LIMIT_INVALID.
extract_zip
Extract with every engine guard ON unless you name the relaxation: zip-slip / absolute / drive / UNC / NUL / ADS / Windows-device names → ZIP_PATH_TRAVERSAL (rejectTraversal: false SKIPS them, listed in skipped; an unsafe path is never emitted); symlinks → ZIP_SYMLINK_REJECTED (rejectSymlinks: false extracts the target text as data, never a link); duplicate sanitized paths → ZIP_EXTRACT_DUPLICATE_PATH (onDuplicate); declared sizes / ratios beyond limits → ZIP_LIMIT_EXCEEDED; overlapping entries and central/local divergence are always refused. The archive is opened eagerly before any plan in both modes, so the dry run runs the same open-time guards as the extraction: overlapping entries, entries reaching into the central directory or past EOF and Zip64 spoofing are refused with their ZIP_* code before a single plan row is returned (and a file-mode extraction fails before its first write, data.writtenCount: 0); a central/local divergence of method, CRC or sizes is detected when the entry is read — verify_zip (localHeaderMatch) is the one-call gate for it. In base64 mode the plan (filter, sanitize, symlink policy, duplicates, budgets) is then computed without touching any payload — includeData: false returns that plan as a dry run. In file mode every file is streamed under outputDir inside the sandbox: only the engine's sanitized path is joined, the parent's real path is checked, files are opened exclusively, and a symlink accepted through rejectSymlinks: false is written as a regular file holding the link target. The archive itself is read in memory (≤ 1 GiB for a zipPath).
Input | Type | Default | Notes |
| string | — | exactly one |
|
|
| inline ≤ 16 MiB per file, ≤ 32 MiB total; file mode bounded by |
| string | — | file mode: relative directory inside the sandbox (created; never overwrites) |
| boolean |
| base64 mode: |
| object | — | as |
| boolean |
|
|
| boolean |
|
|
|
|
| two entries → one sanitized path |
| boolean |
| file mode: create explicit directory entries that hold no file |
| shared fragments |
Outputs: mode, dryRun, fileCount, totalBytes, entries[] ({ path, entryName, index, sizeBytes, crc32, method, dataBase64? | filePath? }), skipped[] ({ entryName, reason: 'unsafe-path' }), outputDir, directoriesCreated, diagnostics[]; file mode adds up to 50 resource_link content blocks. Summary: mode, dryRun, fileCount, totalBytes, skippedCount, paths.
{ "zipBase64": "<base64 ZIP>", "outputMode": "file", "outputDir": "unpacked/report", "filter": { "glob": ["docs/", "*.json"] } }Errors: ZIP_PATH_TRAVERSAL, ZIP_SYMLINK_REJECTED, ZIP_EXTRACT_DUPLICATE_PATH, ZIP_LIMIT_EXCEEDED, ZIP_ENTRY_OVERLAP, ZIP_CD_LFH_MISMATCH, ZIP_UNSUPPORTED_ENCRYPTION, ZIP_UNSUPPORTED_METHOD, ZIP_CRC_MISMATCH, ZIP_SIZE_MISMATCH, ZIP_DEFLATE_CORRUPT, ZIP_DEFLATE_TRUNCATED, ENTRY_TOO_LARGE, OUTPUT_TOO_LARGE, OUTPUT_EXISTS, IO_ERROR, MISSING_OUTPUT_PATH, SECURITY_VIOLATION, CANCELLED, plus the structural refusals and wrapper codes.
scan_zip_forward
Walk LOCAL headers in stream order with bounded memory (zipnative's central-directory-less reader iterateZipEntries). The only tool that works on a truncated archive or a stream cut mid-way — the stream must start at a local header: it cannot skip an SFX / prepended prefix and refuses one with ZIP_SIGNATURE_MISMATCH (route prefixed archives to inspect_zip, which reports prependedData / ZIP_PREPENDED_DATA and shifts offsets; modify_zip mode: 'compact' drops the prefix) — and the only one whose result is NOT authoritative: forward iteration trusts local headers ALONE, so a hostile archive can present different content here than inspect_zip / list_zip_entries report (the upload-scanner differential). Prefer those tools whenever the whole archive is available; use this one for forensics. Names are NOT sanitized — sanitizedPath shows what the extraction gate would use. A zipPath is streamed from disk through one open handle without the 1 GiB buffered cap. The wrapper never reads a PK signature: stoppedAt is derived from byte accounting (the engine leaves the rest of the stream unread when it meets a central directory). Corrupt deflate data is ZIP_DEFLATE_CORRUPT / ZIP_DEFLATE_TRUNCATED on every runtime tier (1.0.0 reported ZIP_DECOMPRESSION_FAILED here).
Input | Type | Default | Notes |
| string | — | exactly one |
|
|
|
|
| object | — | as |
| integer ≥ 1 |
| the default is clamped to the |
| boolean |
|
|
| shared fragments |
Outputs: trust: 'local-headers-only', entryCount, keptCount, entries[] (rows with kept and data: { verified, bytesProduced, dataBase64? }; isSymlink / usesZip64 / unixMode are null), stoppedAt ('central-directory' — the reader met a central directory and left the rest unread | 'eof' — the whole stream was consumed without one: truncated or CD-less | 'max-entries' — more local headers remained past the budget (an archive with exactly maxEntries entries ends at 'central-directory') | 'error'), truncated, inputBytes, bytesDelivered, maxEntries (the budget in force after clamping), error, diagnostics[].
{ "zipBase64": "<base64 of a truncated download>", "tolerateTruncation": true, "verbosity": "summary" }Errors: ZIP_STREAM_TRUNCATED, ZIP_SIGNATURE_MISMATCH, ZIP_RECORD_TRUNCATED, ZIP_DESCRIPTOR_MISMATCH, ZIP_UNSUPPORTED_CD_LESS_DESCRIPTOR, ZIP_UNSUPPORTED_ENCRYPTION, ZIP_UNSUPPORTED_METHOD, ZIP_CRC_MISMATCH, ZIP_SIZE_MISMATCH, ZIP_DEFLATE_CORRUPT, ZIP_DEFLATE_TRUNCATED, ZIP_INFLATE_OUTPUT_OVERFLOW, ZIP_LIMIT_EXCEEDED, ENTRY_TOO_LARGE, OUTPUT_TOO_LARGE, LIMIT_CEILING_EXCEEDED (all engine codes land in error under tolerateTruncation: true).
sanitize_entry_paths
Apply zipnative's single traversal gate sanitizeEntryPath() to a list of names: the safe /-separated relative form each maps to (join it under your extraction root — never the raw name), or null with the rule that fired. Use it when you extract with another tool or plan an external filesystem sink. No archive needed.
Input | Type | Default | Notes |
| string[] 1–10000 | — | each ≤ 4096 chars, as stored in an archive |
| projection |
Outputs: count, rejectedCount, results[] ({ name, sanitized, rejected, reason? } with reason ∈ empty, nul, absolute, drive, unc, traversal, ads, device-name, no-segments). Summary: count, rejectedCount, rejectedNames.
{ "names": ["docs/readme.md", "../../etc/passwd", "C:\\Windows\\win.ini", "aux.txt", "a/./b//c.txt"] }Errors: VALIDATION_ERROR.
create_zip
Write a ZIP from inline entries (dataBase64 | text | sourcePath in the sandbox, or directory: true). Reproducible by default: canonical order (raw UTF-8 name bytes), DOS-epoch timestamps (every date written in UTC), UTF-8 names, constant attributes — defaultDate: 'now' opts out (ZIP_TIMESTAMP_NOT_PINNED diagnostic). compression.deterministic: true pins the pure-TS encoder so the bytes are identical on every runtime (summary.deflateTier: 'pure-pinned'); the default node-zlib tier is byte-stable per environment only. Three determinism verdicts, read them where they apply: summary.deterministic is true only with compression.deterministic: true on every entry and no wall-clock date (cross-runtime identity — a default call reports false although its bytes are reproducible on the same runtime); summary.sha256 (includeSha256, base64 and file mode) is the proof; inspect_zip.determinism.deterministic is the structural verdict against the canonical defaults (epoch timestamps + canonical order + UTF-8 flags — a pinned non-epoch date reproduces but reports false there). order: 'insertion' keeps call order for EPUB / JAR (mimetype first, stored). stream: true on a sourcePath feeds the file through addStream from one open handle (data-descriptor layout, bounded memory); a streamed entry that crosses 4 GiB is ZIP_UNSUPPORTED_ZIP64_STREAMING unless zip64: true opts it into the Zip64 streaming layout (APPNOTE 4.3.9.2). parallel compresses with the worker pool — byte-identical output; with no workers the engine default max(1, min(cores − 1, 8)) applies, bounded by ZIPNATIVE_MCP_WORKERS. Zip64 is emitted exactly when a field overflows — reachable here: up to 100 000 entries per call (the engine default maxEntries and the ZIPNATIVE_MCP_MAX_ENTRIES ceiling remain the effective bound), so a 65 536-entry archive auto-promotes. A name ending in / denotes a directory and cannot carry a payload (VALIDATION_ERROR — drop the slash or pass directory: true). Every archive it writes is ISO/IEC 21320-1 conformant (CI-validated).
Input | Type | Default | Notes |
| array 0–100000 | — | required; each: |
|
|
| |
| string | DOS epoch | ISO-8601 or |
| object | deflate 6 |
|
| string ≤ 65535 | — | archive comment |
| integer 1024–16777216 | 64 KiB | file mode streaming chunk; never changes the bytes |
| object | — |
|
| boolean |
|
|
|
| file mode: relative path ending in a container extension | |
| shared fragments |
A zip64: true entry below 4 GiB still carries the Zip64 records, which some streaming readers (Java's ZipInputStream) cannot read — opt in only for entries that may cross 4 GiB, and raise ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES to write the result into the sandbox.
Outputs: the archive as an embedded resource (≤ 50 MiB) or a resource_link; structuredContent: mode, sizeBytes, filePath, summary (typed in the tool's outputSchema: entryCount, fileCount, directoryCount, streamedCount, uncompressedBytes, order, deterministic, deflateTier, parallel: { requested, workers }, sha256), diagnostics[].
{
"entries": [
{ "name": "mimetype", "text": "application/epub+zip", "compression": { "method": "store" } },
{ "name": "META-INF/container.xml", "text": "<container/>" },
{ "name": "OEBPS/", "directory": true }
],
"order": "insertion",
"compression": { "deterministic": true },
"includeSha256": true,
"outputMode": "file",
"outputPath": "books/sample.epub"
}Errors: VALIDATION_ERROR, INPUT_TOO_LARGE, INPUT_NOT_FOUND, OUTPUT_TOO_LARGE, OUTPUT_EXISTS, IO_ERROR, MISSING_OUTPUT_PATH, INVALID_PATH, INVALID_EXTENSION, SECURITY_VIOLATION, LIMIT_CEILING_EXCEEDED, CANCELLED, ZIP_INVALID_ENTRY_NAME, ZIP_INVALID_OPTION, ZIP_LIMIT_EXCEEDED, ZIP_UNSUPPORTED_ZIP64_STREAMING, ZIP_INPUT_TOO_LARGE, ZIP_STRICT_DIAGNOSTIC, ZIP_API_MISUSE.
modify_zip
Edit an existing archive without recompressing anything: operations add / replace / remove / rename / setComment applied in order. The input is opened eagerly in both modes — the overlap table and every entry's extent are checked before any operation runs, so an overlapping or truncated-header archive is refused even in mode: 'append'; before an append-mode save every non-encrypted entry is also checked against its local header and its declared sizes (a raw read, nothing decompressed), so a method / CRC / size divergence (ZIP_CD_LFH_MISMATCH) or a declared-size bomb (ZIP_LIMIT_EXCEEDED) is refused as mode: 'compact' and the other writers refuse it. Payloads are otherwise copied verbatim and never decoded: a corrupt payload or an unsafe name already in the archive (../../evil.txt) is carried into the output unchanged — extract_zip still refuses such a name. mode: 'append' (default) = save(): the original bytes stay verbatim and edits are appended — fast and byte-preserving, BUT removed or replaced payloads remain in the file (data remanence: recoverable by anyone; a ZIP_DEAD_BYTES_RATIO diagnostic fires past 50 % dead bytes, and 7-Zip is known to read the stale payload). mode: 'compact' = saveCompact(): a rewrite without recompression that sorts the entries by raw name bytes (an insertion-ordered layout — EPUB mimetype first, JAR manifest first — is not preserved: rebuild such a container with create_zip order: 'insertion'), removed data truly gone, SFX prefix dropped; add canonical: {…} to write the canonical deterministic form after the edits (the rules of canonicalize_zip). No edits and an unchanged comment return the same bytes (summary.noOp). Archives with duplicate entry names are refused.
Input | Type | Default | Notes |
| string | — | exactly one |
| array 1–1000 | — |
|
|
|
| |
| object | — |
|
| object | engine default | for new payloads (same shape as |
| string | DOS epoch | for new payloads; ISO-8601 or |
| boolean |
| |
|
| ||
| shared fragments |
Outputs: the archive as an embedded resource or a resource_link; summary (saveMode, operationsApplied, entryCountBefore, entryCountAfter, inputBytes, outputBytes, grewByBytes, noOp, canonical (present when the canonical form was written), sha256), diagnostics[].
{ "zipBase64": "<base64 ZIP>", "operations": [{ "op": "remove", "name": ".env" }, { "op": "rename", "from": "config.json", "to": "config/app.json" }], "mode": "compact", "canonical": {} }Errors: ZIP_ENTRY_NOT_FOUND, ZIP_ENTRY_EXISTS, ZIP_DUPLICATE_ENTRY_NAME, ZIP_INVALID_ENTRY_NAME, ZIP_ENTRY_OVERLAP, ZIP_CD_LFH_MISMATCH, ZIP_RECORD_TRUNCATED, ZIP_LIMIT_EXCEEDED, ZIP_STRICT_DIAGNOSTIC, plus the structural refusals, the output codes and the wrapper codes (data.operationIndex / data.op name the failing operation).
canonicalize_zip
Make ANY archive reproducible. zipnative's canonicalizeZip() rewrites an archive you did not build — Gradle, Maven, zip, Windows Explorer, 7-Zip, bsdtar — into the canonical deterministic form the engine's own writer emits: entries sorted by raw name bytes, one timestamp (the DOS epoch, or the pinned date), the UTF-8 flag on every name, no extra field but Zip64, the constant version-made-by (0x032D), no data descriptor. No payload is recompressed or decoded: the compressed bytes are copied bit for bit, so a corrupt or encrypted entry travels unchanged — and names are copied verbatim too, so an unsafe name such as ../../evil.txt stays in the output (canonicalForm can still be true; extract_zip refuses the name, and merge_zip, which writes names through the checked writer, refuses it with ZIP_INVALID_ENTRY_NAME) — and the result is byte-stable across runtimes because no codec runs. Idempotent — canonicalising the output again yields the same bytes (summary.alreadyCanonical). The output is checked once more by the engine's analyzeDeterminism() with the same pinned date, and the verdict is summary.canonicalForm; verify it later with inspect_zip { check: ['canonical-form'], canonicalDate }. Archives with duplicate entry names are refused, as modify_zip refuses them. To change content and canonicalise in one call, use modify_zip mode: 'compact' with canonical.
Input | Type | Default | Notes |
| string | — | exactly one |
| string | DOS epoch | the one timestamp written on every entry (ISO-8601, 1980-01-01 … 2107-12-31, read as UTC; 2-second DOS granularity) |
| boolean |
| keep the archive comment and the entry comments (the canonical form carries none) |
| boolean |
| keep Unix modes / DOS attributes; |
| boolean |
|
|
|
| file mode: relative path ending in a container extension | |
| shared fragments |
Outputs: the archive as an embedded resource (≤ 50 MiB) or a resource_link; summary (entryCount, inputBytes, outputBytes, alreadyCanonical, canonicalForm, pinnedDate (UTC ISO-8601, or null for the DOS epoch), keepComments, keepExternalAttributes, sha256), diagnostics[].
{ "zipBase64": "<base64 ZIP>", "date": "2026-10-08T00:00:00Z", "keepExternalAttributes": false, "includeSha256": true }Errors: VALIDATION_ERROR (a malformed or out-of-range date), INPUT_TOO_LARGE, INPUT_NOT_FOUND, SECURITY_VIOLATION, INVALID_EXTENSION, IO_ERROR, LIMIT_CEILING_EXCEEDED, OUTPUT_TOO_LARGE, OUTPUT_EXISTS, MISSING_OUTPUT_PATH, CANCELLED, ZIP_DUPLICATE_ENTRY_NAME, ZIP_LIMIT_EXCEEDED, ZIP_STRICT_DIAGNOSTIC, and the structural ZIP_* refusals.
merge_zip
Several archives into one without recompressing anything: zipnative's ZipWriter.addFromReader() transplants each entry's compressed bytes with its method, CRC, sizes, timestamp, attributes, comment and extra fields (Zip64 recomputed). With verify: true (the default) every entry is decompressed once and checked against its CRC and size before it is copied, so a corrupt payload is refused with its frozen code instead of being propagated; an entry this server cannot decompress (a method other than 0 store / 8 deflate) is refused with ZIP_UNSUPPORTED_METHOD rather than copied unchecked (verify: false copies it as it is; summary.verified reports whether verification was on). Each name passes the writer's name rules, so a source entry the writer refuses — the ./ directory entry bsdtar writes, a .. segment, a device name — fails the merge with ZIP_INVALID_ENTRY_NAME and data.sourceIndex: exclude it with that source's filter. Encrypted entries are refused (ZIP_UNSUPPORTED_ENCRYPTION — they cannot be read raw); filter them out to merge the rest. Name collisions: onConflict: 'error' (default) lets the engine refuse the second occurrence (ZIP_DUPLICATE_ENTRY_NAME); 'first' / 'last' keep the first / last occurrence across the sources in order and report the others. The checked transplant is the only form exposed — the engine's addRaw() (where the caller vouches for CRC and sizes) stays unexposed, so an agent cannot forge an inconsistent archive through this tool. The result is ordered like create_zip (order: 'canonical' by default); run canonicalize_zip on it for the full canonical form. Every source is opened in memory.
Input | Type | Default | Notes |
| array 1–32 | — | required; each |
|
|
| two entries with the same name |
| boolean |
|
|
|
|
|
|
| string ≤ 65535 | — | archive comment of the result (the sources' comments are not merged) |
| boolean |
| |
|
| file mode streams the result into the sandbox | |
| shared fragments |
Outputs: the archive as an embedded resource or a resource_link; summary (sourceCount, entryCount, inputBytes, outputBytes, onConflict, verified, order, perSource[] ({ index, label, entryCount, taken, filteredOut, dropped }), dropped[] ({ name, source }, ≤ 200 rows), sha256), diagnostics[]. A refusal names the source in data.sourceIndex.
{ "sources": [{ "zipBase64": "<base64 ZIP A>" }, { "zipBase64": "<base64 ZIP B>", "filter": { "prefix": "assets/" } }], "onConflict": "last", "includeSha256": true }Errors: VALIDATION_ERROR, INPUT_TOO_LARGE, INPUT_NOT_FOUND, SECURITY_VIOLATION, INVALID_EXTENSION, IO_ERROR, LIMIT_CEILING_EXCEEDED, OUTPUT_TOO_LARGE, OUTPUT_EXISTS, MISSING_OUTPUT_PATH, CANCELLED, ZIP_DUPLICATE_ENTRY_NAME, ZIP_INVALID_ENTRY_NAME (a source name the writer refuses — filter it out), ZIP_CRC_MISMATCH, ZIP_SIZE_MISMATCH, ZIP_DEFLATE_CORRUPT, ZIP_DEFLATE_TRUNCATED, ZIP_UNSUPPORTED_METHOD (verify: true on a method with no codec here), ZIP_UNSUPPORTED_ENCRYPTION, ZIP_CD_LFH_MISMATCH, ZIP_ENTRY_OVERLAP, ZIP_LIMIT_EXCEEDED, ZIP_STRICT_DIAGNOSTIC, and the structural ZIP_* refusals.
compute_crc32
The ZIP checksum (IEEE 802.3 CRC-32, the engine's slice-by-8 implementation) of inline bytes / text or a sandbox file (streamed in 1 MiB chunks through one open handle, ≤ 1 GiB). seed continues a running CRC across consecutive chunks; expect compares against a known value. Use it to cross-check an entry's crc32 from list_zip_entries against a file on disk.
Input | Type | Default | Notes |
| string | — | exactly one |
| integer 0–4294967295 |
| running CRC to continue from |
| string | — | 1–8 hex digits, optional |
Outputs: crc32 (unsigned), hex (8 lowercase digits), byteLength, seed, matches (when expect was given).
{ "text": "hello world", "expect": "0d4a1185" }Errors: VALIDATION_ERROR, INPUT_TOO_LARGE, INPUT_NOT_FOUND, SECURITY_VIOLATION, IO_ERROR, CANCELLED (checked between 1 MiB chunks).
inflate_raw
Decompress a raw DEFLATE (RFC 1951) stream — e.g. the payload from read_zip_entry mode: 'raw' — with a mandatory maxOutput bound through zipnative's resumable inflater (constant memory, exact bytesConsumed, trailing bytes reported as leftover; ZIP_INFLATE_OUTPUT_OVERFLOW past the bound). method: 'store' is a bounded pass-through; a numeric method id selects a registered codec (none beyond 0 / 8 in this server → ZIP_UNSUPPORTED_METHOD).
Input | Type | Default | Notes |
| string | — | exactly one |
| integer ≥ 1 | — | required; inline results further capped at 50 MiB, file results at |
| string |
|
|
| boolean |
| accept bytes after the stream silently (always reported as |
|
| file mode: |
Outputs: the inflated bytes as an embedded resource (application/octet-stream) or a resource_link; summary (method, methodName, bytesIn, bytesOut, bytesConsumed, leftover, finished, maxOutput, trailingWarning).
{ "dataBase64": "<raw deflate payload>", "maxOutput": 1048576 }Errors: ZIP_INFLATE_OUTPUT_OVERFLOW, ZIP_DEFLATE_CORRUPT, ZIP_DEFLATE_TRUNCATED, ZIP_UNSUPPORTED_METHOD, ZIP_UNSUPPORTED_CODEC_MODE, ZIP_API_MISUSE, CANCELLED (checked between input chunks), the input wrapper codes and the file-mode codes (OUTPUT_EXISTS, MISSING_OUTPUT_PATH, INVALID_EXTENSION). A maxOutput above the inline or file cap is lowered to that cap, so an oversized result is ZIP_INFLATE_OUTPUT_OVERFLOW (data.configured = the effective bound), never OUTPUT_TOO_LARGE.
describe_engine
Offline preflight (no archive needed): server / engine / tool-API versions and the protocol string, the tool count, the deflate tier in use and the pinned deterministic tier, runtime codecs (node:zlib, CompressionStream, DecompressionStream) and worker threads, the codec registry (codecs[] with sync, stream and incremental — whether the forward and byte-range readers decode the codec chunk by chunk), the engine's default limits (the nine keys), the four operator ceilings (maxUncompressedBytes, maxEntries, maxWorkers, maxFileOutputBytes, with env naming the variables), every server cap, whether the sandbox and the cache are enabled, the 39 frozen ZIP_* error codes and 12 diagnostic codes, and the engine exports this server deliberately does not expose, each with its reason (nameDecoder, addRaw, registerCodec, the worker URL, …). network is always 'none'. Never cached.
Input | Type | Default | Notes |
| projection only |
Outputs: server, engine, capabilities, codecs[], defaultLimits, ceilings, caps, sandbox, network, errorCodes[], diagnosticCodes[], unexposed[].
{ "fields": ["defaultLimits", "ceilings", "caps", "sandbox"] }Errors: VALIDATION_ERROR.
draft_governance_issue
Draft a governance-compliant GitHub issue locally for a human to review and submit — for zipnative (engine behaviour: parsing, writing, limits, codecs) or zipnative-mcp (wrapper behaviour: schemas, sandbox, transport). The server never contacts GitHub — it has no network code path at all; it returns the draft Markdown plus a machine-readable compliance report. Present both to the user, then STOP: they review and submit under their own identity.
Input | Type | Default | Notes |
| string 8–160 | — | required |
| string ≥ 16 | — | required; free-text fields are bounded at 16 384 characters |
|
| — | required |
| string 1–100 |
| documentation only |
|
| — | required, both non-empty |
| string ≥ 4 | — | required |
| string | the reproduction result | |
| string[] ≤ 16 |
| |
| boolean | — | required, MUST be |
|
|
| file mode also writes a relative |
Outputs: title, issueType, targetRepo, outputMode, filePath, sizeBytes, draftMarkdown, warnings[], compliance (zeroDependencyConfirmed, reproductionCommand, reproductionResult, duplicateSearchPerformed, affectedPackages, identityReminderShown, humanGate, environment).
{
"title": "list_zip_entries: an optional sort order for the returned page",
"issueType": "feature",
"targetRepo": "zipnative-mcp",
"summary": "Paging through a 70 000-entry archive returns rows in central-directory order only; finding the largest entries means reading every page.",
"reproduction": { "command": "list_zip_entries { zipPath: 'big.zip', limit: 200, fields: ['entries.name', 'entries.uncompressedSize'] }", "result": "rows in central-directory order; no input sorts them by size or name" },
"expectedBehavior": "An optional sort input (name or uncompressedSize, ascending or descending) applied before offset and limit.",
"affectedPackages": ["zipnative-mcp"],
"duplicateSearchPerformed": true
}A draft that proposes a runtime dependency or sets duplicateSearchPerformed: false is rejected with GOVERNANCE_VIOLATION; a missing reproduction (a required input) is VALIDATION_ERROR. Other errors: VALIDATION_ERROR, MISSING_OUTPUT_PATH, INVALID_EXTENSION, OUTPUT_EXISTS, SECURITY_VIOLATION. See the governance_contract and draft_issue_workflow prompts and docs/guides/AI_GOVERNANCE.md for the full human-in-the-loop contract.
🔐 Security model
zipnative-mcp runs inside the host process and exposes a stdio MCP server (or a loopback-only HTTP endpoint). It treats every archive as untrusted input and performs no I/O outside the configured sandbox other than the opt-in response cache under ZIPNATIVE_MCP_CACHE_DIR.
Threat | Defence | CWE |
Zip-slip path traversal ( |
| CWE-22 / CWE-67 |
Decompression bombs (declared size, total, ratio, entry floods) | the nine | CWE-400 / CWE-409 |
Oversized payload fetched by the byte-range reader |
| CWE-770 |
Symlink entries redirecting extraction |
| CWE-59 |
Overlapping entries (one payload claimed by many entries) | always-on overlap detection → | CWE-405 |
Parser-differential smuggling (central directory vs local headers) | the central directory is authoritative; method / size / CRC divergence → | CWE-436 |
Zip64 sentinel spoofing | Zip64 records cross-checked against every non-sentinel classic field → | CWE-1288 |
Duplicate entry names (shadowing during extraction) |
| CWE-694 |
Ambiguous EOCD / trailing garbage, 64-bit fields above 2^53 | refused ( | CWE-190 |
Catastrophic backtracking on hostile entry names | globs compile to a Thompson NFA: one match is O(name × pattern), patterns ≤ 1 024 characters; the governance policy patterns are linear too | CWE-1333 |
A sandbox file swapped between the check and the read | every read goes through one open handle whose real path is proven inside the sandbox and bound to the handle by device and inode; a file that changes under the read is | CWE-367 |
The sandbox (
ZIPNATIVE_MCP_OUTPUT_DIR) is the one filesystem boundary for reads (zipPath,sourcePath,resources/read, amerge_zipsource, the forward scanner's stream, a byte-range reader) and writes (outputPath,outputDir, extracted files, governance drafts). Paths must be relative; absolute / UNC paths,.., NUL bytes and non-container extensions are rejected; the real path of the file read and of the parent written must stay inside (a symlink or junction planted in the sandbox isSECURITY_VIOLATIONon both sides — the check is on the file itself for reads, so a file symlink cannot slip past a parent-only check); files are opened exclusively (OUTPUT_EXISTS— never overwritten); a failed or cancelled write removes the partial file. Unset ⇒ base64 only (the opt-in response cache underZIPNATIVE_MCP_CACHE_DIRis the only other file I/O).Inputs are validated against strict JSON Schemas + Zod at the boundary of every tool — unknown or misspelt keys (top-level or nested) are
VALIDATION_ERROR; base64 is sanity-checked (data:prefix tolerated, base64-twice rejected) before any parser runs;fieldspaths refuse prototype segments and the projection walks own properties only.The error boundary — every
ZipErrorkeeps its frozen code; anything else that escapes the engine or the runtime isENGINE_ERRORwithdata.errorClass: 'internal', its message stripped of absolute paths. No error text carries a host path.Encryption is never performed: encrypted entries are detected,
skippedbyverify_zipwhen their local header agrees, refused by the read paths and bymerge_zipwithZIP_UNSUPPORTED_ENCRYPTION. There is no password input.Every engine diagnostic goes through a sink — nothing is ever printed to stdout on the stdio transport.
HTTP transport (
ZIPNATIVE_MCP_PORT) binds loopback only with a Host / Origin guard, request timeouts and a header-count bound; it has no authentication unlessZIPNATIVE_MCP_HTTP_TOKENis set (then401without a valid bearer token).
The full threat model, the supply chain and the reproducibility promise are in SECURITY.md; the defences with their CWE ids in docs/guides/SECURITY.md.
Network & egress
None, ever. The server has no network code path: no telemetry, no GitHub, no update check, no URL from any tool argument, no remote byte-range source (the engine's openZipRange runs over a sandbox file handle only), and — unlike its PDF sibling — no operator-configurable endpoint of any kind. describe_engine reports network: 'none' and tests/no-network.test.ts holds the source tree to it. Archive bytes only ever flow back in the JSON-RPC response or into the sandbox. The engine itself never opens a socket, never touches the filesystem and never evals.
Cap | Value |
stdio frame / HTTP body | 256 MiB |
decoded | 128 MiB |
buffered | 1 GiB ( |
inline output (archive, inflated bytes, resource read) | 50 MiB |
file output | 4 GiB by default ( |
inline entry ( | 16 MiB |
inline extraction total | 32 MiB |
| 200 default / 2000 max |
| 100000 / 1000 / 10000 |
| 32 / 1 GiB |
| 1000 / 32 / 1024 / 16 / 16 |
| 10000 (clamped to the ceiling) / 16 MiB / 50 |
| 200 rows / 16 384 characters |
| 1000 / 8 / 50 MiB |
response cache ( | 3600 s / 256 MiB |
diagnostics per result | 200 (de-duplicated by code + entry) |
engine defaults ( | 100000 entries · 1 GiB per entry · 1 GiB + 1 MiB compressed per fetched entry · 8 GiB total · ratio 1024 · 4096-byte names · 65535-byte extra fields and comments · 256 MiB central directory |
Every row is a constant in src/caps.ts and is advertised by describe_engine.caps (stdioMaxFrameBytes, maxBase64InputBytes, maxPathInputBytes, maxBase64OutputBytes, maxFileOutputBytes, maxEntryInlineBytes, maxExtractInlineTotalBytes, maxListLimit, defaultListLimit, maxCreateEntries, maxModifyOperations, maxMergeSources, maxMergeInputBytes, maxSanitizeNames, maxDiagnostics, maxDeterminismOffenders, maxFilterNames, maxFilterGlobs, maxGlobLength, maxExtraFields, maxIssueTextChars, maxFieldsPaths, defaultScanMaxEntries, maxResourceLinks, maxRangeLength, maxListedResources, maxResourceWalkDepth, maxResourceReadBytes, cacheTtlSeconds, cacheMaxBytes); the file-output cap in force is describe_engine.ceilings.maxFileOutputBytes.
See SECURITY.md for the responsible disclosure process.
🧪 Conformance (veraZIP)
ZIP has no veraPDF — JHOVE never shipped a ZIP module and no ISO/IEC 21320-1 validator existed — so the zipnative ecosystem ships its own gate, veraZIP: an ISO/IEC 21320-1:2015 (Document Container File) validator vendored from the engine at its 1.1.0 commit (scripts/verazip-core.mjs) that raw-parses the bytes with its own EOCD / central-directory / local-header reader and never imports zipnative — a validator that shared the engine's parser would attest the engine with the engine.
npm run validate:zip builds the server, drives the real tools/call handler (create_zip, modify_zip, canonicalize_zip and merge_zip across every writer path — buffered, stream: true, a Zip64 streamed entry, parallel, compression.deterministic, order: 'insertion', extra fields, comments, Unix modes, append, compact and compact + canonical, canonicalisation of an append-mode archive (idempotence asserted) and with a pinned date, a merge) plus raw-crafted files to write a 43-archive corpus, then validates every file clause by clause (23 check ids). The 39 conformant archives include four hostile-but-conformant ones (zip-slip, a Windows device name, duplicate paths, a symlink entry): the ISO profile constrains the container, not the meaning of names, so they PASS the validator and extract_zip must refuse them — the gate checks both. 4 negative canaries, raw-crafted, must be rejected with a declared clause id. The expected verdict is 39 PASS and 4 XFAIL. Level 0 (the ISO clauses) needs no external tool and always runs; level 1 re-tests every conformant archive with the foreign integrity tools present on the machine (unzip -t, 7z t, python -m zipfile -t, tar -tf, jar tf) and skips the absent ones visibly; VERAZIP_REQUIRED=1 (set in CI) fails closed. The corpus definition is recorded in docs/data/verazip.json and the manifest of each run in test-output/zip/manifest.json.
A PASS proves that every archive this server writes is a well-formed ISO/IEC 21320-1 document container: one self-consistent end-of-central-directory record, a central directory that agrees with every local header, only store / deflate methods, no encryption, no multi-disk fields, valid Zip64 records exactly where a field overflows, a data descriptor whose width matches the local header's Zip64 extra. It is not a certification — validation evidence against a specific validator revision — and conformant does not mean safe: the security guards exist on top of conformance.
The gate is blocking: the ci workflow runs it inside scripts/gate.ts --ci --require-all on Linux (Node 22 and 24), Windows and macOS, verazip.yml repeats it on Linux and Windows on every push and pull request to main (and on manual dispatch), and it runs again before every publish. Every archive a test produces through a tool must also pass assertValidZip (ISO conformance included). See docs/guides/CONFORMANCE.md.
🧪 Local development
git clone https://github.com/Nizoka/zipnative-mcp.git
cd zipnative-mcp
npm ci --ignore-scripts # .npmrc sets ignore-scripts=true: nothing builds on install
npm run gate:fast # before every commit: typecheck:all, lint, test, server-json, verify:docs
npm run gate # the CI profile: + build, dist checks, stdio smoke test, tool shape, samples, coverage, docs, the veraZIP corpus, examples
npx tsx scripts/gate.ts --publish --require-all # release branches: every step, a skipped step failsnpm run gate (scripts/gate.ts) is the single definition of green — 1643 tests across 79 test files, the coverage thresholds of vitest.config.ts (94 / 90 / 95 / 94), and the built server driven over stdio, where stdout must carry JSON-RPC frames only. It prints one line per step and writes the logs to test-output/.gate/<step>.log; --only <step> runs one step, --from <step> resumes, --json gives machine output (call the script directly to pass flags: PowerShell swallows a bare -- after npm run). The gate is hermetic: TZ=UTC, every inherited ZIPNATIVE_MCP_* variable scrubbed, no network. The individual steps are ordinary npm scripts:
Script | What it does |
|
|
|
|
| vitest / with v8 coverage — thresholds in |
|
|
| runs the 38 |
| the 54-sample baseline: the built server under the hermetic env → |
| the veraZIP corpus written to |
| the built |
| every count, version, tool, code, operator variable, link and anchor in the docs held to |
| the governance draft checker |
| regenerates |
| the mechanical version bump (never commits, tags or publishes) |
| opt-in git hooks (pre-commit and pre-push) |
Smoke-test the server over stdio:
node dist/cli.js
# In another terminal, send a JSON-RPC initialize request via stdin (e.g. with the MCP Inspector).
npx @modelcontextprotocol/inspector --cli node dist/cli.js --method tools/listDrive the built server from Claude Code with the repository's .mcp.json (run npm run build first; Claude Code asks for approval on first use and injects ${CLAUDE_PROJECT_DIR}), from Claude Desktop with absolute paths, or from the MCP Inspector — the scenario to replay and the expected results are in docs/guides/LOCAL_TESTING.md § "Drive it from Claude Code / Claude Desktop / MCP Inspector".
Run it over HTTP with a bearer token and a sandbox:
ZIPNATIVE_MCP_PORT=3000 ZIPNATIVE_MCP_HTTP_TOKEN=change-me-to-a-real-secret ZIPNATIVE_MCP_OUTPUT_DIR=/tmp/zips npx zipnative-mcp
# POST http://127.0.0.1:3000/mcp with Authorization: Bearer …Contributors: start at
AGENTS.md(the repository rules shared by every coding agent and contributor) and see CONTRIBUTING.md for the full workflow — the gate, examples-as-tests, the sample baseline,assertValidZipon every archive a test writes, the veraZIP corpus, and the MCP Inspector.
📣 Release process
zipnative-mcp follows the same release formalism as zipnative, pdfnative-mcp and pkinative-mcp:
One release note file per tag in
release-notes/vX.Y.Z.md(no emojis);CHANGELOG.mdmirrors each release bullet list; the GitHub Release body is copied from the notenpx tsx scripts/release-prepare.ts --version X.Y.Zapplies the mechanical part of a version bump (it never commits, tags or publishes); the version moves in lock-step acrosspackage.json,src/version.ts,server.jsonandCITATION.cff(tests/metadata.test.ts) and indocs/assets/ecosystem.json(npm run verify:docs);TOOL_API_VERSIONbumps only when a schema or error code changesA release branch must pass
npx tsx scripts/gate.ts --publish --require-all— every step, the veraZIP corpus included, with no skipnpm publication is handled by GitHub Actions Trusted Publishing (OIDC), without
NPM_TOKEN:publish.ymlrunsverify(the publish gate, onenpm pack, the CycloneDX SBOM, the digests) →publish(the protectednpm-publishenvironment, the handed-on tarball published with--provenance) →attest(the registry bytes compared, a build-provenance attestation for the tarball and the SBOM, both attached to the release) →registry(server.jsonto the MCP registry through amcp-publisherpinned by SHA-256)Every job starts with
step-security/harden-runner(skipped on macOS, which the action does not support), checks out withpersist-credentials: false, installs withnpm ci --ignore-scriptsand runs under least-privilegepermissions; actions are pinned by commit SHAHuman-in-the-loop: coding agents prepare and verify; the maintainer pushes, opens the pull request, tags and publishes (
.github/AGENT_RULES.md)
See release-notes/TEMPLATE.md for the canonical structure and publication checklist, and CONTRIBUTING.md for the release procedure.
📚 Project structure
src/
├── cli.ts # entrypoint: stdio (default, 256 MiB frames) or Streamable HTTP (ZIPNATIVE_MCP_PORT); reads the four ceilings once
├── http.ts # Node http <-> Web Request/Response bridge, 256 MiB body cap, Host/Origin loopback guard, timeouts, security headers
├── auth.ts # opt-in HTTP bearer token (ZIPNATIVE_MCP_HTTP_TOKEN)
├── server.ts # Server factory, TOOLS registry, dispatchOutput, cache gate, cancellation / progress wiring, SERVER_INSTRUCTIONS, 7 prompts, resources
├── call-context.ts # per-call context: the cancellation signal, the progress reporter, CANCELLED
├── caps.ts # every byte / count cap + the operator ceilings (MAX_UNCOMPRESSED_BYTES, MAX_ENTRIES, WORKERS, MAX_FILE_OUTPUT_BYTES)
├── limits.ts # the nine ZipLimits bounds as an input fragment; LIMIT_CEILING_EXCEEDED
├── sandbox.ts # the single filesystem boundary (ZIPNATIVE_MCP_OUTPUT_DIR): container extensions, one-handle reads, realpath checks, wx writes
├── archive-input.ts # zipBase64 XOR zipPath; buffered load, streamed source, access auto / memory / range (openZipRange over the file handle)
├── canonical.ts # the canonical-form options shared by canonicalize_zip and modify_zip canonical
├── base64.ts # base64 boundary decoding with agent-facing diagnostics
├── zip-errors.ts # ZipError -> ToolError (the 39 frozen ZIP_* codes verbatim), guard(), the 12 diagnostic codes
├── wrapper-codes.ts # the 17 wrapper error codes with meaning and remedy
├── diagnostics.ts # engine diagnostic sink (dosTimeMode utc, signal, onProgress; de-duplicated, capped at 200) -> diagnostics[]
├── entry.ts # EntryView: the CLI's --json row shape (flags, unixMode, sanitizedPath, extra fields, rawNameHex, opt-in timestamps / ids)
├── filter.ts # names / prefix / glob entry filter (linear-time Thompson NFA)
├── entry-spec.ts # create / modify payloads: compression, dates (UTC), unixMode / externalAttributes, extra fields
├── engine.ts # initNodeZipCodecs once; runtime capability probe
├── output.ts # base64 emitter / sandboxed exclusive file writer (buffered + streamed, partial file removed)
├── resources.ts # zipnative://output/{+path} over every sandbox file (MIME by extension)
├── projection.ts # verbosity / fields projection for the read tools (prototype segments refused)
├── cache.ts # opt-in SHA-256 response cache (ZIPNATIVE_MCP_CACHE_DIR)
├── governance.ts # AI-governance / HITL contract text + draft validation (linear-time patterns)
├── errors.ts # ToolError, SecurityError, GovernanceError, absolute-path redaction
├── version.ts # ZIPNATIVE_MCP_VERSION, TOOL_API_VERSION
├── index.ts # public library exports
└── tools/
├── _shared.ts # parseInput, ProjectionShape, EngineOptionsShape, sha256Hex
├── inspect-zip.ts
├── list-zip-entries.ts
├── read-zip-entry.ts
├── verify-zip.ts
├── extract-zip.ts
├── scan-zip-forward.ts
├── sanitize-entry-paths.ts
├── create-zip.ts
├── modify-zip.ts
├── canonicalize-zip.ts
├── merge-zip.ts
├── compute-crc32.ts
├── inflate-raw.ts
├── describe-engine.ts
└── draft-governance-issue.ts
scripts/
├── gate.ts # THE quality gate (npm run gate / gate:fast; --publish --require-all on release branches)
├── verify-docs.ts # npm run verify:docs — the docs held to docs/assets/ecosystem.json and the source tree
├── release-prepare.ts # mechanical version bump (never commits, tags or publishes)
├── tool-shape.ts # structural tools/list fingerprint and size budgets (--write refreshes the fixture, --check, --stats)
├── generate-samples.ts # npm run test:generate — the examples through the built server -> test-output/samples/
├── verify-samples.ts # npm run verify:samples — the sample baseline (tests/_fixtures/samples.sha256.json)
├── build-claude-rules.ts # npm run agents:rules — .github/instructions/ -> .claude/rules/
├── install-git-hooks.mjs # npm run hooks:install / hooks:uninstall
├── verazip-core.mjs # vendored engine-independent ISO/IEC 21320-1 validator (never imports zipnative)
├── verazip-core.d.mts # its type declarations
├── generate-zip-corpus.mjs # writes the veraZIP corpus through the real tools + raw-crafted canaries (npm run corpus:zip)
├── validate-zip.mjs # the veraZIP gate: level 0 (ISO clauses) + level 1 (foreign integrity tools)
├── verify-issue.mjs # governance draft checker (npm run verify:issue)
├── helpers/
│ ├── zip-corpus.mjs # the corpus definition (every writer path + the hostile-but-conformant quartet + the canaries)
│ ├── zip-corpus.d.mts # its type declarations
│ ├── raw-zip-builder.mjs # engine-independent raw ZIP writer for the crafted canaries (vendored from zipnative)
│ ├── interop-tools.mjs # unzip / 7z / python zipfile / tar / jar probes for level 1
│ ├── hermetic.ts # the hermetic environment (TZ=UTC, ZIPNATIVE_MCP_* scrubbed)
│ ├── tz.ts # time-zone helpers for the hermetic runs
│ ├── io.ts # file helpers of the scripts
│ └── server.ts # drives the built server from the scripts
└── lib/
├── tool-shape.ts # the fingerprint and the 192 KiB / 8 KiB budgets
├── sample-fingerprint.ts # bytes / semantic fingerprints of the samples
├── example-runner.ts # runs an examples/*.json sequence
├── mcp-surface.ts # tools, codes and variables read from the source tree
├── markdown-anchors.ts # heading anchors for the link checks
├── agent-config.ts # .claude/ settings, guard hook and PR template checks
├── prose-language.ts # the English-only rule
├── secret-needles.ts # the no-secret-material needles
└── json-schema-lite.ts # offline server.json validation against the vendored registry schema
docs/
├── AGENT_CONTRACT.md # the consumer contract for agents that USE the server (catalogue, decision tree, recipes, error reference)
├── AI_GUIDE.md # decision tree + pitfalls for agents
├── API_STABILITY.md # the tool-API stability charter (versioning, bump rules, per-tool matrix)
├── KNOWLEDGE_BASE.md # architecture reference + the 106-export coverage ledger (§8)
├── guides/AI_GOVERNANCE.md # the human-in-the-loop contract, narrated
├── guides/CONFORMANCE.md # the veraZIP gate explained
├── guides/DETERMINISM.md # the determinism levels, canonicalisation and how to prove them
├── guides/INCREMENTAL.md # modify_zip: append vs compact, remanence, the 7-Zip differential
├── guides/LARGE_ARCHIVES.md # byte-range access, Zip64 streaming, the file-output ceiling
├── guides/LOCAL_TESTING.md # the gate, assertValidZip, veraZIP locally, driving the server from Claude Code / Inspector
├── guides/SECURITY.md # threat model and defences with CWE ids
├── data/errors.json # the 39 codes + 12 diagnostics (copy of the engine registry)
├── data/core-exports.json # the 106 exports of zipnative 1.1.0 and their reach
├── data/verazip.json # the corpus counts
└── assets/ecosystem.json # the single source of every count and version quoted in the docs
examples/ # 38 executable tool invocations (npm run examples:check)
tests/ # vitest suites, one per tool / module (<tool>.test.ts, <module>.test.ts) plus:
├── _zip-fixtures.ts # benign archives via the engine + hostile ones via _fixtures/raw-zip-builder.ts (vendored)
├── _zip-assert.ts # assertValidZip / assertIsoConformant / expectCheck
├── _mcp-harness.ts # in-memory legacy-handshake client (captures notifications)
├── _http-fixture.ts # loopback HTTP fixture (2026-07-28 envelope + legacy initialize)
├── _agent-client.ts # the scripted MCP client of the autonomy scenario (stdio / HTTP × legacy / modern)
├── _fuzz.ts # seeded PRNG and mutators (deterministic; not coverage-guided)
├── autonomy.test.ts # S0–S17 agent scenario on stdio legacy / stdio modern / HTTP legacy / HTTP modern + bearer token
├── protocol-revisions.test.ts # the five legacy revisions (2024-10-07 … 2025-11-25) negotiated on both transports
├── stdio-modern.test.ts # 2026-07-28 server/discover + _meta envelope over the built CLI
├── http-modern.test.ts # 2026-07-28 over HTTP: cache hints, Mcp-Method / Mcp-Name, -32602, batches
├── http-hardening.test.ts # body cap, timeouts, header bound, security headers
├── http-shutdown.test.ts # SIGTERM in HTTP mode exits cleanly and releases the port
├── cancellation.test.ts # CANCELLED and notifications/progress over MCP
├── range-access.test.ts # access 'range' / 'auto' through the byte-range reader
├── zip64-streaming.test.ts # create_zip zip64 on stream entries and the file-output ceiling
├── zip64.test.ts # 70 000-entry reads and a 65 536-entry create_zip (auto-promotion)
├── reproducible-build.test.ts # identical bytes from the built server under two time zones
├── zip-fuzz.test.ts # the seeded hostile-input suite over every tool
├── no-network.test.ts # the no-network doctrine over src/
├── schema-lockstep.test.ts # Zod input schemas ↔ advertised JSON Schemas (keys, required, enums, defaults, bounds, patterns)
├── catalogue-parity.test.ts # tools/list vs tests/_fixtures/tool-shape.json
├── catalogue-superset.test.ts # tools/list is a superset of the frozen tests/_fixtures/tool-shape.v1.0.0.json
├── docs-consistency.test.ts # counts / names / codes quoted in the docs, the SFX guard, the export ledger, the veraZIP corpus counts, this tree
├── diagnostics-inventory.test.ts # every reachable diagnostic produced by a tool call
├── tools/ # the repository tooling suites (gate, verify-docs, release-prepare, workflows, guard, …)
└── fixtures/ # interop archives (bsdtar, PowerShell), the frozen example inputs, SHA256SUMS, PROVENANCE.md
AGENTS.md, CLAUDE.md # repository rules for coding agents; .github/instructions/ holds the per-area rules
.claude/ # shared agent settings, the fail-closed guard hook, generated rules, the release-audit skill
.mcp.json # drives the built server from Claude Code (stdio, sandbox under test-output/mcp-sandbox)
.github/workflows/ci.yml # the gate (--ci --require-all) on Linux (Node 22 / 24), Windows and macOS
.github/workflows/verazip.yml # BLOCKING veraZIP job on Linux and Windows (VERAZIP_REQUIRED=1)
.github/workflows/sample-regression.yml # the sample baseline on every pull request that can change an output
.github/workflows/publish.yml # verify -> publish -> attest -> registry (Trusted Publishing, provenance, SBOM, attestation, MCP registry)
.github/workflows/docs.yml # verify:docs on docs changes and weekly; the manifest against npm
.github/workflows/codeql.yml # CodeQL security-extended
.github/workflows/scorecard.yml # OpenSSF Scorecard
.github/workflows/dependency-review.yml # licence and advisory gate on dependency changes
.github/workflows/audit.yml # weekly npm audit (full tree and runtime tree)
.github/workflows/node-current.yml # the gate on the current Node line (advisory)
.github/rulesets/ # branch and tag rulesets to import🗺 Roadmap
v1.1.0 is the first minor release, on zipnative 1.1.0: two new tools (canonicalize_zip, merge_zip), byte-range reads, the Zip64 streaming opt-in, cancellation and progress, UTC timestamps, and a live catalogue checked as a superset of the frozen 1.0.0 one. The full plan — released milestones, what is blocked upstream and the long-term direction — lives in ROADMAP.md.
Not exposed, by design:
Encryption and decryption — none in the engine 1.x (ZipCrypto is broken; AES is an engine 2.0 candidate); only then will a password input exist here.
nameEncoding/nameDecoder— legacy non-UTF-8 names are decoded as cp437, as the specification says; an agent-supplied decoder is not exposed.addRaw, custom codecs, a remote byte-range source —merge_zipis the checked transplant,registerCodecis executable code, and the server has no network path.
Have a feature idea? Open an issue or PR — or let an agent draft one with draft_governance_issue and submit it yourself.
⭐ Star the project
If zipnative-mcp is useful to you, please ⭐ this repository — and consider also starring the underlying engine Nizoka/zipnative. Stars help others discover the project and motivate continued development.
🤝 Contributing
Contributions are very welcome. Please read CONTRIBUTING.md, check the open issues, and follow the code of conduct.
📄 License
MIT © 2026 Nizoka. Third-party material: THIRD-PARTY-NOTICES.md.
zipnative-mcp is built on top of zipnative and the Model Context Protocol TypeScript SDK.
Available Tools
15 toolscanonicalize_zipCanonicalise archive (reproducible, no recompression)AIdempotent
Make ANY archive reproducible: zipnative's canonicalizeZip() rewrites a Gradle, Maven, zip, Explorer or 7-Zip archive into the canonical deterministic form zipnative itself writes — entries sorted by raw name bytes, one timestamp (the DOS epoch, or a pinned date, read as UTC), the UTF-8 flag on every name, no extra field but Zip64, the constant version-made-by, no data descriptor — WITHOUT recompressing a payload (the compressed bytes travel bit for bit; nothing is decoded, so the result is identical on every runtime). Idempotent: canonicalising the result again returns the same bytes (summary.alreadyCanonical). Entry names and payloads travel verbatim (a corrupt or unsafe entry stays so — run verify_zip first when integrity matters). summary.canonicalForm is the engine's own analyzeDeterminism() verdict on the output. keepComments (default false) and keepExternalAttributes (default true) choose what survives. Duplicate-name archives are refused. Verify with inspect_zip { check:['canonical-form'], canonicalDate } and compare summary.sha256 across builds.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | The one timestamp written on every entry (ISO-8601, 1980-01-01 … 2107-12-31, read as UTC; 2-second DOS granularity). Default: the DOS epoch 1980-01-01T00:00:00 — the deterministic default. Pass the same value to inspect_zip `canonicalDate` to verify the result. | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| outputMode | No | 'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens. | base64 |
| outputPath | No | Relative path inside the sandbox (only when outputMode='file'); must end in a container extension (.zip recommended). | |
| keepComments | No | Keep the archive comment and the entry comments (default false: the canonical form carries none). | |
| includeSha256 | No | Add `summary.sha256` of the produced bytes (the reproducibility proof to compare across builds). | |
| keepExternalAttributes | No | Keep each entry's external attributes — Unix modes, DOS attributes (default true); false writes the canonical defaults (0o100644 files, 0o40755 directories). |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| summary | Yes | canonicalize_zip summary. |
| filePath | No | Sandboxed absolute path (file mode). |
| sizeBytes | Yes | |
| diagnostics | Yes | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare idempotentHint and destructiveHint=false, but the description adds genuinely new behavior: payloads travel bit for bit without recompression, the operation is idempotent with summary.alreadyCanonical as the signal, duplicate-name archives are refused, and corrupt/unsafe entries are preserved rather than fixed. That is exactly the kind of consequence-level disclosure annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core promise, then the canonical-form details, then safety caveats and verification. It is dense with em-dash clauses and list-like enumerations, but nearly every clause carries information; slightly tighter phrasing would make it scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter mutation tool with an output schema, it covers determinism guarantees, idempotency, refusal cases and verification workflow. The one missing piece is the archive-source requirement (zipPath xor zipBase64), which the agent must infer from the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description restates the keepComments and keepExternalAttributes defaults, which the schema already documents, and adds only the cross-call hint that the same `date` must be passed to inspect_zip's canonicalDate — useful but marginal, and it never mentions that exactly one of zipPath/zipBase64 must be supplied.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'canonicalizeZip() rewrites a Gradle, Maven, zip, Explorer or 7-Zip archive into the canonical deterministic form' — and enumerates exactly what the canonical form is (sorted entries, one timestamp, UTF-8 flag, no data descriptor). It is clearly distinguishable from inspect_zip, verify_zip and create_zip by being a rewrite-in-place-of-bytes transform.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete routing: 'run verify_zip first when integrity matters' and 'Verify with inspect_zip { check:['canonical-form'], canonicalDate }'. It does not explicitly say when NOT to use it (e.g. versus create_zip or modify_zip for fresh archives), so it stops just short of the top band.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compute_crc32Compute CRC-32ARead-onlyIdempotent
The ZIP checksum (IEEE 802.3 CRC-32, the engine's slice-by-8 implementation) of inline bytes / text or a sandbox file (streamed in 1 MiB chunks). seed continues a running CRC across consecutive chunks; expect compares against a known value (matches). Use it to cross-check an entry's crc32 from list_zip_entries against a file on disk.
| Name | Required | Description | Default |
|---|---|---|---|
| seed | No | Running CRC to continue from (chain calls over consecutive chunks). | |
| text | No | UTF-8 text. Exclusive with dataBase64 / sourcePath. | |
| expect | No | Optional expected CRC (1–8 hex digits, optional 0x prefix, e.g. '4e08bfb4'); the result carries `matches`. | |
| dataBase64 | No | Bytes as base64. Exclusive with text / sourcePath. | |
| sourcePath | No | Relative path of a file inside the sandbox (streamed, ≤ 1 GiB). Exclusive with dataBase64 / text. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hex | Yes | 8 lowercase hex digits. |
| seed | Yes | |
| crc32 | Yes | Unsigned 32-bit CRC. |
| matches | No | Present when `expect` was given. |
| byteLength | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate read-only, idempotent, non-destructive behavior; the description adds valuable behavioral detail such as streaming in 1 MiB chunks, continuation via seed, and comparison via expect that produces matches. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with the main function, but it repeats seed/expect behavior also present in the schema and includes an optional implementation detail (slice-by-8) that adds little operational value. Still well-structured and appropriately brief.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers algorithm, input forms, chunking, stream limits, and cross-check use case, but the schema has no required fields and the description never states that exactly one of text, dataBase64, or sourcePath must be provided. This is a meaningful gap for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema descriptions already cover all five parameters with exclusivity and size constraints, so the bar for additional meaning is high. The prose adds context by explaining seed as chunk continuation and expect as a compare-with-known-value, but most parameter semantics are already present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States the tool computes the IEEE 802.3 CRC-32 checksum of inline bytes/text or a sandbox file, with a concrete use case for cross-checking list_zip_entries data against a disk file. The title reinforces the action, and the algorithm/resource scope are unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit use case ('Use it to cross-check an entry's crc32 from list_zip_entries against a file on disk') and describes how to chain chunks with seed and assert with expect. It does not name alternative tools or explicitly say when not to use it, so it falls just short of full guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_zipCreate archive (reproducible; deterministic:true for cross-runtime identical bytes)AIdempotent
Write a ZIP from inline entries (dataBase64 | text | sourcePath in the sandbox, or directory:true). Reproducible by default: canonical order (raw UTF-8 name bytes), DOS-epoch timestamps, UTF-8 names, constant attributes — defaultDate:'now' or per-entry date opts out (ZIP_TIMESTAMP_NOT_PINNED diagnostic). compression {method store|deflate, level 0–9, deterministic:true = pinned pure-TS encoder ⇒ identical bytes on every runtime} at archive or entry level; order:'insertion' for EPUB / JAR (mimetype first, stored); comment, per-entry comment / unixMode / externalAttributes / raw extraFields. stream:true on a sourcePath feeds the file through addStream (data-descriptor layout, bounded memory). parallel compresses with the worker pool — byte-identical output. Zip64 is emitted exactly when a field overflows. Output base64 (≤ 50 MiB) or a sandbox file (streamed, never overwritten); summary.deterministic is true ONLY with compression.deterministic:true (cross-runtime identity); a default call is reproducible on the same runtime (deflateTier 'node-zlib') and reports false; sha256 (includeSha256, base64 and file modes) is the proof. Every archive it writes is ISO/IEC 21320-1 conformant (CI-validated).
| Name | Required | Description | Default |
|---|---|---|---|
| order | No | 'canonical' (default): entries sorted by raw UTF-8 name bytes (reproducible). 'insertion': call order preserved (EPUB / JAR mimetype-first layouts). | canonical |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| comment | No | Archive comment (UTF-8, ≤ 65535 bytes). | |
| entries | Yes | The entries to write (0–100000; an empty array writes a valid empty archive). Each takes exactly one of dataBase64 / text / sourcePath, or directory: true. | |
| parallel | No | Compress with the worker pool (zipnative/worker). Byte-identical to the sequential writer; only worth it for many large entries. `workers` 0 keeps everything on the calling thread. | |
| chunkSize | No | file mode: output chunk size for the streaming assembler (default 64 KiB). Never changes the bytes. | |
| outputMode | No | 'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens. | base64 |
| outputPath | No | Relative path inside the sandbox (only when outputMode='file'); must end in a container extension (.zip recommended). | |
| compression | No | Compression settings. `method` 'deflate' (default) or 'store'; `level` 0–9 (default 6); `deterministic: true` pins the pure-TS encoder so the bytes are identical on every runtime (slower). | |
| defaultDate | No | Timestamp for entries that set no `date`: an ISO-8601 instant (1980-01-01 … 2107-12-31, 2-second DOS granularity) or 'now' (wall clock — emits the ZIP_TIMESTAMP_NOT_PINNED diagnostic and makes the bytes non-reproducible). Omitted: the DOS epoch 1980-01-01T00:00:00 (reproducible). | |
| includeSha256 | No | Add `summary.sha256` of the produced bytes (determinism proofs). |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| summary | Yes | create_zip summary. |
| filePath | No | Sandboxed absolute path (file mode). |
| sizeBytes | Yes | |
| diagnostics | Yes | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare write/idempotent/non-destructive; the description adds the parts that actually matter: default output is reproducible but not cross-runtime identical, summary.deterministic is true ONLY with compression.deterministic:true, sha256 is the proof, file mode never overwrites, base64 capped at 50 MiB. It also discloses diagnostics and ISO/IEC 21320-1 conformance, which no structured field conveys.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is correctly front-loaded in sentence one, but the rest is a single dense run-on paragraph of ~200 words mixing reproducibility, streaming, Zip64, compression, parallelism, output modes and conformance. Most clauses carry information an agent needs, though several duplicate the schema descriptions, and the absence of any breaks makes scanning harder than it should be for a 12-parameter tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-value explanation is properly omitted; annotations cover the safety profile; schema descriptions are complete. The description closes the remaining gaps an agent would otherwise hit — token cost of base64 vs file mode, size ceilings, and the determinism contract — so nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds synthesis the schema does not state in one place — the interaction between defaultDate:'now' and the ZIP_TIMESTAMP_NOT_PINNED diagnostic, and why a default call reports deterministic:false while 'insertion' ordering exists for EPUB/JAR layouts. Some of it restates schema text nearly verbatim (compression.deterministic), capping it below 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence names a specific verb+resource ('Write a ZIP from inline entries') and enumerates the four mutually exclusive input modes, so an agent immediately knows what the tool produces. It stops short of naming the sibling it is not — modify_zip (edit existing) and canonicalize_zip (rewrite for reproducibility) — so the boundary with those tools must be inferred.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Rich conditional guidance for option choice: 'order:"insertion" for EPUB / JAR (mimetype first, stored)', 'stream:true on a sourcePath', 'parallel ... only worth it for many large entries', and 'opt in only for entries that may cross 4 GiB' (that last caveat lives in the schema). What is missing is explicit tool-routing ('use modify_zip instead when the archive already exists').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
describe_engineDescribe engine & server capabilitiesARead-onlyIdempotent
Offline preflight (no archive needed): server / engine / tool-API versions, the deflate tier in use and the pinned deterministic tier, runtime codecs (node:zlib, CompressionStream, DecompressionStream) and worker threads, the codec registry, the engine's default limits, the four operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES, ZIPNATIVE_MCP_WORKERS, ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES), every server cap, whether the sandbox and the cache are enabled, the 39 frozen ZIP_* error codes and 12 diagnostic codes, and the engine exports this server deliberately does not expose (with why). network is always 'none'. Call it first in a session, or whenever a cap / ceiling refusal needs interpreting.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
Output Schema
| Name | Required | Description |
|---|---|---|
| caps | No | |
| codecs | No | |
| engine | No | |
| server | No | |
| network | No | |
| sandbox | No | |
| ceilings | No | |
| unexposed | No | |
| errorCodes | No | |
| capabilities | No | |
| defaultLimits | No | |
| diagnosticCodes | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, closed-world), and the description adds value beyond them: no archive required, network is always 'none', and the exact scope of what is reported (default limits, four operator ceilings, disabled engine exports with rationale). It stops short of stating any failure modes for the tool itself, so a 4 rather than 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and the call-first instruction are front-loaded, which is good, but the middle is a long comma-chained enumeration including counts ('39 frozen ZIP_* error codes and 12 diagnostic codes') that reads as a dump rather than structured detail. It is informative but would earn more if the inventory were compressed or itemized.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description needn't detail return shape, and annotations cover safety; what remains — when to call, what domain the report covers, and that no archive is needed — is fully stated. Nothing an agent needs to invoke this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and both parameters carry their own descriptions ('dot-paths to keep', 'summary: token-frugal form'), so the schema does the work. The description never references `fields` or `verbosity`, adding no semantics beyond the structured fields; baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (describe) and an explicit resource expansion (server / engine / tool-API versions, codecs, limits, ceilings, error codes) rather than restating the title. The enumerated return contents make it unmistakable against siblings like inspect_zip or verify_zip, which operate on archives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
States the trigger condition directly: 'Call it first in a session, or whenever a cap / ceiling refusal needs interpreting.' It also rules out the usual prerequisite with 'Offline preflight (no archive needed)', so the agent knows no other state must exist first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
draft_governance_issueDraft a governance-compliant GitHub issue (HITL)AIdempotent
Produce a LOCAL GitHub issue draft + compliance report for zipnative / zipnative-mcp and NEVER submit it — the agent drafts, a human reviews and files it under their own identity (Human-In-The-Loop contract, .github/AGENT_RULES.md). No GitHub write path and no network path exist in this server. The draft is checked against the zero-dependency + reproduction policy; a policy violation (a new runtime dependency, duplicateSearchPerformed:false) is GOVERNANCE_VIOLATION; a missing field (reproduction included) is VALIDATION_ERROR. targetRepo 'zipnative' for engine behaviour (parsing, writing, limits, codecs), 'zipnative-mcp' for wrapper behaviour (schemas, sandbox, transport). Returns markdown inline (outputMode:'file' also writes a .md inside the sandbox). Use it only after reproducing a defect — for questions read describe_engine / docs/AGENT_CONTRACT.md instead. Present the draft AND the report to the user, then STOP.
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Concise issue title (imperative, no trailing period). | |
| summary | Yes | One or two paragraphs describing the problem or proposal. | |
| issueType | Yes | Issue category: 'bug' | 'feature' | 'security' | 'docs' | 'performance'. | |
| outputMode | No | 'inline' (default) returns the draft markdown in the response. 'file' additionally writes it to the sandbox (requires ZIPNATIVE_MCP_OUTPUT_DIR); outputPath must be a relative .md path. | inline |
| outputPath | No | Relative .md path inside the sandbox (only when outputMode='file'). | |
| targetRepo | No | Destination repository label for the draft (documentation only — the server never contacts it). 'zipnative-mcp' (wrapper: schemas, sandbox, transport) or 'zipnative' (engine: parser, writer, limits, codecs). | zipnative-mcp |
| reproduction | Yes | The minimal, locally-executed reproduction that justifies this issue. | |
| actualBehavior | No | What actually happened (defaults to the reproduction result when omitted). | |
| affectedPackages | No | Packages impacted by this issue (e.g. ["zipnative-mcp"], ["zipnative"]). | |
| expectedBehavior | Yes | What you expected to happen instead. | |
| duplicateSearchPerformed | Yes | MUST be true: confirms you searched open AND closed issues/PRs for duplicates before drafting. |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | Yes | |
| filePath | No | Sandboxed absolute path (when outputMode='file'). |
| warnings | Yes | |
| issueType | Yes | |
| sizeBytes | Yes | |
| compliance | Yes | |
| outputMode | Yes | |
| targetRepo | Yes | |
| draftMarkdown | Yes | The full draft, ready for a human to review and submit. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond the annotations: the HITL contract, the absence of any GitHub or network write path (consistent with openWorldHint=false), the sandbox write side effect when outputMode='file', and the error taxonomy (GOVERNANCE_VIOLATION vs VALIDATION_ERROR). It does not explain the idempotentHint=true semantics (overwriting an existing outputPath file), which is the one behavioral detail left implicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The critical constraint (LOCAL draft, never submitted) is front-loaded in the first clause, and every subsequent sentence carries governance, routing, or output detail. It is dense and em-dash-heavy in the middle, but for a tool with this much contract surface almost nothing is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter tool with nested reproduction object, enums, and a governance gate, the description covers purpose, the human-in-the-loop contract, repo routing, output modes, error classes, and the stopping condition. An output schema exists, so return-value detail is correctly omitted.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds real meaning by assigning semantics to targetRepo values ('zipnative' for engine behaviour, 'zipnative-mcp' for wrapper behaviour) and by tying duplicateSearchPerformed:false to a specific GOVERNANCE_VIOLATION outcome. It adds little on title/summary/reproduction beyond what the schema says.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and concrete artifact set: 'Produce a LOCAL GitHub issue draft + compliance report', scoped to zipnative / zipnative-mcp. It immediately distinguishes itself from every sibling in the list (zip inspection/manipulation tools) by being the only issue/governance tool, and names describe_engine as the read-path alternative.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit exclusions and alternatives are given: 'Use it only after reproducing a defect — for questions read describe_engine / docs/AGENT_CONTRACT.md instead.' The terminal instruction 'Present the draft AND the report to the user, then STOP' closes the workflow loop so the agent knows not to chain a submit step.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
extract_zipExtract (secure by default)AIdempotent
Extract with every engine guard ON unless you name the relaxation: zip-slip / absolute / drive / UNC / NUL / ADS / Windows-device names → ZIP_PATH_TRAVERSAL (rejectTraversal:false SKIPS them, listed in skipped; an unsafe path is never emitted); symlinks → ZIP_SYMLINK_REJECTED (rejectSymlinks:false extracts the target text as data, never a link); duplicate sanitized paths → ZIP_EXTRACT_DUPLICATE_PATH (onDuplicate 'first' | 'last'); declared sizes / ratios beyond limits → ZIP_LIMIT_EXCEEDED; overlapping entries and central/local divergence are always refused. filter keeps a subset. outputMode 'base64' (default) returns files inline (≤ 16 MiB each, ≤ 32 MiB total; includeData:false = dry run of the plan); 'file' streams into a relative outputDir inside ZIPNATIVE_MCP_OUTPUT_DIR (never overwrites) and returns resource links. Directories are implied by paths (emptyDirectories:true creates the explicit ones). The dry run opens eagerly: an overlapping, offset-into-CD or Zip64-spoofed archive is refused before any plan is returned. Prefer read_zip_entry for a single file.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| filter | No | Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false). | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| outputDir | No | Relative directory inside the sandbox to extract into (file mode; created if absent; no absolute paths, no '..'). | |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| outputMode | No | 'base64' (default): files inline (≤ 16777216 bytes each, ≤ 33554432 bytes total). 'file': stream every file under outputDir inside ZIPNATIVE_MCP_OUTPUT_DIR (never overwrites) and return resource links. | base64 |
| includeData | No | base64 mode: false = dry run — list what WOULD be extracted (paths, sizes) without decompressing or returning bytes. | |
| onDuplicate | No | Two entries mapping to the same sanitized path: 'error' (default, ZIP_EXTRACT_DUPLICATE_PATH), keep the 'first', or keep the 'last'. | error |
| rejectSymlinks | No | true (default): a symlink entry fails the call with ZIP_SYMLINK_REJECTED. false: its target text is extracted as ordinary data (never materialised as a link). | |
| rejectTraversal | No | true (default): an entry whose name escapes the root (zip-slip, absolute, drive/UNC, NUL, ADS, Windows device name) fails the call with ZIP_PATH_TRAVERSAL. false: such entries are SKIPPED and listed in `skipped` — an unsafe path is never emitted either way. | |
| emptyDirectories | No | file mode: also create explicit directory entries (with a safe name) that hold no file. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | No | |
| paths | No | summary only: the extracted paths. |
| dryRun | No | true when includeData was false: nothing was decompressed. |
| entries | No | One row per extracted file, in central-directory order. base64 mode carries dataBase64 (unless dryRun); file mode carries filePath. |
| skipped | No | Entries skipped because their name is unsafe (only with rejectTraversal:false). |
| fileCount | No | |
| outputDir | No | Absolute sandbox directory (file mode). |
| totalBytes | No | Sum of the extracted (or planned) uncompressed sizes. |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| skippedCount | No | summary only: number of skipped (unsafe-path) entries. |
| diagnosticCount | No | summary only: number of distinct diagnostics. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| directoriesCreated | No | |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far exceeds the annotations: it discloses the exact error codes per guard (ZIP_PATH_TRAVERSAL, ZIP_SYMLINK_REJECTED, ZIP_EXTRACT_DUPLICATE_PATH, ZIP_LIMIT_EXCEEDED), that unsafe paths are never emitted even when skipped, that file mode never overwrites, and that the dry run eagerly refuses malformed archives. These are consistent with destructiveHint=false and readOnlyHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but nearly every clause carries operational information; no filler. It is a single long paragraph that leads with guard behavior rather than the core extract action, so it is slightly less front-loaded and scannable than ideal.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 14-parameter, nested-schema tool with an output schema, the description supplies the security model, the mode trade-offs, the dry-run semantics, and error taxonomy. Nothing an agent needs to invoke it safely and correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the 3 baseline applies, but the description adds real meaning: rejectTraversal:false populates `skipped`, rejectSymlinks:false extracts target text as data, onDuplicate 'first'|'last' semantics, and the base64 size ceilings. Only minor gaps remain (e.g., strict, verbosity interplay).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (extract) and resource (zip archive) and immediately distinguishes scope from siblings via 'Prefer read_zip_entry for a single file'. An agent can tell what it does and which sibling to use for the narrow case without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes single-entry reads to read_zip_entry and explains the dry-run case (includeData:false) and the relaxation switches. It does not contrast with list_zip_entries/inspect_zip/verify_zip, so the alternative-selection guidance is clear but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
inflate_rawInflate raw DEFLATE (bounded)AIdempotent
Decompress a raw DEFLATE (RFC 1951) stream — e.g. the payload from read_zip_entry mode:'raw' — with a MANDATORY maxOutput bound through zipnative's resumable inflater (constant memory, exact bytesConsumed, trailing bytes reported as leftover; ZIP_INFLATE_OUTPUT_OVERFLOW past the bound). method 'store' is a bounded pass-through; a numeric method id selects a registered codec (none beyond 0 / 8 in this server → ZIP_UNSUPPORTED_METHOD). Output base64 (≤ 50 MiB) or a sandbox file. Use it only for a raw payload or a bare deflate stream; to read an entry's content prefer read_zip_entry mode:'data'.
| Name | Required | Description | Default |
|---|---|---|---|
| method | No | 'deflate' (default, method 8), 'store' (method 0, bounded pass-through) or a numeric method id for a registered codec. | deflate |
| maxOutput | Yes | Mandatory output bound in bytes: decompression stops with ZIP_INFLATE_OUTPUT_OVERFLOW beyond it (zip-bomb guard). Inline results are further capped at 52428800 bytes, file results at the file cap (ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4294967296). | |
| dataBase64 | No | The compressed stream as base64 (e.g. the payload from read_zip_entry mode:"raw"). Exclusive with sourcePath. | |
| outputMode | No | 'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens. | base64 |
| outputPath | No | Relative path inside the sandbox (only when outputMode='file'); the inflated bytes are raw data, so it must end in .bin, .dat, .raw, .txt, .json, .xml, .md, .csv, .html, .tar or .zip. | |
| sourcePath | No | Relative path of a file inside the sandbox holding the compressed stream (streamed). Exclusive with dataBase64. | |
| allowTrailing | No | Accept bytes after the end of the deflate stream silently (they are always reported as `leftover`). |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| summary | Yes | inflate_raw summary (the produced bytes are application/octet-stream, not an archive). |
| filePath | No | Sandboxed absolute path (file mode). |
| sizeBytes | Yes | |
| diagnostics | Yes | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=false, so the description carries real weight — and it does: mandatory bound enforcement, constant memory, resumable inflater, exact bytesConsumed, trailing bytes surfaced as `leftover`, ZIP_INFLATE_OUTPUT_OVERFLOW on the bound, and 50 MiB inline / file-cap limits. Minor gap: it doesn't say why readOnlyHint is false (a sandbox file can be written), leaving a small inconsistency for the agent to infer from outputMode='file'.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One dense sentence packed with semicolon clauses; front-loaded with the verb, but the parenthetical chains (constant memory, bytesConsumed, leftover, error codes, size caps, method semantics) read as a wall. Information earns its place, but the structure is closer to a spec fragment than to prose an agent can scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter bounded-decompression tool with full schema coverage and an output schema, the description covers what the schema cannot: safety bound rationale (zip-bomb guard), error codes, memory profile, and sibling routing. Nothing material is missing for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3; the description adds value beyond it by explaining that method 'store' is a bounded pass-through (not the schema's 'bounded pass-through' phrase — it adds the bound semantics), linking dataBase64/sourcePath to read_zip_entry mode:'raw', and stating the ≤50 MiB vs. file-cap split. It doesn't restate the exclusivity rules, but those are already in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Decompress a raw DEFLATE (RFC 1951) stream') and disambiguates from siblings by naming the exact companion tool and mode (`read_zip_entry mode:'raw'`). An agent can distinguish this from read_zip_entry without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit positive condition ('only for a raw payload or a bare deflate stream') and an explicit exclusion with the preferred alternative ('to read an entry's content prefer read_zip_entry mode:'data''). The error mapping for unsupported methods (ZIP_UNSUPPORTED_METHOD) also tells the agent when the call isn't the right tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
inspect_zipInspect archive (facts, determinism verdict, CI checks)ARead-onlyIdempotent
Read-only archive report in ONE call: size, entry / file / directory counts, Zip64, comment, compressed vs uncompressed totals, per-method counts, encrypted / symlink / data-descriptor / Zip64 / cp437 / duplicate / unsafe-name counts, date range, a determinism verdict (epoch timestamps + canonical order + UTF-8 flags ⇒ reproducible; canonicalLayout = no data descriptors) and every engine diagnostic (prepended SFX data, name mismatches, …). Opens EAGERLY by default: every entry's real extent is checked up front — overlapping entries, entries reaching into the central directory or past EOF, and Zip64 spoofing — and refused with their ZIP_* code rather than summarised (a method / CRC / size divergence between central and local headers is caught by verify_zip and on read, not here). check:[…] / assert:{…} turn it into a CI gate → checks[] + checksPassed. Token-frugal: verbosity:'summary', fields:[…]. Input: zipBase64 or zipPath (sandbox).
| Name | Required | Description | Default |
|---|---|---|---|
| check | No | CI assertions evaluated over the report: deterministic, epoch-timestamps, canonical-order, utf8-names, no-data-descriptor, canonical-layout, no-zip64, zip64, no-encryption, no-symlinks, safe-names, no-duplicates, no-diagnostics, store-only, deflate-only, and the engine's canonical form (canonical-form = zipnative's analyzeDeterminism verdict: epoch or pinned timestamps, canonical order, UTF-8 flags, Zip64-only extras, version-made-by 0x032D; canonical-extras; canonical-version-made-by). Result: `checks[]` + `checksPassed`. | |
| access | No | 'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap). | auto |
| assert | No | Parametrised assertions (each becomes a `checks[]` row). | |
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| validate | No | 'eager' (default) cross-checks every local header and the overlap table up front — hostile shapes are refused here; 'lazy' defers to first read (cheaper on huge archives). | eager |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| canonicalDate | No | The date the archive was canonicalised with (canonicalize_zip `date`, modify_zip `canonical.date`): the canonical-form verdict then expects that timestamp on every entry instead of the DOS epoch. Read as UTC, like every date this server writes. | |
| determinismReport | No | true adds `determinismReport`: zipnative's analyzeDeterminism() verdict in full — the stricter canonical form canonicalize_zip produces (also Zip64-only extras and the constant version-made-by) and every offending entry with the rule it breaks. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ratio | No | |
| access | No | Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap): `determinism` is then absent and the determinism checks fail with that reason. |
| checks | No | |
| comment | No | Archive comment (UTF-8, lossy). |
| isZip64 | No | A Zip64 end-of-central-directory record is present. |
| methods | No | |
| fileCount | No | |
| commentHex | No | |
| entryCount | No | |
| latestDate | No | |
| determinism | No | `deterministic` = reproducible (epoch timestamps + canonical order + UTF-8 flags); `canonicalLayout` = the buffered layout (no data descriptors) — a streamed archive is reproducible but not canonical. |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| archiveBytes | No | |
| checksPassed | No | Present when check / assert was supplied: true when every assertion holds. |
| commentBytes | No | |
| earliestDate | No | |
| multipleEocd | No | |
| symlinkCount | No | |
| deterministic | No | summary only: the determinism verdict. |
| prependedData | No | SFX-style prefix before the first local header (offsets shifted). |
| utf8NameCount | No | |
| cp437NameCount | No | |
| directoryCount | No | |
| encryptedCount | No | |
| canonicalLayout | No | summary only: buffered (no data descriptor) layout. |
| compressedBytes | No | |
| diagnosticCount | No | summary only: number of distinct diagnostics. |
| unsafeNameCount | No | Names sanitizeEntryPath() refuses (traversal, absolute, drive/UNC, NUL, ADS, device names). |
| zip64EntryCount | No | |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| determinismReport | No | Present with determinismReport: true — zipnative's analyzeDeterminism() report. `deterministic` here is the engine's canonical form (stricter than determinism.deterministic: also no extra field but Zip64 and version-made-by 0x032D). |
| uncompressedBytes | No | |
| duplicateNameCount | No | |
| dataDescriptorCount | No | |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: it discloses the default eager validation behavior, that hostile shapes (overlapping entries, entries past EOF, Zip64 spoofing) are refused with their ZIP_* code rather than summarized, that strict turns the first diagnostic into an error, and the access/limits cost tradeoffs. It also clarifies what is deliberately out of scope (central-vs-local divergence → verify_zip).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, but the body is one long compressed block with arrow glyphs and parenthetical asides that is dense to parse. Nearly every clause carries information, but the packing hurts scannability for an agent deciding whether to call it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter tool with nested objects and an output schema, the description covers behavior, failure modes, CI-gate output shape, and cost knobs. Missing pieces are minor: it does not distinguish this tool from list_zip_entries/read_zip_entry, and does not describe the eager-vs-lazy cost threshold concretely (the schema covers access details).
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds real semantic linkage: check/assert produce checks[] + checksPassed (CI-gate semantics), verbosity:'summary' + fields shrink the payload, zipBase64/zipPath are mutually exclusive with zipPath saving re-sends, and eager-by-default maps to validate. It does not add much beyond the schema for access, limits, or canonicalDate, which the schema already explains.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (read-only archive report) plus exactly what it returns: counts, Zip64/encryption/symlink flags, date range, a determinism verdict, and engine diagnostics. It explicitly differentiates from siblings by noting that method/CRC/size divergence between central and local headers is caught by verify_zip and on read, not here.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear usage contexts: one-call report, CI gate via check/assert, and token-frugal variants via verbosity:'summary' / fields:[…]. It names verify_zip as the alternative for header-divergence validation, but does not systematically cover when to prefer list_zip_entries, read_zip_entry, or scan_zip_forward.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_zip_entriesList entries (paged inventory)ARead-onlyIdempotent
Read-only central-directory inventory without decompressing anything: every entry (duplicates included) in directory order as a full row — name + encoding, sizes, ratio, CRC-32, method, timestamp, decoded flags, Unix mode, symlink, Zip64, data descriptor, host system, offsets, named extra fields, the sanitized path an extraction would use (null = unsafe name), comment. filter selects by exact names / prefix / globs; offset + limit page (default 200, max 2000, hasMore). Prefer inspect_zip for a one-shot summary and read_zip_entry / extract_zip for content. Token-frugal: verbosity:'summary' → names only; fields:[…].
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Return at most this many entries (default 200, max 2000); `hasMore` tells you to page. | |
| access | No | 'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap). | auto |
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| filter | No | Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false). | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| offset | No | Skip this many matching entries (paging). | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| validate | No | 'lazy' (default) parses the central directory only; 'eager' also cross-checks every local header up front. | lazy |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| includeUnixIds | No | Add each entry's `unixIds` (zipnative getUnixIds: the Info-ZIP ux uid / gid, or null). | |
| includeExtraData | No | Include each extra field payload as base64 (forensics). | |
| includeExtendedTimestamps | No | Add each entry's `extendedTimestamps` (zipnative getExtendedTimestamps: UT, then NTFS, then the DOS fields; ISO-8601, UTC). |
Output Schema
| Name | Required | Description |
|---|---|---|
| limit | No | |
| names | No | summary only: the names on this page. |
| access | No | Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap). |
| offset | No | |
| entries | No | |
| hasMore | No | |
| isZip64 | No | |
| entryCount | No | Entries in the central directory (before filtering). |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| matchedCount | No | Entries kept by the filter (before paging). |
| returnedCount | No | |
| diagnosticCount | No | summary only: number of distinct diagnostics. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, but the description adds real behavioral context: no decompression occurs, duplicates are included, order is directory order, paging defaults (200) and cap (2000) with hasMore, and that a null sanitized path means an unsafe name. It does not touch on access-mode costs or limits overrides, but those are documented in the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense sentences, front-loaded with the core operation and ending with token-saving knobs. The long mid-sentence field enumeration is heavy but each item conveys what a row contains; nothing is redundant with the title or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 14 parameters, an output schema and annotations, the description covers what an agent needs to select and call it: what is returned, how to filter and page, and when a sibling is better. Behavior of the advanced knobs is delegated to a fully-described schema, which is acceptable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3; the description still earns above that by compressing the meaning of filter (exact / prefix / globs), offset+limit paging with hasMore, and verbosity/fields into one readable sentence. The remaining 14 parameters (access, limits, validate, strict, include*) are left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+mechanism: 'Read-only central-directory inventory without decompressing anything', and immediately enumerates the returned row. It is clearly differentiated from inspect_zip, read_zip_entry and extract_zip, which it names.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'Prefer inspect_zip for a one-shot summary and read_zip_entry / extract_zip for content', and points to verbosity:'summary' / fields:[…] when tokens are a constraint. The selection condition is stated, not inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
merge_zipMerge archives (no recompression)AIdempotent
Merge 1–32 archives into one WITHOUT recompressing anything: zipnative's addFromReader() transplants each entry's compressed bytes with its method, CRC, sizes, timestamp, attributes, comment and extra fields, in O(bytes copied). Each source is zipBase64 XOR zipPath with an optional filter. Every entry is CRC- and size-checked before it is copied (verify, default true), so a corrupt payload is refused with its ZIP_* code instead of being propagated; an entry whose method has no codec here (ZIP_UNSUPPORTED_METHOD) and encrypted entries are refused. A foreign name the writer will not accept (e.g. a bsdtar './'-prefixed entry) is ZIP_INVALID_ENTRY_NAME — exclude it with that source's filter. Name collisions: onConflict 'error' (default, ZIP_DUPLICATE_ENTRY_NAME), 'first' or 'last' (summary.dropped lists what was not kept). order 'canonical' (default) sorts like create_zip. Run canonicalize_zip on the result for the full canonical form. Output base64 or a sandbox file (streamed).
| Name | Required | Description | Default |
|---|---|---|---|
| order | No | 'canonical' (default): entries sorted by raw name bytes, as create_zip writes them; 'insertion': the sources' order, then each source's central-directory order. | canonical |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| verify | No | Decompress each entry once to check its CRC and size before copying it (secure by default: a corrupt entry is refused with ZIP_CRC_MISMATCH / ZIP_SIZE_MISMATCH instead of being propagated). false trusts the sources — their bytes travel as they are. | |
| comment | No | Archive comment of the result (UTF-8, ≤ 65535 bytes). Default none — the sources' comments are not merged. | |
| sources | Yes | The archives to merge, in order (1–32; total ≤ 1073741824 bytes, inline payloads ≤ 134217728 bytes in all). Each is zipBase64 XOR zipPath, with an optional filter selecting the entries taken from it (directories are kept unless the filter excludes them). | |
| onConflict | No | Two entries with the same name: 'error' (default) — the engine refuses the second with ZIP_DUPLICATE_ENTRY_NAME; 'first' / 'last' — keep the first / last occurrence across the sources in order, the others are reported in summary.dropped. | error |
| outputMode | No | 'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens. | base64 |
| outputPath | No | Relative path inside the sandbox (only when outputMode='file'); must end in a container extension (.zip recommended). | |
| includeSha256 | No | Add `summary.sha256` of the produced bytes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| summary | Yes | merge_zip summary. |
| filePath | No | Sandboxed absolute path (file mode). |
| sizeBytes | Yes | |
| diagnostics | Yes | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover safety/idempotency; the description adds substantial behavior: O(bytes copied) cost model, CRC/size pre-verification with verify default true, the exact ZIP_* refusal codes, refusal of unsupported-method and encrypted entries, and collision resolution semantics. It also discloses the non-overwriting file output and sandbox constraints — far beyond the annotation surface.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the defining property (no recompression), then progressively narrows to errors, collisions, ordering and output. It is dense and clause-heavy in the middle, but nearly every sentence carries decision-relevant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter, nested-object mutation tool with an output schema, the description covers the cost model, failure modes and their codes, ordering, conflict policy and output-size ceilings. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still earns credit by explaining outcomes rather than formats — onConflict 'first'/'last' populating summary.dropped, the canonical-vs-insertion ordering, and the XOR exclusivity of zipBase64/zipPath. It does not document the limits sub-object (deferring to describe_engine), which the schema already covers.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Merge 1–32 archives into one WITHOUT recompressing') and immediately names the mechanism (addFromReader byte transplant). It distinguishes itself from siblings by referencing create_zip (ordering parity) and canonicalize_zip (full canonical form).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operating context (already-compressed sources, filters, onConflict choices) and points to canonicalize_zip as the follow-up when full canonical form is wanted. It stops short of an explicit 'use this instead of create_zip when...' rule, so the create-vs-merge decision is inferred from naming rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
modify_zipModify archive (incremental, no recompression)AIdempotent
Edit an existing archive without recompressing anything: operations add / replace / remove / rename / setComment applied in order. mode 'append' (default) = save(): the original bytes stay verbatim and edits are appended — fast and byte-preserving, BUT removed or replaced payloads REMAIN in the file (data remanence: recoverable by anyone; a ZIP_DEAD_BYTES_RATIO diagnostic fires past 50 % dead bytes, and 7-Zip is known to read the stale payload). mode 'compact' = saveCompact(): a rewrite without the dead bytes, entries SORTED by raw name bytes (an insertion-ordered layout — EPUB mimetype first, JAR manifest first — is not kept; 'append' keeps it), still no recompression, removed data truly gone, SFX prefix dropped; add canonical: {…} for the canonical deterministic form (zipnative 1.1.0, the canonicalize_zip rules applied after the edits). No edits and an unchanged comment return the same bytes (summary.noOp). The input is opened eagerly and, in 'append' mode, every entry is cross-checked against its local header and declared sizes before its bytes are re-emitted (ZIP_ENTRY_OVERLAP / ZIP_CD_LFH_MISMATCH / ZIP_LIMIT_EXCEEDED). Entry names travel verbatim (an unsafe name stays unsafe — extract_zip sanitises on the way out). Archives with duplicate entry names are refused — prefer create_zip for a full rebuild or when the source has duplicate names. Output base64 or a sandbox file.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | 'append' (default): save() — original bytes verbatim + appended entries + new central directory; removed/replaced payloads REMAIN in the file (data remanence). 'compact': saveCompact() — a rewrite without recompression, entries sorted by raw name bytes, removed data truly gone, SFX prefix dropped (add `canonical` for the canonical deterministic form). | append |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| canonical | No | mode 'compact' only (zipnative 1.1.0 saveCompact({ canonical })): after the edits, rewrite every entry into the canonical deterministic form — sorted by raw name bytes, one timestamp, UTF-8 flags, Zip64-only extras, version-made-by 0x032D — still without recompression. `{}` takes the defaults. The same rules as canonicalize_zip. | |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| operations | Yes | Edits applied in order. add: new entry (ZIP_ENTRY_EXISTS if present); replace: existing entry (ZIP_ENTRY_NOT_FOUND otherwise); remove; rename (never overwrites: remove the target first); setComment. | |
| outputMode | No | 'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens. | base64 |
| outputPath | No | Relative path inside the sandbox (only when outputMode='file'); must end in a container extension (.zip recommended). | |
| compression | No | Compression settings. `method` 'deflate' (default) or 'store'; `level` 0–9 (default 6); `deterministic: true` pins the pure-TS encoder so the bytes are identical on every runtime (slower). | |
| defaultDate | No | Timestamp for entries that set no `date`: an ISO-8601 instant (1980-01-01 … 2107-12-31, 2-second DOS granularity) or 'now' (wall clock — emits the ZIP_TIMESTAMP_NOT_PINNED diagnostic and makes the bytes non-reproducible). Omitted: the DOS epoch 1980-01-01T00:00:00 (reproducible). | |
| includeSha256 | No | Add `summary.sha256` of the produced bytes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | Yes | |
| summary | Yes | modify_zip summary. |
| filePath | No | Sandboxed absolute path (file mode). |
| sizeBytes | Yes | |
| diagnostics | Yes | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far exceeds the annotations: it discloses data remanence of removed/replaced payloads in append mode, the ZIP_DEAD_BYTES_RATIO 50% diagnostic, that 7-Zip reads stale payload, that compact re-sorts entries and drops SFX prefixes (breaking EPUB/JAR layout expectations), the no-op byte-identity guarantee, and the eager cross-checks (ZIP_ENTRY_OVERLAP / ZIP_CD_LFH_MISMATCH / ZIP_LIMIT_EXCEEDED). destructiveHint=false is consistent, since edits produce new bytes and never overwrite the input.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the verb and operation list, and nearly every clause carries a hazard or constraint. It is a single dense paragraph with some duplication of the `mode` and `outputMode` schema text ('Output base64 or a sandbox file'), which costs a point.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter mutation tool with nested objects, it covers the decision-critical facts: mode semantics and consequences, ordering, duplicate-name refusal, no-op behaviour, diagnostics, and where output lands. Return values are covered by the output schema and only referenced (summary.noOp, summary.sha256) where relevant.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds meaning the schema does not: that 'append' preserves insertion order while 'compact' sorts by raw name bytes, and that `canonical: {...}` applies canonicalize_zip rules after edits. Entry-name verbatim handling is also clarified beyond the `name` field description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Edit an existing archive without recompressing') and enumerates the exact operation set (add/replace/remove/rename/setComment). It also carves out its lane against siblings by naming create_zip for full rebuilds and deferring sanitisation to extract_zip.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing rules: use create_zip 'for a full rebuild or when the source has duplicate names' (duplicates are refused here), and pass the same date to inspect_zip `canonicalDate` to verify. Mode choice is framed as a trade-off (fast/byte-preserving vs. dead-bytes-free) rather than left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_zip_entryRead one entry (random access)ARead-onlyIdempotent
Read ONE entry without extracting the archive, by name (exact; the last duplicate wins) or index (central-directory position — disambiguates duplicates). mode 'data' (default): decompressed content as base64 or UTF-8 text (encoding:'utf8'; ENTRY_NOT_UTF8 otherwise), CRC-verified; a range {offset,length} reads a byte window through the chunked stream (bounded memory, no CRC). mode 'raw': the compressed payload verbatim (feed it to inflate_raw). mode 'verify': CRC / size / local-header agreement only. Inline cap 16 MiB per entry (ENTRY_TOO_LARGE → use a range or extract_zip file mode). Encrypted entries → ZIP_UNSUPPORTED_ENCRYPTION (no decryption in this engine).
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | 'data' (default): decompressed content; 'raw': the compressed payload bytes verbatim (zero-copy, no decompression); 'verify': CRC / size / local-header check only, no payload. | data |
| name | No | Entry name (exact, case-sensitive; the LAST duplicate wins). Exclusive with index. | |
| index | No | Central-directory position (from list_zip_entries) — disambiguates duplicate names. Exclusive with name. | |
| range | No | Read only a byte window of the decompressed content through the chunked stream (bounded memory; CRC is not verified for a window). length ≤ 16777216. | |
| access | No | 'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap). | auto |
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| encoding | No | Content encoding for mode 'data': base64 (default) or UTF-8 text (invalid UTF-8 → ENTRY_NOT_UTF8). | base64 |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| verifyCrc | No | Verify the decompressed CRC-32 against the central directory (mode data, no range). | |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| includeUnixIds | No | Add the entry's `unixIds` (zipnative getUnixIds: the Info-ZIP ux uid / gid, or null). | |
| includeExtendedTimestamps | No | Add the entry's `extendedTimestamps` (zipnative getExtendedTimestamps: UT, then NTFS, then the DOS fields; ISO-8601, UTC). |
Output Schema
| Name | Required | Description |
|---|---|---|
| mode | No | |
| name | No | summary only: the entry name. |
| text | No | |
| entry | No | |
| index | No | summary only: the entry index. |
| range | No | |
| access | No | Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap). |
| encoding | No | |
| sizeBytes | No | Bytes returned (data / raw) or the declared uncompressed size (verify). |
| dataBase64 | No | |
| textLength | No | |
| crcVerified | No | |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| verification | No | |
| diagnosticCount | No | summary only: number of distinct diagnostics. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark it read-only, idempotent, and non-destructive, but the description adds important behavioral context: the 16 MiB inline cap, CRC behavior by mode, bounded-memory range reads, and the `ZIP_UNSUPPORTED_ENCRYPTION` outcome. These are exactly the operational details an agent needs beyond the safety annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but front-loaded and organized around the tool's three modes. Every sentence contributes operational guidance, error handling, or selection rules, with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the rich schema, annotations, and output schema, the description is complete enough for correct invocation. It covers the main failure modes, mode differences, memory bounds, encryption limitation, and fallback alternatives without needing to restate every parameter.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already explains all 15 parameters in detail. The description reinforces key cross-parameter behavior such as `name` vs `index`, `encoding` error handling, and range-vs-CRC semantics, but it does not meaningfully document many remaining parameters like `access`, `limits`, `fields`, or `strict` beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: read ONE entry without extracting the archive. It immediately disambiguates selection by exact `name` or central-directory `index`, distinguishing this from archive-wide tools like `list_zip_entries` or `extract_zip`.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit mode selection guidance for `data`, `raw`, and `verify`, plus concrete alternatives: use a range or `extract_zip` file mode when the inline cap is exceeded, and feed `raw` output to `inflate_raw`. It also states encrypted entries are unsupported, preempting a common failure case.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sanitize_entry_pathsSanitize entry paths (the traversal gate)ARead-onlyIdempotent
Apply zipnative's single traversal gate sanitizeEntryPath() to a list of names: the safe '/'-separated relative form each maps to (join it under your extraction root — never the raw name), or null with the rule that fired (empty, NUL byte, absolute, drive letter, UNC, '..' traversal, NTFS alternate data stream ':', Windows reserved device name CON/NUL/COM1…). Use it when you extract with another tool or plan an external filesystem sink. No archive needed.
| Name | Required | Description | Default |
|---|---|---|---|
| names | Yes | Entry names to check (as stored in an archive). | |
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| results | No | |
| rejectedCount | No | |
| rejectedNames | No | summary only: the rejected names. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), and the description adds real value beyond them: it enumerates the specific rejection rules (empty, NUL byte, absolute, drive letter, UNC, '..', NTFS ADS ':', Windows reserved device names) and gives an operational instruction to join the result under an extraction root rather than trusting the raw name. It does not discuss batching cost or ordering of results across the 10,000-item cap, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One dense but front-loaded sentence delivers purpose, return shape, and the safety caveat, followed by a short usage sentence. The long parenthetical rule list is high-value content, though the sentence is heavy enough that a reader must parse carefully; no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and rich annotations, the description need not document return structure, and it correctly focuses on when to call it and the critical safety instruction. The only gap is that the `fields`/`verbosity` projection controls, which shape what the caller sees, are left entirely to the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents `names`, `fields`, and `verbosity`. The description reinforces what `names` means (entry names as stored in an archive, each producing either a safe path or null) but says nothing about the field-projection or verbosity parameters. Baseline 3 is warranted when the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource — apply the `sanitizeEntryPath()` traversal gate to a list of entry names — and states exactly what each name maps to (a safe '/'-separated relative form, or null plus the rule that fired). This is clearly distinguishable from siblings like extract_zip or inspect_zip, which operate on archives rather than bare name lists.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states the condition for use: 'Use it when you extract with another tool or plan an external filesystem sink,' which implicitly routes archive-writing work to extract_zip instead. The 'No archive needed' note removes a common false precondition. It stops short of naming a sibling alternative outright, so it lands at 4 rather than 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_zip_forwardScan forward (truncated / unseekable streams — NOT authoritative)ARead-onlyIdempotent
Walk LOCAL headers in stream order with bounded memory (zipnative's central-directory-less reader). The only tool that works on a truncated archive or a stream cut mid-way (it cannot skip an SFX prefix: the stream must start at a local header) — and the only one whose result is NOT authoritative: forward iteration trusts local headers ALONE, so a hostile archive can present different content here than inspect_zip / list_zip_entries report (the upload-scanner differential). Prefer those tools whenever the whole archive is available; use this one for forensics (tolerateTruncation:true returns the partial inventory + error). data 'none' skips payloads, 'verify' decompresses to check CRCs, 'include' returns them (inline caps). stoppedAt tells you whether a central directory was reached. Names are NOT sanitized — sanitizedPath shows what the extraction gate would use.
| Name | Required | Description | Default |
|---|---|---|---|
| data | No | 'none' (default): skip payloads (a data-descriptor entry still costs a full decompress-and-discard); 'verify': decompress every kept entry so CRC and sizes are checked; 'include': also return the content as base64 (inline caps apply). | none |
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| filter | No | Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false). | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| strict | No | Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error. | |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| maxEntries | No | Stop after this many local headers (bounded work on an unbounded stream). Default 10000, clamped to the operator ceiling ZIPNATIVE_MCP_MAX_ENTRIES; an explicit value above the ceiling is refused (LIMIT_CEILING_EXCEEDED). | |
| tolerateTruncation | No | true: a truncated / corrupt stream ends the scan with `stoppedAt:"error"` and the partial inventory (forensics) instead of failing the call. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| names | No | summary only: the kept entry names. |
| trust | No | Constant reminder: nothing here was cross-checked against a central directory. |
| entries | No | |
| keptCount | No | |
| stoppedAt | No | 'central-directory' is the normal end of a complete archive; 'eof' means the stream ended before any central directory (truncated or CD-less); 'max-entries' means more local headers remained. |
| truncated | No | true when the stream ended without reaching a central directory. |
| entryCount | No | Local headers seen (before the filter). |
| inputBytes | No | |
| maxEntries | No | The header budget in force (after clamping to the operator ceiling). |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| bytesDelivered | No | Bytes handed to the forward reader. |
| diagnosticCount | No | summary only: number of distinct diagnostics. |
| diagnosticCounts | No | Occurrences per code, duplicates included. |
| diagnosticsTruncated | No | true when rows beyond 200 were dropped. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/idempotent/non-destructive; the description goes far beyond, disclosing bounded memory, that results are NOT authoritative, the hostile-archive differential versus the authoritative tools, and the `tolerateTruncation`/`stoppedAt` forensics behavior. It also warns names are unsanitized and `sanitizedPath` shows what the extraction gate would use.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the scoping constraint and the warning about non-authoritative results, and every sentence carries information that isn't in structured fields. It is dense—long em-dash-chained sentences—but not padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter tool with nested objects, an output schema, and full annotation coverage, the description supplies the missing decision-level context: why results differ from authoritative tools, truncation handling, and unsanitized names. Return-value detail is correctly delegated to the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics beyond the schema: the three `data` modes are explained in terms of cost and CRC checking, `tolerateTruncation:true` is tied to the partial inventory + `error`, and `stoppedAt` is explained as a central-directory-reached signal. Minor gap: `limits`/`strict`/`fields` are left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('walk LOCAL headers in stream order') plus the distinguishing mechanism ('central-directory-less reader'). It explicitly names siblings inspect_zip and list_zip_entries and explains how the result differs from theirs, so an agent can tell it apart without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Prefer those tools whenever the whole archive is available; use this one for forensics' gives an explicit when-to-use and when-not-to-use rule with named alternatives. It also names the concrete precondition (stream must start at a local header; cannot skip an SFX prefix) that would disqualify it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
verify_zipVerify archive integrity (one call, never throws for archive problems)ARead-onlyIdempotent
Deep verification in one call — zipnative's verifyZip report verbatim: eager structural validation, then every entry's CRC-32 / size / local-header agreement. ok is true when the structure is valid AND every verifiable entry passed AND every design-time skip ('encrypted' | 'stream-only-codec') has a local header that agrees with the central directory. This tool NEVER fails for a problem with the archive: a structural refusal lands in error {code: ZIP_*, message}; each row carries skipped with the reason its payload was not verified — a design-time skip with an agreeing local header counts in skippedCount and does not fail the archive; an entry whose method has no codec here (skipped: 'unsupported-method') or an encrypted entry whose local header contradicts the central directory is FAILED — the engine's verdict verbatim. Only caller mistakes (bad limits, bad input) are isError. entries:'failed' keeps the rows you act on; verbosity:'summary' keeps the verdict + counts. Verify before you trust: run it on any archive from an untrusted source before extract_zip.
| Name | Required | Description | Default |
|---|---|---|---|
| fields | No | Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields. | |
| limits | No | Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input. | |
| entries | No | Which per-entry rows to return: 'all' (default), only 'failed' (ok:false, not skipped), only 'skipped', or 'none' (verdict + counts only). | all |
| zipPath | No | Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls. | |
| verbosity | No | 'summary': scalar verdict fields only (the token-frugal form). | full |
| zipBase64 | No | Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath. | |
| maxEntries | No | Cap on returned rows (the counts always cover every entry); `entriesTruncated` flags a cut. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| error | No | The structural refusal when the archive could not even be opened (frozen ZIP_* code + engine message), else null. |
| entries | No | |
| entryCount | No | |
| diagnostics | No | Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive. |
| failedCount | No | Entries that fail the archive: a CRC / size / local-header mismatch, an unknown compression method, or a design-time skip whose local header contradicts the central directory. |
| skippedCount | No | Entries that could not be verified by design (encrypted, stream-only codec) and whose local header agrees with the central directory. |
| verifiedCount | No | Entries that passed every check. |
| diagnosticCount | No | summary only: number of diagnostics. |
| entriesTruncated | No | |
| diagnosticsTruncated | No | Present (true) when more than 200 distinct diagnostics were raised and the list was cut. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the tool never throws for archive problems, structural refusals land in `error` with ZIP_* codes, skipped entries and FAILED conditions are defined, and only caller mistakes (bad limits/input) produce isError. This discloses failure semantics and verdict logic that readOnly/idempotent annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the first clause states the tool's role, then semantics follow in logical order. It is long and slightly run-on, but most sentences carry distinct information needed for this complex verification tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema already present, the description still supplies the missing behavioral contract: error shape, skipped vs failed rules, and caller-error distinction. With seven parameters including a nested limits object, this is complete enough for an agent to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents fields, limits, entries, and verbosity. The description adds some interpretation ('entries:failed keeps the rows you act on', summary keeps verdict + counts) but largely restates what the schema provides, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Deep verification in one call' of an archive's integrity) and distinguishes itself from siblings by naming extract_zip and positioning verification before extraction. An agent can tell this is the deep, non-destructive integrity check rather than a listing or extraction tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to 'run it on any archive from an untrusted source before extract_zip', giving a clear condition and a named sibling to precede. It does not explicitly cover when-not or contrast with lighter siblings like inspect_zip, so it stops short of the full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
14 tool updates
v1.0.1- Added
canonicalize_zip - Changed
create_zip18 fields changed- changed
Input schema / properties / entries / items / properties / stream / descriptionPrevious value: -"With sourcePath: feed the file through addStream (bounded memory, data-descriptor layout; entries > 4 GiB refused). The archive is then assembled by streaming."New value: +"With sourcePath: feed the file through addStream (bounded memory, data-descriptor layout). The archive is then assembled by streaming. An entry that crosses 4 GiB is refused with ZIP_UNSUPPORTED_ZIP64_STREAMING unless zip64: true." - added
Input schema / properties / entries / items / properties / zip64Added value: +{ + "default": false, + "description": "stream: true only (zipnative 1.1.0 AddEntryOptions.zip64): write this streamed entry in the Zip64 streaming layout (APPNOTE 4.3.9.2: version 45, a Zip64 extra in the local header, a 24-byte data descriptor) so it may exceed 4 GiB. An opted-in entry below 4 GiB still carries the Zip64 records, which some streaming readers (Java's ZipInputStream) cannot read — opt in only for entries that may cross 4 GiB. The archive in a file is bounded by ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES (default 4 GiB); compression.deterministic caps a streamed entry at 2 GiB.", + "type": "boolean" +} - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / outputMode / descriptionPrevious value: -"'base64' (default) returns the bytes inline as an embedded resource (≤ 50 MiB). 'file' writes them inside ZIPNATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 4 GiB, streamed) and returns a resource_link."New value: +"'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Output schema / descriptionPrevious value: -"Result of an archive-producing tool. base64 mode: the archive arrives as an embedded `resource` content block (not duplicated here); file mode: `filePath` + a resource_link."New value: +"Archive result: base64 mode = an embedded `resource` block (not repeated here); file mode = `filePath` + a resource_link." - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped."
- Changed
describe_engine5 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - added
Output schema / properties / ceilings / properties / maxFileOutputBytesAdded value: +{ + "type": "integer" +} - added
Output schema / properties / codecs / items / properties / incrementalAdded value: +{ + "description": "The codec exposes createDecompressor (zipnative 1.1.0): the forward reader and the byte-range reader decode it in O(chunk).", + "type": "boolean" +}
- Changed
draft_governance_issue5 fields changed- added
Input schema / properties / actualBehavior / maxLengthAdded value: +16384 - added
Input schema / properties / expectedBehavior / maxLengthAdded value: +16384 - added
Input schema / properties / reproduction / properties / command / maxLengthAdded value: +16384 - added
Input schema / properties / reproduction / properties / result / maxLengthAdded value: +16384 - added
Input schema / properties / summary / maxLengthAdded value: +16384
- Changed
extract_zip22 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / filter / descriptionPrevious value: -"Select entries by name. `names` (exact, case-sensitive), `prefix` (path prefix) and `glob` (patterns: `*` within a segment, `**` across segments, `?` one character; a pattern without `/` matches at any depth, a trailing `/` matches a whole subtree) are ANDed when several are given; `includeDirectories` (default false) keeps explicit directory entries."New value: +"Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false)." - changed
Input schema / properties / filter / properties / glob / descriptionPrevious value: -"Glob patterns; an entry matching ANY pattern is kept (e.g. ['**/*.json', 'docs/'])."New value: +"Patterns (≤ 1024 characters); ANY match keeps the entry (e.g. ['**/*.json', 'docs/'])." - added
Input schema / properties / filter / properties / glob / items / maxLengthAdded value: +1024 - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped."
- Changed
inflate_raw7 fields changed- changed
Input schema / properties / maxOutput / descriptionPrevious value: -"Mandatory output bound in bytes: decompression stops with ZIP_INFLATE_OUTPUT_OVERFLOW beyond it (zip-bomb guard). Inline results are further capped at 52428800 bytes, file results at 4294967296."New value: +"Mandatory output bound in bytes: decompression stops with ZIP_INFLATE_OUTPUT_OVERFLOW beyond it (zip-bomb guard). Inline results are further capped at 52428800 bytes, file results at the file cap (ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4294967296)." - changed
Input schema / properties / outputMode / descriptionPrevious value: -"'base64' (default) returns the bytes inline as an embedded resource (≤ 50 MiB). 'file' writes them inside ZIPNATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 4 GiB, streamed) and returns a resource_link."New value: +"'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens." - changed
Input schema / properties / outputPath / descriptionPrevious value: -"Relative path inside the sandbox (only when outputMode='file'); must end in a container extension (.zip recommended)."New value: +"Relative path inside the sandbox (only when outputMode='file'); the inflated bytes are raw data, so it must end in .bin, .dat, .raw, .txt, .json, .xml, .md, .csv, .html, .tar or .zip." - changed
Output schema / descriptionPrevious value: -"Result of an archive-producing tool. base64 mode: the archive arrives as an embedded `resource` content block (not duplicated here); file mode: `filePath` + a resource_link."New value: +"Archive result: base64 mode = an embedded `resource` block (not repeated here); file mode = `filePath` + a resource_link." - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped."
- Changed
inspect_zip27 fields changed- added
Input schema / properties / accessAdded value: +{ + "default": "auto", + "description": "'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap).", + "enum": [ + "auto", + "memory", + "range" + ], + "type": "string" +} - added
Input schema / properties / canonicalDateAdded value: +{ + "description": "The date the archive was canonicalised with (canonicalize_zip `date`, modify_zip `canonical.date`): the canonical-form verdict then expects that timestamp on every entry instead of the DOS epoch. Read as UTC, like every date this server writes.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}", + "type": "string" +} - changed
Input schema / properties / check / descriptionPrevious value: -"CI assertions evaluated over the report: deterministic, epoch-timestamps, canonical-order, utf8-names, no-data-descriptor, canonical-layout, no-zip64, zip64, no-encryption, no-symlinks, safe-names, no-duplicates, no-diagnostics, store-only, deflate-only. Result: `checks[]` + `checksPassed`."New value: +"CI assertions evaluated over the report: deterministic, epoch-timestamps, canonical-order, utf8-names, no-data-descriptor, canonical-layout, no-zip64, zip64, no-encryption, no-symlinks, safe-names, no-duplicates, no-diagnostics, store-only, deflate-only, and the engine's canonical form (canonical-form = zipnative's analyzeDeterminism verdict: epoch or pinned timestamps, canonical order, UTF-8 flags, Zip64-only extras, version-made-by 0x032D; canonical-extras; canonical-version-made-by). Result: `checks[]` + `checksPassed`." - changed
Input schema / properties / check / items / enumPrevious value: -[ - "deterministic", - "epoch-timestamps", - "canonical-order", - "utf8-names", - "no-data-descriptor", - "canonical-layout", - "no-zip64", - "zip64", - "no-encryption", - "no-symlinks", - "safe-names", - "no-duplicates", - "no-diagnostics", - "store-only", - "deflate-only" -]New value: +[ + "deterministic", + "epoch-timestamps", + "canonical-order", + "utf8-names", + "no-data-descriptor", + "canonical-layout", + "no-zip64", + "zip64", + "no-encryption", + "no-symlinks", + "safe-names", + "no-duplicates", + "no-diagnostics", + "store-only", + "deflate-only", + "canonical-form", + "canonical-extras", + "canonical-version-made-by" +] - changed
Input schema / properties / check / maxItemsPrevious value: -15New value: +18 - added
Input schema / properties / determinismReportAdded value: +{ + "default": false, + "description": "true adds `determinismReport`: zipnative's analyzeDeterminism() verdict in full — the stricter canonical form canonicalize_zip produces (also Zip64-only extras and the constant version-made-by) and every offending entry with the rule it breaks.", + "type": "boolean" +} - changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - added
Output schema / properties / accessAdded value: +{ + "description": "Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap): `determinism` is then absent and the determinism checks fail with that reason.", + "enum": [ + "range" + ], + "type": "string" +} - added
Output schema / properties / determinismReportAdded value: +{ + "additionalProperties": false, + "description": "Present with determinismReport: true — zipnative's analyzeDeterminism() report. `deterministic` here is the engine's canonical form (stricter than determinism.deterministic: also no extra field but Zip64 and version-made-by 0x032D).", + "properties": { + "canonicalExtras": { + "description": "No extra field other than Zip64 (0x0001) on any entry.", + "type": "boolean" + }, + "canonicalOrder": { + "type": "boolean" + }, + "canonicalVersionMadeBy": { + "description": "Every entry has version-made-by 0x032D.", + "type": "boolean" + }, + "deterministic": { + "type": "boolean" + }, + "epochTimestamps": { + "description": "Every entry carries the DOS epoch, or the canonicalDate given.", + "type": "boolean" + }, + "noDataDescriptors": { + "type": "boolean" + }, + "offenderCount": { + "minimum": 0, + "type": "integer" + }, + "offenders": { + "description": "Every broken rule, entry by entry, in central-directory order (at most 200 rows).", + "items": { + "additionalProperties": false, + "properties": { + "concern": { + "enum": [ + "timestamp", + "order", + "utf8-flag", + "extra-field", + "version-made-by", + "data-descriptor" + ], + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "offendersTruncated": { + "type": "boolean" + }, + "pinnedDate": { + "description": "The canonicalDate the timestamps were compared with (ISO-8601, UTC).", + "type": "string" + }, + "utf8Flags": { + "type": "boolean" + } + }, + "type": "object" +} - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped."
- Changed
list_zip_entries29 fields changed- added
Input schema / properties / accessAdded value: +{ + "default": "auto", + "description": "'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap).", + "enum": [ + "auto", + "memory", + "range" + ], + "type": "string" +} - changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / filter / descriptionPrevious value: -"Select entries by name. `names` (exact, case-sensitive), `prefix` (path prefix) and `glob` (patterns: `*` within a segment, `**` across segments, `?` one character; a pattern without `/` matches at any depth, a trailing `/` matches a whole subtree) are ANDed when several are given; `includeDirectories` (default false) keeps explicit directory entries."New value: +"Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false)." - changed
Input schema / properties / filter / properties / glob / descriptionPrevious value: -"Glob patterns; an entry matching ANY pattern is kept (e.g. ['**/*.json', 'docs/'])."New value: +"Patterns (≤ 1024 characters); ANY match keeps the entry (e.g. ['**/*.json', 'docs/'])." - added
Input schema / properties / filter / properties / glob / items / maxLengthAdded value: +1024 - added
Input schema / properties / includeExtendedTimestampsAdded value: +{ + "default": false, + "description": "Add each entry's `extendedTimestamps` (zipnative getExtendedTimestamps: UT, then NTFS, then the DOS fields; ISO-8601, UTC).", + "type": "boolean" +} - added
Input schema / properties / includeUnixIdsAdded value: +{ + "default": false, + "description": "Add each entry's `unixIds` (zipnative getUnixIds: the Info-ZIP ux uid / gid, or null).", + "type": "boolean" +} - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - added
Output schema / properties / accessAdded value: +{ + "description": "Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap).", + "enum": [ + "range" + ], + "type": "string" +} - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped." - added
Output schema / properties / entries / items / properties / extendedTimestampsAdded value: +{ + "additionalProperties": false, + "description": "With includeExtendedTimestamps: modification / access / creation instants (ISO-8601, UTC) from the UT (0x5455) extra, else the NTFS (0x000a) extra, else the DOS fields (source names which); a missing one is null.", + "properties": { + "atime": { + "type": [ + "string", + "null" + ] + }, + "ctime": { + "type": [ + "string", + "null" + ] + }, + "mtime": { + "type": [ + "string", + "null" + ] + }, + "source": { + "enum": [ + "ut", + "ntfs", + "dos" + ], + "type": "string" + } + }, + "type": "object" +} - changed
Output schema / properties / entries / items / properties / nameEncoding / enumPrevious value: -[ - "utf-8", - "cp437" -]New value: +[ + "utf-8", + "cp437", + "custom" +] - added
Output schema / properties / entries / items / properties / unixIdsAdded value: +{ + "additionalProperties": false, + "description": "With includeUnixIds: the Unix owner uid / gid from the Info-ZIP ux (0x7875) extra; null when the archive carries none.", + "properties": { + "gid": { + "minimum": 0, + "type": "integer" + }, + "uid": { + "minimum": 0, + "type": "integer" + } + }, + "type": [ + "object", + "null" + ] +}
- Added
merge_zip - Changed
modify_zip21 fields changed- added
Input schema / properties / canonicalAdded value: +{ + "additionalProperties": false, + "description": "mode 'compact' only (zipnative 1.1.0 saveCompact({ canonical })): after the edits, rewrite every entry into the canonical deterministic form — sorted by raw name bytes, one timestamp, UTF-8 flags, Zip64-only extras, version-made-by 0x032D — still without recompression. `{}` takes the defaults. The same rules as canonicalize_zip.", + "properties": { + "date": { + "description": "The one timestamp written on every entry (ISO-8601, 1980-01-01 … 2107-12-31, read as UTC; 2-second DOS granularity). Default: the DOS epoch 1980-01-01T00:00:00 — the deterministic default. Pass the same value to inspect_zip `canonicalDate` to verify the result.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}", + "type": "string" + }, + "keepComments": { + "default": false, + "description": "Keep the archive comment and the entry comments (default false: the canonical form carries none).", + "type": "boolean" + }, + "keepExternalAttributes": { + "default": true, + "description": "Keep each entry's external attributes — Unix modes, DOS attributes (default true); false writes the canonical defaults (0o100644 files, 0o40755 directories).", + "type": "boolean" + } + }, + "type": "object" +} - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / mode / descriptionPrevious value: -"'append' (default): save() — original bytes verbatim + appended entries + new central directory; removed/replaced payloads REMAIN in the file (data remanence). 'compact': saveCompact() — canonical rewrite without recompression, removed data truly gone, SFX prefix dropped."New value: +"'append' (default): save() — original bytes verbatim + appended entries + new central directory; removed/replaced payloads REMAIN in the file (data remanence). 'compact': saveCompact() — a rewrite without recompression, entries sorted by raw name bytes, removed data truly gone, SFX prefix dropped (add `canonical` for the canonical deterministic form)." - changed
Input schema / properties / outputMode / descriptionPrevious value: -"'base64' (default) returns the bytes inline as an embedded resource (≤ 50 MiB). 'file' writes them inside ZIPNATIVE_MCP_OUTPUT_DIR (never overwrites; ≤ 4 GiB, streamed) and returns a resource_link."New value: +"'base64' (default): inline embedded resource (≤ 50 MiB; the bytes appear in both its data: uri and its blob). 'file': written into the sandbox (never overwrites; ≤ ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES, default 4 GiB) and returned as a resource_link — far fewer tokens." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - changed
Output schema / descriptionPrevious value: -"Result of an archive-producing tool. base64 mode: the archive arrives as an embedded `resource` content block (not duplicated here); file mode: `filePath` + a resource_link."New value: +"Archive result: base64 mode = an embedded `resource` block (not repeated here); file mode = `filePath` + a resource_link." - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped." - added
Output schema / properties / summary / properties / canonicalAdded value: +{ + "description": "Present (true) when the compact rewrite produced the canonical deterministic form (input canonical).", + "type": "boolean" +}
- Changed
read_zip_entry28 fields changed- added
Input schema / properties / accessAdded value: +{ + "default": "auto", + "description": "'auto' (default): in memory, and a zipPath above 1073741824 bytes by byte ranges. 'range': always by byte ranges over the sandbox file (never a URL; memory ≈ the central directory). 'memory': always buffered (INPUT_TOO_LARGE above the cap).", + "enum": [ + "auto", + "memory", + "range" + ], + "type": "string" +} - changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - added
Input schema / properties / includeExtendedTimestampsAdded value: +{ + "default": false, + "description": "Add the entry's `extendedTimestamps` (zipnative getExtendedTimestamps: UT, then NTFS, then the DOS fields; ISO-8601, UTC).", + "type": "boolean" +} - added
Input schema / properties / includeUnixIdsAdded value: +{ + "default": false, + "description": "Add the entry's `unixIds` (zipnative getUnixIds: the Info-ZIP ux uid / gid, or null).", + "type": "boolean" +} - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - added
Output schema / properties / accessAdded value: +{ + "description": "Present when the archive was read by byte ranges (access 'range', or a zipPath above the in-memory cap).", + "enum": [ + "range" + ], + "type": "string" +} - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped." - added
Output schema / properties / entry / properties / extendedTimestampsAdded value: +{ + "additionalProperties": false, + "description": "With includeExtendedTimestamps: modification / access / creation instants (ISO-8601, UTC) from the UT (0x5455) extra, else the NTFS (0x000a) extra, else the DOS fields (source names which); a missing one is null.", + "properties": { + "atime": { + "type": [ + "string", + "null" + ] + }, + "ctime": { + "type": [ + "string", + "null" + ] + }, + "mtime": { + "type": [ + "string", + "null" + ] + }, + "source": { + "enum": [ + "ut", + "ntfs", + "dos" + ], + "type": "string" + } + }, + "type": "object" +} - changed
Output schema / properties / entry / properties / nameEncoding / enumPrevious value: -[ - "utf-8", - "cp437" -]New value: +[ + "utf-8", + "cp437", + "custom" +] - added
Output schema / properties / entry / properties / unixIdsAdded value: +{ + "additionalProperties": false, + "description": "With includeUnixIds: the Unix owner uid / gid from the Info-ZIP ux (0x7875) extra; null when the archive carries none.", + "properties": { + "gid": { + "minimum": 0, + "type": "integer" + }, + "uid": { + "minimum": 0, + "type": "integer" + } + }, + "type": [ + "object", + "null" + ] +} - added
Output schema / properties / verification / properties / localHeaderMatch / descriptionAdded value: +"The local header agrees with the central directory — cross-checked for every entry, encrypted ones included (zipnative 1.1.0)." - added
Output schema / properties / verification / properties / skippedAdded value: +{ + "description": "Why the payload was not verified; absent when it was.", + "enum": [ + "encrypted", + "stream-only-codec", + "unsupported-method" + ], + "type": "string" +}
- Changed
sanitize_entry_paths3 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)."
- Changed
scan_zip_forward25 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / filter / descriptionPrevious value: -"Select entries by name. `names` (exact, case-sensitive), `prefix` (path prefix) and `glob` (patterns: `*` within a segment, `**` across segments, `?` one character; a pattern without `/` matches at any depth, a trailing `/` matches a whole subtree) are ANDed when several are given; `includeDirectories` (default false) keeps explicit directory entries."New value: +"Select entries: `names` (exact), `prefix`, `glob` (`*` in a segment, `**` across, `?`, `[a-z]`; no `/` = any depth, trailing `/` = subtree), ANDed; `includeDirectories` (default false)." - changed
Input schema / properties / filter / properties / glob / descriptionPrevious value: -"Glob patterns; an entry matching ANY pattern is kept (e.g. ['**/*.json', 'docs/'])."New value: +"Patterns (≤ 1024 characters); ANY match keeps the entry (e.g. ['**/*.json', 'docs/'])." - added
Input schema / properties / filter / properties / glob / items / maxLengthAdded value: +1024 - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / strict / descriptionPrevious value: -"Escalate the first engine diagnostic (prepended data, duplicate name, name mismatch, …) to a ZIP_STRICT_DIAGNOSTIC error instead of reporting it."New value: +"Turn the first engine diagnostic into a ZIP_STRICT_DIAGNOSTIC error." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - changed
Output schema / properties / diagnosticCounts / descriptionPrevious value: -"Number of diagnostics received per code (counts every occurrence, including de-duplicated ones)."New value: +"Occurrences per code, duplicates included." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - changed
Output schema / properties / diagnosticsTruncated / descriptionPrevious value: -"Present (true) when more than 200 distinct diagnostics were dropped from `diagnostics`."New value: +"true when rows beyond 200 were dropped." - added
Output schema / properties / entries / items / properties / extendedTimestampsAdded value: +{ + "additionalProperties": false, + "description": "With includeExtendedTimestamps: modification / access / creation instants (ISO-8601, UTC) from the UT (0x5455) extra, else the NTFS (0x000a) extra, else the DOS fields (source names which); a missing one is null.", + "properties": { + "atime": { + "type": [ + "string", + "null" + ] + }, + "ctime": { + "type": [ + "string", + "null" + ] + }, + "mtime": { + "type": [ + "string", + "null" + ] + }, + "source": { + "enum": [ + "ut", + "ntfs", + "dos" + ], + "type": "string" + } + }, + "type": "object" +} - changed
Output schema / properties / entries / items / properties / nameEncoding / enumPrevious value: -[ - "utf-8", - "cp437" -]New value: +[ + "utf-8", + "cp437", + "custom" +] - added
Output schema / properties / entries / items / properties / unixIdsAdded value: +{ + "additionalProperties": false, + "description": "With includeUnixIds: the Unix owner uid / gid from the Info-ZIP ux (0x7875) extra; null when the archive carries none.", + "properties": { + "gid": { + "minimum": 0, + "type": "integer" + }, + "uid": { + "minimum": 0, + "type": "integer" + } + }, + "type": [ + "object", + "null" + ] +}
- Changed
verify_zip21 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Dot-path projection applied after verbosity (e.g. ['entryCount', 'entries.name']); array segments map over elements; unmatched paths are reported in _meta.unmatchedFields."New value: +"Dot-paths to keep, after verbosity (e.g. ['entryCount', 'entries.name']); unmatched ones are listed in _meta.unmatchedFields." - added
Input schema / properties / fields / items / patternAdded value: +"^(?!(?:[^.]*\\.)*(?:__proto__|constructor|prototype)(?:\\.|$)).+$" - changed
Input schema / properties / limits / descriptionPrevious value: -"Override zipnative's named security bounds for this call (CWE-400 / CWE-409 guards). Every key is optional; absent keys keep the engine defaults (maxEntries 100000, maxEntryUncompressedSize 1 GiB, maxTotalUncompressedSize 8 GiB, maxCompressionRatio 1024, maxNameBytes 4096, maxExtraFieldBytes 65535, maxCommentBytes 65535, maxCentralDirectoryBytes 256 MiB). Values above the operator ceilings (ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES, ZIPNATIVE_MCP_MAX_ENTRIES) are refused with LIMIT_CEILING_EXCEEDED. Raise a bound for trusted input only."New value: +"Per-call override of zipnative's security bounds (CWE-400/409); absent keys keep the engine defaults (describe_engine.defaultLimits). A value above its operator ceiling is LIMIT_CEILING_EXCEEDED. Raise only for trusted input." - changed
Input schema / properties / limits / properties / maxCentralDirectoryBytes / descriptionPrevious value: -"Maximum central-directory size in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCommentBytes / descriptionPrevious value: -"Maximum comment length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxCompressionRatio / descriptionPrevious value: -"Maximum declared uncompressed/compressed ratio for entries >= 1 KiB compressed (zip-bomb guard)."New value: +"Declared ratio (entries ≥ 1 KiB compressed)." - changed
Input schema / properties / limits / properties / maxEntries / descriptionPrevious value: -"Maximum central-directory entry count."New value: +"Entries." - added
Input schema / properties / limits / properties / maxEntryCompressedSizeAdded value: +{ + "description": "Bytes, one compressed payload fetched by the byte-range reader (access range).", + "minimum": 1, + "type": "integer" +} - changed
Input schema / properties / limits / properties / maxEntryUncompressedSize / descriptionPrevious value: -"Maximum decompressed size of one entry, bytes."New value: +"Bytes, one entry decompressed." - changed
Input schema / properties / limits / properties / maxExtraFieldBytes / descriptionPrevious value: -"Maximum extra-field block length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxNameBytes / descriptionPrevious value: -"Maximum entry-name length in bytes."New value: +"Bytes." - changed
Input schema / properties / limits / properties / maxTotalUncompressedSize / descriptionPrevious value: -"Maximum total decompressed size across an extraction, bytes."New value: +"Bytes, all entries decompressed." - changed
Input schema / properties / verbosity / descriptionPrevious value: -"'summary' returns only the scalar verdict fields (drops arrays and payloads) — the token-frugal form."New value: +"'summary': scalar verdict fields only (the token-frugal form)." - changed
Input schema / properties / zipBase64 / descriptionPrevious value: -"The archive bytes as base64 (exactly once; a data: URI prefix is tolerated). Exclusive with zipPath. Decoded size ≤ 128 MiB."New value: +"Archive bytes, base64 (≤ 128 MiB decoded). Exclusive with zipPath." - changed
Input schema / properties / zipPath / descriptionPrevious value: -"Relative path of an archive inside the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (no absolute paths, no '..'; container extensions only: .zip .jar .docx .epub …). Exclusive with zipBase64. Lets a create/modify → verify/extract chain avoid re-sending the bytes."New value: +"Archive path relative to the ZIPNATIVE_MCP_OUTPUT_DIR sandbox (container extensions: .zip .jar .docx .epub …). Exclusive with zipBase64; saves re-sending bytes between calls." - changed
Output schema / properties / diagnostics / descriptionPrevious value: -"Non-fatal conformance concerns the engine raised while parsing or writing (de-duplicated by code + entry, at most 200). Empty for a clean archive. Codes: ZIP_PREPENDED_DATA, ZIP_MULTIPLE_EOCD, ZIP_NAME_MISMATCH, ZIP_UNICODE_PATH_CONFLICT, ZIP_INVALID_UTF8_NAME, ZIP_DUPLICATE_NAME, ZIP_EXTRA_FIELD_MALFORMED, ZIP_ZIP64_EXTRA_IGNORED, ZIP_TIMESTAMP_NOT_PINNED, ZIP_NONDETERMINISTIC_CODEC, ZIP_DEAD_BYTES_RATIO."New value: +"Non-fatal engine concerns (de-duplicated by code + entry, ≤ 200; codes in describe_engine.diagnosticCodes). Empty for a clean archive." - added
Output schema / properties / diagnosticsTruncatedAdded value: +{ + "description": "Present (true) when more than 200 distinct diagnostics were raised and the list was cut.", + "type": "boolean" +} - added
Output schema / properties / entries / items / properties / skipped / descriptionAdded value: +"Why the payload was not verified (zipnative 1.1.0 adds unsupported-method, which still fails the archive)." - changed
Output schema / properties / entries / items / properties / skipped / enumPrevious value: -[ - "encrypted", - "stream-only-codec" -]New value: +[ + "encrypted", + "stream-only-codec", + "unsupported-method" +] - changed
Output schema / properties / failedCount / descriptionPrevious value: -"Entries with a CRC / size / local-header mismatch."New value: +"Entries that fail the archive: a CRC / size / local-header mismatch, an unknown compression method, or a design-time skip whose local header contradicts the central directory." - changed
Output schema / properties / skippedCount / descriptionPrevious value: -"Entries that could not be verified (encrypted, stream-only codec)."New value: +"Entries that could not be verified by design (encrypted, stream-only codec) and whose local header agrees with the central directory."
13 tool updates
v1.0.0- First observed
compute_crc32 - First observed
create_zip - First observed
describe_engine - First observed
draft_governance_issue - First observed
extract_zip - First observed
inflate_raw - First observed
inspect_zip - First observed
list_zip_entries - First observed
modify_zip - First observed
read_zip_entry - First observed
sanitize_entry_paths - First observed
scan_zip_forward - First observed
verify_zip
TDQS
Scored across 15 tools
Most tools target clearly distinct operations (create/modify/canonicalize/merge/extract/inflate/CRC), and descriptions explicitly cross-reference when to prefer one over another. The one soft spot is the read-only inspection cluster — inspect_zip, list_zip_entries, verify_zip and scan_zip_forward all report on an archive, and inspect_zip already performs eager structural validation that overlaps conceptually with verify_zip, so selection requires careful reading rather than being instantly obvious.
Every name is snake_case with a leading verb and an object noun: inspect_zip, list_zip_entries, read_zip_entry, verify_zip, extract_zip, create_zip, modify_zip, canonicalize_zip, merge_zip, compute_crc32, sanitize_entry_paths, describe_engine, draft_governance_issue. The pattern is predictable and the verb choice telegraphs the action.
15 tools sits at the top of the well-scoped band and each one covers a genuinely different lifecycle stage (inspect, list, read, verify, extract, create, modify, canonicalize, merge, plus utilities). Nothing looks redundant or padded; even the odd-seeming draft_governance_issue is a deliberate, documented HITL workflow.
Coverage is unusually thorough: full read/write lifecycle, compaction, determinism rewriting, merging, path sanitization, CRC utilities and engine introspection, with removal reachable via modify_zip. The notable gap is encryption — encrypted entries are explicitly refused rather than decrypted — and there is no tool to list or delete files already written to the sandbox output directory.
Maintenance
Related MCP Connectors
PDF tools for AI agents: validate, extract, merge, split, compress, OCR, sign and redact.
500+ deterministic tools for AI agents: math, conversion, validation, hashing, encoding, date/time.
Deterministic AI agent microtools, no accounts/API keys. fetch_extract: 98% token cut. 47 tools.
Deterministic PDF tools for AI agents: inspect fields, fill forms, merge PDFs, invoices.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA powerful ZIP file processing tool that enables creating, extracting, listing, adding, deleting, viewing, and verifying ZIP files through MCP protocol integration with AI development environments.21 npmMIT
- AlicenseBqualityFmaintenanceProvides tools for AI assistants to compress, decompress, and manage ZIP archives including metadata retrieval. It supports directory compression, password protection, and configurable extraction options.44 npmISC
- AlicenseAqualityAmaintenanceEnterprise-grade PDF engine for AI agents. Pure TypeScript, zero-dependency, sandboxed, and local-first. Exposes 28 tools to generate ISO-compliant PDF/A documents, inspect layouts, extract text, handle digital signatures (PKCS#7), and render high-performance tables (10,000+ rows/s).28392 npm2MIT
- AlicenseNot gradedqualityDmaintenanceProvides deterministic, verifiable text/code/measurement utilities for AI agents, enabling tasks like unit conversion, citation formatting, diffing, proofreading, readability scoring, and syntax checking with re-executable proof.MIT