Skip to content

ci: pin our own MCP tool surface with truecopy#24

Merged
askalf merged 1 commit into
masterfrom
feat/pin-own-tool-surface
Jul 16, 2026
Merged

ci: pin our own MCP tool surface with truecopy#24
askalf merged 1 commit into
masterfrom
feat/pin-own-tool-surface

Conversation

@askalf

@askalf askalf commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Why

The security stack should be gated by its own supply-chain tool. This pins the composed server's actual MCP tool surface — the tools/list wire response — with truecopy, and verifies it in CI with the published truecopy-action.

What

  • support/dump-tools.mjs — connects a real MCP client over an in-memory transport and writes the server's live tools/list output to mcp-manifest.json (sorted, stable). --check mode exits 1 if the committed manifest no longer matches the code.
  • mcp-manifest.json — the three trilogy tools (canon_scan, keeper_lease, warden_check) with their titles, descriptions, generated JSON schemas, and annotations.
  • truecopy.lock — the manifest pinned (truecopy add), per-tool hashes + redstamp detection provenance.
  • .github/workflows/truecopy-gate.yml — CI chain: npm cidump-tools --check (manifest == code) → askalf/truecopy-action@v1.0.1 (SHA-pinned) verify (manifest == pinned bytes).

Any change to a tool name, description, or schema — including a dep bump that changes the SDK's generated schemas — fails the gate until the manifest is regenerated and consciously re-pinned. That's the poisoned-tool-definition failure mode this stack exists to catch, enforced on itself.

Worth noting: the scan verdict on our own manifest is clean with zero findings — tool descriptions that talk about exfiltration, secrets, and poisoning are exactly the security-tooling false-positive class from our marketplace studies, and the severity tiering handles them without --force.

Verified locally: dump → --checkscan (clean) → addverify (all 1 pinned skills verified) all round-trip.

🤖 Generated with Claude Code

support/dump-tools.mjs writes the server's real tools/list response to
mcp-manifest.json (in-memory MCP client, sorted, stable) with a --check
mode; the manifest is pinned in truecopy.lock and gated in CI by
askalf/truecopy-action@v1.0.1 (SHA-pinned). Chain: code -> manifest
(--check) -> lock (verify). A changed tool name, description, or
generated schema fails the gate until consciously re-pinned.
@askalf
askalf enabled auto-merge (squash) July 16, 2026 18:30

@sprayberry-reviewer sprayberry-reviewer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review from the Sprayberry Labs fleet code reviewer (sprayberry-reviewer bot). This is a verdict-bearing automated review.

Verdict: no blocking issues — approving. 4 new files, 200 additions, 0 deletions. The supply-chain gate is correctly wired and CI is all green.


What I checked

support/dump-tools.mjs

The dump script correctly:

  • Builds a real in-memory MCP client over InMemoryTransport.createLinkedPair(), which produces the exact tools/list wire response a real MCP client would see — not a synthetic reconstruction.
  • Sorts tools by name with tools.slice().sort((a, b) => a.name.localeCompare(b.name)) for a stable, deterministic output. All three current tool names are ASCII (canon_scan, keeper_lease, warden_check), so locale-sensitivity in localeCompare is a non-issue in practice.
  • The --check comparison (lines 38–43) normalises \r\n\n before comparing to rendered (which JSON.stringify always emits with \n). Edge case handled correctly.
  • If the manifest file is absent in --check mode, committed becomes '', which won't match a non-empty rendered JSON — exits 1 as expected.
  • Path resolution via fileURLToPath(import.meta.url) is the correct ESM idiom.

.github/workflows/truecopy-gate.yml

  • All three third-party actions are SHA-pinned:
    • actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
    • actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
    • askalf/truecopy-action@5c6fcf00edb83ff4846206392722292d45fee227 # v1.0.1
  • permissions: read-all and persist-credentials: false are correct hardening for a security-tooling repo.
  • Step ordering is correct: npm cidump-tools --check (manifest == code) → truecopy-action verify (manifest == pinned lock). A change to tool name, description, or schema fails step 2; a tampered manifest-on-disk that bypasses --check would fail step 3. The two gates catch complementary failure modes.

mcp-manifest.json

Cross-checked the three tool schemas against the definitions in mcp.mjs:

  • warden_check: input is z.record(z.any()).optional() in code → only tool in required[] in manifest ✓
  • keeper_lease: host, ttlS, uses all optional in code → only name in required[] in manifest ✓
  • canon_scan: manifest is required in both ✓

CI's dump-tools --check step passing at the PR head is the authoritative proof that the committed manifest matches the live server output.

truecopy.lock

Lock file carries per-tool content hashes and a (manifest) hash for the whole file. verdict: "clean" with findings: 0 from redstamp 0.4.1. The CI truecopy-action verify step (14 s, green) confirms the lock is internally consistent.

CI status (all green at PR head)

  • CodeQL / analyze (javascript-typescript) — pass
  • test — pass
  • verify pinned tool surface — pass (14 s)

Minor (non-blocking)

  • truecopy-gate.yml line 31: the truecopy-action step has no with: block specifying the action verb. The step name says "truecopy verify" and CI confirms it runs correctly, but an explicit with: { action: verify } (or whatever the action's input key is) would make the intent self-documenting without needing to consult the action's README.
  • support/dump-tools.mjs line 35: await client.close() is called but server.close() is not. For a one-shot script that exits immediately after, this is harmless — the OS reclaims everything. Worth knowing if this script is ever adapted for use inside a longer-lived process.

What's good

Dogfooding the project's own supply-chain tool on its own tool surface is exactly the right move for a security-tooling repo. The two-stage gate (code→manifest, manifest→lock) closes the obvious escape hatch where a committed manifest could be manually forged — you'd need to forge a hash that the truecopy verifier accepts. The script uses the real MCP wire protocol rather than introspecting exported metadata directly, which means schema-generation side effects from dep bumps are caught automatically. Clean work.

@askalf
askalf merged commit 007a80b into master Jul 16, 2026
4 checks passed
@askalf
askalf deleted the feat/pin-own-tool-surface branch July 16, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants