Back to Browse

Zipnative MCP Server

by Nizoka
Developer ToolsLow Risk9.5MCP RegistryLocal
Free

Server data from the Official MCP Registry

ZIP MCP server: create, inspect, verify, extract, canonicalize, merge, modify. 15 tools

About

ZIP MCP server: create, inspect, verify, extract, canonicalize, merge, modify. 15 tools

Security Report

9.5
Low Risk9.5Low Risk

Valid MCP server (1 strong, 2 medium validity signals). 1 known CVE in dependencies Package registry verified. Imported from the Official MCP Registry.

5 files analyzed · 2 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

file_system

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

Absolute path of the one sandbox directory: zipPath / sourcePath inputs are read from it and outputMode='file' / outputDir outputs are written under it (never overwritten; the real path of every file read and of every parent written must stay inside — a planted symlink or junction is SECURITY_VIOLATION). Unset: every tool works on base64 only (no file I/O beyond the opt-in cache).Optional

Environment variable: ZIPNATIVE_MCP_OUTPUT_DIR

Opt-in content-addressed response cache directory (absolute path) (SHA-256 keyed, 1h TTL, 256 MiB, oldest-written evicted first, plaintext at rest). Never caches zipPath / sourcePath inputs, file output, defaultDate='now', parallel, describe_engine or draft_governance_issue.Optional

Environment variable: ZIPNATIVE_MCP_CACHE_DIR

Serve Streamable HTTP on this loopback port (POST /mcp) instead of stdio (MCP 2026-07-28, stateless, legacy fallback). Unauthenticated unless ZIPNATIVE_MCP_HTTP_TOKEN is set.Optional

Environment variable: ZIPNATIVE_MCP_PORT

Opt-in bearer token for the HTTP transport (>= 16 chars, no whitespace; a weaker value refuses to start). When set, /mcp requires 'Authorization: Bearer <token>' or answers 401. Compared constant-time, never logged.Required

Environment variable: ZIPNATIVE_MCP_HTTP_TOKEN

Operator ceiling for limits.maxEntryUncompressedSize, limits.maxTotalUncompressedSize and limits.maxEntryCompressedSize in bytes (default 8589934592 = 8 GiB, the engine default). Integer >= 1024, read once at startup; an invalid value refuses to start. A per-call value above it is refused with LIMIT_CEILING_EXCEEDED.Optional

Environment variable: ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES

Operator ceiling for limits.maxEntries and scan_zip_forward maxEntries (default 100000, the engine default; the scanner's default budget of 10000 is clamped to it, only an explicit value above it is refused). Integer >= 1, read once at startup; an invalid value refuses to start.Optional

Environment variable: ZIPNATIVE_MCP_MAX_ENTRIES

Operator ceiling for create_zip parallel.workers (default 8; with no explicit workers the engine default max(1, min(cores - 1, 8)) applies, bounded by this ceiling; 0 disables worker threads and keeps compression on the calling thread). Integer >= 0, read once at startup; an invalid value refuses to start.Optional

Environment variable: ZIPNATIVE_MCP_WORKERS

Operator ceiling for every file written in outputMode='file' (create_zip, modify_zip, canonicalize_zip, merge_zip, extract_zip, inflate_raw), enforced while streaming (default 4294967296 = 4 GiB, the 1.0.0 behaviour). Integer >= 1048576, read once at startup; an invalid value refuses to start. Raise it to write Zip64 archives above 4 GiB into the sandbox (create_zip stream entries with zip64: true).Optional

Environment variable: ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-nizoka-zipnative-mcp": {
      "env": {
        "ZIPNATIVE_MCP_PORT": "your-zipnative-mcp-port-here",
        "ZIPNATIVE_MCP_WORKERS": "your-zipnative-mcp-workers-here",
        "ZIPNATIVE_MCP_CACHE_DIR": "your-zipnative-mcp-cache-dir-here",
        "ZIPNATIVE_MCP_HTTP_TOKEN": "your-zipnative-mcp-http-token-here",
        "ZIPNATIVE_MCP_OUTPUT_DIR": "your-zipnative-mcp-output-dir-here",
        "ZIPNATIVE_MCP_MAX_ENTRIES": "your-zipnative-mcp-max-entries-here",
        "ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES": "your-zipnative-mcp-max-file-output-bytes-here",
        "ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES": "your-zipnative-mcp-max-uncompressed-bytes-here"
      },
      "args": [
        "-y",
        "zipnative-mcp",
        "-y",
        "zipnative-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

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.

npm version npm downloads Node version License: MIT CI ISO 21320-1 · veraZIP MCP zipnative TypeScript OpenSSF Scorecard CodeQL


✨ 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.

ToolWhat it doesRead-only
inspect_zipONE-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 analyzeDeterminism(); the default verdict is the 1.0.0 formula: epoch timestamps + canonical order + UTF-8 flags) and every engine diagnostic. Opens eagerly: overlapping entries, entries reaching into the central directory or past EOF and Zip64 spoofing are refused with their ZIP_* code. check: […] + assert: {…} turn it into a CI gate; determinismReport: true adds the engine's stricter canonical-form verdict with every offending entry.✓
list_zip_entriesPaged 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). filter by names / prefix / glob, offset + limit (200 default, 2000 max).✓
read_zip_entryONE entry by name or index without extracting: decompressed content as base64 or UTF-8 text (CRC-verified), a byte range through the chunked stream, the raw compressed payload (mode: 'raw'), or a non-throwing integrity check (mode: 'verify', with the engine's skipped reason).✓
verify_zipDeep verification in one call — the engine's verifyZip report verbatim: structure, every entry's CRC-32 / size / local-header agreement, each unverifiable entry with its skipped reason ('encrypted', 'stream-only-codec', 'unsupported-method'). Never isError for an archive problem: branch on ok and error.code.✓
extract_zipSecure by default: zip-slip / device names, symlinks, duplicate paths, declared-size and ratio bombs, overlapping entries and central/local divergence refused with frozen ZIP_* codes; relaxations are explicit named inputs that skip, never emit. Inline files (includeData: false = dry run — it opens eagerly and refuses an overlapping, offset-into-CD or Zip64-spoofed archive before returning any plan) or streamed into the sandbox with resource links.
scan_zip_forwardWalk 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 (trust: 'local-headers-only'). tolerateTruncation: true returns the partial inventory plus the error. It cannot skip an SFX / prepended prefix and refuses one (ZIP_SIGNATURE_MISMATCH): inspect_zip reports it (prependedData, ZIP_PREPENDED_DATA) and modify_zip mode: 'compact' drops it.✓
sanitize_entry_pathsThe engine's single traversal gate over a list of names: the safe /-relative form each maps to, or null with the rule that fired (traversal, absolute, drive, UNC, NUL, ADS, device name). No archive needed.✓
create_zipWrite 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); compression.deterministic: true for identical bytes on every runtime — summary.deterministic is true ONLY then; store / deflate at archive or entry level; order: 'insertion' for EPUB / JAR; comments, Unix modes, extra fields; streamed sources (zip64: true lets a streamed entry cross 4 GiB); the worker pool (parallel, byte-identical); includeSha256 proofs in base64 and file mode.
modify_zipadd / replace / remove / rename / setComment without recompressing anything. mode: 'append' keeps the original bytes verbatim (removed content stays recoverable — data remanence, said loudly); mode: 'compact' rewrites so it is truly gone, and canonical: {…} makes that rewrite the canonical deterministic form. The input is opened eagerly in both modes.
canonicalize_zipMake ANY archive reproducible: the engine's canonicalizeZip() rewrites a Gradle, Maven, zip, Explorer or 7-Zip archive into the canonical deterministic form (sorted by raw name bytes, one timestamp, UTF-8 flags, Zip64-only extras, the constant version-made-by, no data descriptor) without recompressing a payload. Idempotent; summary.canonicalForm is the engine's own analyzeDeterminism() verdict on the output.
merge_zip1 to 32 source archives into one without recompressing anything (ZipWriter.addFromReader): with verify: true (the default) every entry is decompressed once and CRC- and size-checked before it is copied, and an entry it cannot check (a method other than store / deflate) is refused (ZIP_UNSUPPORTED_METHOD); per-source filter, name collisions onConflict: 'error' (default, ZIP_DUPLICATE_ENTRY_NAME) | 'first' | 'last' with the drops reported. Encrypted entries are refused.
compute_crc32The 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; seed chains chunks, expect compares.✓
inflate_rawRaw DEFLATE (RFC 1951) through the engine's resumable inflater with a mandatory maxOutput bound: exact bytesConsumed, trailing bytes as leftover. Feed it read_zip_entry mode: 'raw'.
describe_engineOffline preflight: versions, deflate tiers, runtime codecs (with incremental) and workers, the engine's default limits, the four operator ceilings, every server cap, sandbox / cache state, the 39 error codes and 12 diagnostic codes, the deliberately unexposed engine exports. network is always 'none'.✓
draft_governance_issueDraft 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_zip emits 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: true pins the pure-TypeScript encoder for the same SHA-256 on every runtime and is the only setting under which summary.deterministic is true; canonicalize_zip brings any foreign archive into the canonical form; parallel is 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' and fields: […]; a produced archive arrives as an embedded resource block, never in structuredContent (the block carries the bytes in blob and repeats them in its data: URI, so outputMode: 'file' — a resource_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 except verify_zip (zipnative's verifyZip is 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 (CANCELLED is 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 embedded resource content block: the bytes are in resource.blob, and resource.uri repeats them as a data: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; structuredContent carries { mode, sizeBytes, summary, diagnostics, diagnosticCounts }, never the bytes.
  • file — the archive is streamed into a sandboxed directory configured via ZIPNATIVE_MCP_OUTPUT_DIR (≤ 4 GiB by default, ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES moves the ceiling; never overwritten) and the result carries a resource_link (the cheapest response; structuredContent.filePath and 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 is SECURITY_VIOLATION on both sides). The same sandbox serves zipPath / sourcePath inputs, so a create_zip → modify_zip → verify_zip → extract_zip chain 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_zip keeps deterministic, canonicalLayout, checksPassed.
  • fields: ['a', 'b.c'] — projects the structured result to named dot-paths (≤ 16); composes after verbosity. Unmatched paths are omitted and reported in _meta.unmatchedFields (with _meta.availableFields); a path holding a __proto__, constructor or prototype segment is VALIDATION_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 ZipLimits keys).
  • 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.

🚀 Installation

# Run directly with npx (recommended for MCP clients)
npx -y zipnative-mcp

# Or install globally
npm install -g zipnative-mcp
zipnative-mcp

Requirements: 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/discover replaces the session handshake; every result carries resultType and the _meta serverInfo envelope. Over HTTP, 2026-07-28 clients send Mcp-Method / Mcp-Name headers and the MCP-Protocol-Version header with each POST /mcp (a 2026-07-28 request without it is refused by the SDK with 400 / -32020).
  • Cache hints — tools/list and prompts/list are public with a 24 h ttlMs, server/discover is public for 1 h, and resources/list / resources/templates/list / resources/read are private with ttlMs: 0 (sandbox files are per-host user data).
  • Protocol errors — tools/call with an unknown tool name is a JSON-RPC error (-32602, [UNKNOWN_TOOL] Unknown tool: …) rather than an isError result, and an unknown or malformed resource URI is -32602 (Invalid params, UNKNOWN_RESOURCE), as the specification requires; isError: true is 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 for compute_crc32 and inflate_raw — nothing is cached and a partial output file is removed. verify_zip is the exception: zipnative's verifyZip is synchronous and takes no signal (an engine limitation), so a verify runs to completion and its result is returned; sanitize_entry_paths, describe_engine and draft_governance_issue do 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.progressToken receives notifications/progress whose progress is 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 initialize is 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.ts proves each legacy revision negotiates exactly that revision and serves the 15 tools; tests/stdio-modern.test.ts and tests/http-modern.test.ts prove the 2026-07-28 path on stdio and HTTP.
  • HTTP — POST /mcp only; GET / DELETE /mcp answer 405 (no SSE resumability; the server is stateless). Bound to 127.0.0.1 with a Host / Origin guard (foreign values → 403; the Origin port must equal the server port); ZIPNATIVE_MCP_HTTP_TOKEN adds an opt-in bearer-token gate (401 + WWW-Authenticate without 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 carries X-Content-Type-Options: nosniff and Cache-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 before initialize is dropped without a reply on the legacy path.
  • Output schemas — every structuredContent validates against the tool's outputSchema (a 2026-07-28 MUST), including verbosity: 'summary' and fields projections: the read tools declare projectable schemas (all properties optional, additionalProperties: false kept). Input schemas carry no $schema keyword and no $ref by policy (some hosts forward inputSchema to function-calling APIs that reject unknown keywords). serverInfo carries websiteUrl; the resource template is zipnative://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.

ClientTransportProtocol negotiated
Claude Desktop, Cursor, Continue, Zed, Windsurf, Clinestdiolegacy initialize (2024-xx / 2025-xx) — unchanged
ChatGPT and other Streamable HTTP hostsHTTP POST /mcplegacy stateless streamable HTTP — unchanged
MCP 2026-07-28 clients (SDK v2 Client, current MCP Inspector)stdio / HTTPserver/discover, cache hints, _meta envelope

Compatibility matrix — every negotiated revision, on both transports, with the test that proves it:

RevisionErastdioHTTP POST /mcpProven by
2024-10-07legacy initialize✓✓tests/protocol-revisions.test.ts
2024-11-05legacy initialize✓✓tests/protocol-revisions.test.ts
2025-03-26legacy initialize (JSON-RPC batches accepted over HTTP, refused on stdio)✓✓tests/protocol-revisions.test.ts, tests/cli-stdio.test.ts, tests/http-modern.test.ts
2025-06-18legacy initialize✓✓tests/protocol-revisions.test.ts, tests/cli-stdio.test.ts, tests/http-modern.test.ts, tests/http-transport.test.ts
2025-11-25legacy initialize✓✓tests/protocol-revisions.test.ts, tests/cli-stdio.test.ts, tests/http-modern.test.ts, tests/autonomy.test.ts
2026-07-28modern (server/discover, _meta envelope, cache hints)✓✓ (+ bearer token)tests/stdio-modern.test.ts, tests/http-modern.test.ts, tests/autonomy.test.ts

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.

VariableDefaultMeaning
ZIPNATIVE_MCP_OUTPUT_DIR(unset)Absolute path of the one sandbox directory: zipPath / sourcePath inputs are read from it, outputMode: 'file' / outputDir outputs are written under it (exclusive create — never overwritten). The real path of every file read and of every parent written must stay inside it: a symlink or junction planted in the sandbox is SECURITY_VIOLATION on zipPath / sourcePath / resources/read as much as on writes. Every read goes through one open handle bound to the file's real path by device and inode, so a file swapped or grown between the check and the read is refused, never served. Unset ⇒ every path input and file output is refused and the tools work on base64 only.
ZIPNATIVE_MCP_CACHE_DIR(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 zipPath / sourcePath inputs (a merge_zip source by zipPath included), file output, defaultDate: 'now', parallel, describe_engine, draft_governance_issue or a cancelled call; a hit carries _meta.cached: true.
ZIPNATIVE_MCP_PORT(unset → stdio)When set to a valid port (1–65535), serves Streamable HTTP on http://127.0.0.1:<port>/mcp instead of stdio; any other value refuses to start. Loopback only, DNS-rebinding protection (foreign Host / Origin → 403), GET / DELETE → 405, bodies up to 256 MiB. No authentication unless ZIPNATIVE_MCP_HTTP_TOKEN is set — other local processes can reach the endpoint.
ZIPNATIVE_MCP_HTTP_TOKEN(unset)(secret) Opt-in bearer token for the HTTP transport (≥ 16 characters, no whitespace — a weaker value aborts startup). When set, every /mcp request must carry Authorization: Bearer <token>; otherwise 401 + WWW-Authenticate: Bearer realm="zipnative-mcp" (with error="invalid_token" only when credentials were sent — RFC 6750 §3.1). Compared constant-time, never logged.
ZIPNATIVE_MCP_MAX_UNCOMPRESSED_BYTES8589934592 (8 GiB)Operator ceiling for limits.maxEntryUncompressedSize, limits.maxTotalUncompressedSize and limits.maxEntryCompressedSize. Integer ≥ 1024. A per-call value above it is LIMIT_CEILING_EXCEEDED; a ceiling below the engine default tightens the default silently.
ZIPNATIVE_MCP_MAX_ENTRIES100000Operator ceiling for limits.maxEntries and scan_zip_forward maxEntries (whose default of 10 000 is clamped to the ceiling; only an explicit value above it is LIMIT_CEILING_EXCEEDED). Integer ≥ 1.
ZIPNATIVE_MCP_WORKERS8Operator ceiling for create_zip parallel.workers; with no explicit workers the engine default max(1, min(cores − 1, 8)) applies, bounded by this ceiling; 0 disables worker threads (compression stays on the calling thread). Integer ≥ 0.
ZIPNATIVE_MCP_MAX_FILE_OUTPUT_BYTES4294967296 (4 GiB)Operator ceiling for every file written in outputMode: 'file' (create_zip, modify_zip, canonicalize_zip, merge_zip, extract_zip, inflate_raw), enforced while streaming. Integer ≥ 1048576. The default is the 1.0.0 cap; raise it to write an archive above 4 GiB into the sandbox (create_zip stream entries with zip64: true).

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.

InputTypeDefaultNotes
zipBase64 / zipPathstring—exactly one
access'auto' | 'memory' | 'range''auto''range' (or a zipPath above 1 GiB under 'auto'): read by byte ranges — determinism is then absent, the determinism checks fail with that reason, and determinismReport / canonicalDate are VALIDATION_ERROR (data.remedy: "access:'memory'")
validate'eager' | 'lazy''eager'lazy defers local-header cross-checks to first read (cheaper on huge archives)
checkstring[] ≤ 18—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, canonical-extras, canonical-version-made-by
determinismReportbooleanfalseadd determinismReport (deterministic, the seven sub-verdicts, pinnedDate, offenderCount, offenders[])
canonicalDatestring—the date the archive was canonicalised with (canonicalize_zip date, modify_zip canonical.date): the canonical-form verdict then expects that timestamp instead of the DOS epoch; read as UTC
assertobject—maxEntries, minEntries, maxUncompressedBytes (integers ≥ 0), maxRatio (number ≥ 1, worst per-entry ratio), has (≤ 100 exact names), method ('store' | 'deflate')
strict, limits, verbosity, fieldsshared 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).

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!