diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69f20a0..6da64f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,6 +95,37 @@ jobs: path: dist/ if-no-files-found: error + - name: Set up Node 22 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + cache: npm + cache-dependency-path: packages/lock-ts/package-lock.json + + - name: Test and pack TypeScript verifier + working-directory: packages/lock-ts + run: | + npm ci + npm test + mkdir -p ../../node-dist + npm pack --pack-destination ../../node-dist + + - name: Checksum Python and TypeScript release artifacts + run: | + python - <<'PY' + from hashlib import sha256 + from pathlib import Path + artifacts = sorted([*Path("dist").glob("*.whl"), *Path("dist").glob("*.tar.gz"), *Path("node-dist").glob("*.tgz")]) + Path("node-dist/SHA256SUMS").write_text("".join(f"{sha256(p.read_bytes()).hexdigest()} {p.name}\n" for p in artifacts)) + PY + + - name: Upload TypeScript and checksum artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: lock-ts + path: node-dist/ + if-no-files-found: error + # -------------------------------------------------------------------------- # Job 2: publish to PyPI via OIDC Trusted Publishing. # @@ -190,10 +221,16 @@ jobs: name: dist path: dist/ - - name: Sign sdist + wheel and attach bundles to the Release + - name: Download TypeScript and checksum artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: lock-ts + path: dist/ + + - name: Sign release artifacts and attach bundles to the Release uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0 with: - inputs: ./dist/*.tar.gz ./dist/*.whl + inputs: ./dist/*.tar.gz ./dist/*.whl ./dist/*.tgz ./dist/SHA256SUMS # Self-check: verify what we just signed against THIS workflow's own # identity so a broken run never publishes a bad bundle. verify: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 758d821..1c9b870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,17 @@ Streamable HTTP; the v0.3 `guard` proxy adds deterministic runtime *result* insp ## [Unreleased] +## [2.0.0] — 2026-10-02 + +### Breaking compatibility + +- CLI 2.0.0 and TypeScript verifier 0.2.0 implement schema level 4. Existing v1–v3 + locks remain readable, but operators must review and re-pin before v4 approval. + Historical signatures must not be treated as approval for the expanded surface. +- The GitHub Release ships the npm-installable TypeScript tarball, signed by the + same release identity as the Python artifacts, with SHA-256 checksums. + This is GitHub artifact distribution, not an npm registry publication. + ### Fixed — release validation - Refresh the dev/CI and Action dependency locks from PyJWT 2.13.0 to 2.15.1 diff --git a/DOCUMENTATION_INDEX.md b/DOCUMENTATION_INDEX.md index ece86a8..c662aa6 100644 --- a/DOCUMENTATION_INDEX.md +++ b/DOCUMENTATION_INDEX.md @@ -202,8 +202,8 @@ scope-honesty box and makes no compliance/regulatory claim. | Doc | Purpose | |-----|---------| | [`RELEASING.md`](RELEASING.md) | Operator runbook: one-time PyPI Trusted-Publisher (OIDC) setup, cut-a-release checklist, post-release verification, rollback/yank. PyPI dist name is `mcp-warden-cli`; CLI/repo stay `mcp-warden`. | -| [`CHANGELOG.md`](CHANGELOG.md) | Keep-a-Changelog history (0.3.0 → 1.0.0 → 1.0.1) with explicit in/out-of-scope. | -| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release workflow: build sdist+wheel → publish to PyPI via OIDC Trusted Publishing (no stored token, **gated on repo var `PYPI_TRUSTED_PUBLISHER=true`** + `skip-existing`, #64) → Sigstore-keyless sign the artifacts and attach bundles to the Release. Live: `mcp-warden-cli` Trusted Publisher configured + the gate variable set. | +| [`CHANGELOG.md`](CHANGELOG.md) | Keep-a-Changelog history through CLI 2.0.0 / schema level 4 with explicit in/out-of-scope. | +| [`.github/workflows/release.yml`](.github/workflows/release.yml) | Publish-on-Release workflow: build Python distributions + tested TypeScript 0.2.0 tarball → publish Python to PyPI via OIDC Trusted Publishing (no stored token, **gated on repo var `PYPI_TRUSTED_PUBLISHER=true`** + `skip-existing`, #64) → Sigstore-keyless sign Python, TypeScript, and checksums and attach bundles to the Release. Live: `mcp-warden-cli` Trusted Publisher configured + the gate variable set. | | [`requirements-dev.lock`](requirements-dev.lock) · [`.github/workflows/deps-locked.yml`](.github/workflows/deps-locked.yml) | **(#59)** Hash-pinned dev/CI dependency lock + the "Hash-locked dev/CI install" check (verifies `--require-hashes` install + that the lock stays in sync with `pyproject.toml` without floating to latest, #65). Dependency-update policy lives in [`SECURITY.md`](SECURITY.md). | --- diff --git a/README.md b/README.md index 60672c3..b44583b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Last Updated: 2026-10-02 tool/resource/prompt surface into a signed `warden.lock`, then fails CI when that surface drifts.** `pin` and `check` support stdio and Streamable HTTP; `guard` is stdio-only. -**Schema level 4** also locks complete tool annotations and output schemas. Changing +**CLI 2.0.0 / schema level 4** locks complete tool annotations and output schemas. Changing destructiveHint, removing a result schema, or widening its types now triggers drift; the existing runtime tools/list gate checks these fields against v4 locks too. Older locks stay readable, but require review and re-pinning for this coverage. diff --git a/RELEASING.md b/RELEASING.md index 028f317..f338487 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -141,6 +141,17 @@ Do this on a clean checkout of `main` with all v1 PRs merged. --- +## TypeScript release artifact + +The build job also runs the shared TypeScript conformance suite and `npm pack`. +The npm-installable `.tgz` and `SHA256SUMS` are downloaded only by the signing job; +PyPI receives only the Python distribution artifact. The existing release identity +signs and attaches the TypeScript tarball and checksums alongside Python artifacts. +No npm registry token or publisher is configured by this workflow. + +Verify the tarball bundle against the same release workflow identity, then install +the exact GitHub asset into a fresh consumer and import `@mcp-warden/lock`. + ## 2. Post-release verification 1. **Install from PyPI** (give the CDN a minute): diff --git a/SECURITY.md b/SECURITY.md index 34dcb9c..d56067b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -44,19 +44,16 @@ sharpen those boundaries are still welcome. ## Supported versions -Security fixes are issued for the latest minor series. Older series are not +Security fixes are issued for the latest released series. Older series are not patched — upgrade to a supported release. -| Version | Supported | -| ------- | ------------------ | -| 0.3.x | :white_check_mark: | -| 0.2.x | :x: | -| 0.1.x | :x: | -| < 0.1 | :x: | +| Version | Supported | +| ------- | --------- | +| 2.0.x | :white_check_mark: | +| 1.x and earlier | :x: | -> Pre-1.0 note: the public surface is still evolving. The supported series will -> advance with each minor release; only the most recent `0.x` minor receives -> security patches. +CLI 2.0.0 implements schema level 4. Historical v1–v3 **lock formats** remain +readable, but review and re-pin are required to approve the newly covered fields. ## Response window diff --git a/SYSTEM_CONTEXT_DIAGRAM.md b/SYSTEM_CONTEXT_DIAGRAM.md index c246179..4e35f41 100644 --- a/SYSTEM_CONTEXT_DIAGRAM.md +++ b/SYSTEM_CONTEXT_DIAGRAM.md @@ -2,7 +2,7 @@ Last Updated: 2026-10-02 -**Schema level 4** extends capture/lock/check to complete tool annotations and output +**CLI 2.0.0 / schema level 4** extends capture/lock/check to complete tool annotations and output schemas, with structural output drift and Python/TypeScript parity. The existing tools/list gate compares those commitments for v4 locks; legacy locks retain narrower runtime coverage and require re-pin for the new fields. Annotations grant no authority. diff --git a/docs-site/lock-format.md b/docs-site/lock-format.md index d2fb5c9..4ad2302 100644 --- a/docs-site/lock-format.md +++ b/docs-site/lock-format.md @@ -8,7 +8,7 @@ become a shared standard rather than one project's internal contract. ## What the format is The MCP Lock Format v1 records the **declared surface** of an MCP server — the -names, descriptions, input schemas, and derived capability flags it advertises +names, descriptions, input schemas, tool annotations, output schemas, and derived capability flags it advertises over the `initialize` / `tools/list` / `resources/list` / `prompts/list` handshake — and produces a **reproducible digest** over that surface so that any later change ("drift") is detectable deterministically. @@ -26,6 +26,23 @@ It is defined in normative, implementation-independent terms: Two tools that implement the format correctly will compute the **same digest over the same declared surface** — that byte-reproducibility is the conformance bar. +## Schema level 4 — CLI 2.0.0 + +The current schema hashes complete tool annotations and output schemas. Missing/null +metadata differs from an empty object. Hint changes and output-schema drift invalidate +an approved baseline; annotations never grant authority. The Python and TypeScript +implementations share the same conformance corpus. + +Existing v1–v3 locks remain readable, but upgrading requires reviewing the expanded +surface and re-pinning. Historical signatures do not approve newly covered fields. +Keep the previous lock as review evidence, then run your existing `pin --approve` +workflow (and sign the new baseline when signatures are required). + +The [2.0.0 release](https://github.com/DataScience-EngineeringExperts/mcp-warden/releases/tag/v2.0.0) +also distributes the npm-installable TypeScript verifier 0.2.0 with checksums and +Sigstore bundles. The broader Warden checkpoints remain a +[design proposal](https://github.com/DataScience-EngineeringExperts/mcp-warden/blob/main/docs/plans/2026-10-02-tool-integrity-upgrade.md). + ## Read the full specification The complete, normative specification is the source of truth and lives in the diff --git a/docs/SPEC.md b/docs/SPEC.md index baec485..1175b54 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -47,7 +47,7 @@ A matching digest means only that the declared surface is byte-identical to the ```jsonc { - "schema_version": 3, // integer; see §14 — the current format level + "schema_version": 4, // integer; see §14 — the current format level "warden_version": "x.y.z", // semver of the tool that wrote the file "server": { ... }, // §6 server identity "tools": [ { ... } ], // §7 per-entry, sorted by name @@ -428,7 +428,7 @@ relaxations of optional result-inspection checks for that one tool: An implementation is **conformant** with MCP Lock Format v1 if and only if: 1. It writes and reads a `warden.lock` matching the top-level schema (§3), with - `schema_version` naming the format level it implements (currently `3`, §14). + `schema_version` naming the format level it implements (currently `4`, §14). 2. It canonicalizes per RFC 8785 JCS (§4) and hashes per §5, emitting every digest as `sha256:` followed by 64 lowercase hex characters. 3. Given the **same declared surface**, it produces a **byte-identical** `overall_digest` @@ -468,10 +468,11 @@ in the reference — never a reason to edit the vector. --- -## 13. Minimal worked example +## 13. Historical v3 worked example -A minimal lock for a single-tool server (digests abbreviated for readability; a real lock -carries full 64-hex digests): +This historical v3 example illustrates the pre-migration formula. Current v4 entries +also require the §7.1 annotation/output commitments; see the conformance vectors +for complete current documents. Digests below are abbreviated for readability. ```json { diff --git a/packages/lock-ts/README.md b/packages/lock-ts/README.md index 2031dbf..8272124 100644 --- a/packages/lock-ts/README.md +++ b/packages/lock-ts/README.md @@ -38,6 +38,18 @@ if (!result.ok) { console.log("surface matches baseline", digest(surface)); ``` +## Install the released artifact + +Version 0.2.0 is distributed with the CLI 2.0.0 GitHub Release: + +```bash +npm install https://github.com/DataScience-EngineeringExperts/mcp-warden/releases/download/v2.0.0/mcp-warden-lock-0.2.0.tgz +``` + +The release includes SHA-256 checksums and a Sigstore bundle for this tarball. +It is an npm-installable artifact, not an npm registry publication. Review the +[release migration notes](https://github.com/DataScience-EngineeringExperts/mcp-warden/releases/tag/v2.0.0) before re-pinning old locks. + ## API | export | purpose | diff --git a/packages/lock-ts/package-lock.json b/packages/lock-ts/package-lock.json index 7a250d0..be3235a 100644 --- a/packages/lock-ts/package-lock.json +++ b/packages/lock-ts/package-lock.json @@ -1,12 +1,12 @@ { "name": "@mcp-warden/lock", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mcp-warden/lock", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "devDependencies": { "@types/node": "22.10.2", diff --git a/packages/lock-ts/package.json b/packages/lock-ts/package.json index dc1d08e..9b7e49c 100644 --- a/packages/lock-ts/package.json +++ b/packages/lock-ts/package.json @@ -1,6 +1,6 @@ { "name": "@mcp-warden/lock", - "version": "0.1.0", + "version": "0.2.0", "description": "Zero-dependency verifier for MCP Lock Format v1 (warden.lock): RFC 8785 canonicalization, SHA-256 digests, and drift classification.", "license": "MIT", "type": "module", diff --git a/pyproject.toml b/pyproject.toml index a27b8ee..a057d78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ # `mcp-warden` — install `mcp-warden-cli`, then run `mcp-warden ...`. See README # impostor warning + CHANGELOG. name = "mcp-warden-cli" -version = "1.2.0" +version = "2.0.0" description = "CI-first MCP supply-chain integrity gate: pin and verify the declared tool/resource/prompt surface of an MCP server, plus runtime tool-result inspection (guard/inspect)." readme = "README.md" requires-python = ">=3.11" diff --git a/src/mcp_warden/__init__.py b/src/mcp_warden/__init__.py index f3d3df0..31a6aba 100644 --- a/src/mcp_warden/__init__.py +++ b/src/mcp_warden/__init__.py @@ -7,7 +7,7 @@ on runtime tool behavior or tool results. See ``docs/THREAT_MODEL.md``. """ -__version__ = "1.2.0" +__version__ = "2.0.0" #: Lock schema version. Bumped 3 → 4 for annotation/outputSchema commitments #: (DSE-1539). Missing/null metadata hashes JSON null, not an empty object. #: Earlier 2 → 3 migration was #29 (in-document ``$ref`` resolution in