pin-the-protocol
A verifying MCP gateway that pins approved tool manifests and skill files to content addresses, blocking tampered descriptions before they reach model context.
README
mcp-skills-integrity
Approve what an agent reads by content, not by name.
Integrity verification for MCP tool descriptions and Agent Skills, so a changed tool or skill is caught before it reaches model context.
Every line in that picture is reproduced by ./demo.sh on your machine.
The problem
Agents load a server's tool descriptions, and a skill's SKILL.md, directly into model context, and they fetch them by name every time. That text is mutable: whoever can update the server or the file changes what the agent does, after you approved it. This is the documented MCP tool poisoning attack, and it applies just as cleanly to Agent Skills. Model Context Protocol is now a Linux Foundation and AAIF project at ecosystem scale, so this is the supply chain gap sitting under three AAIF working groups: Security and Privacy, Identity and Trust, and Observability and Traceability.
The solution
A verifying gateway pins each approved server's tool manifest, and each approved skill's
SKILL.md, to its content address. Any drift is blocked and diffed before it reaches model context, and the fleet's trusted state is one signed, auditable root.
Approval stops being a name and becomes a hash. A changed tool description is, by definition, a different address the agent never approved, so a change is rejected outright, not merely flagged after the fact.
The content address and signed audit root are backed by the secure, content-addressable kappa registry from The UOR Foundation: an OCI style registry that verifies every blob against its own hash on write and signs a deterministic root over the namespace. It gives the guarantee a durable, standards-aligned home instead of an ad hoc key value store.
What you'll see: ./demo.sh, four acts
| Act | What runs | Result |
|---|---|---|
| 1. The attack, unprotected | A real MCP client connects directly to a vendor server. The vendor ships an update: same name, same endpoint, altered description. | The agent reads the altered text and leaks a planted secret into a tool call. No signal. |
| 2. The same attack, through the gateway | Identical attack, connection routed through the verifying gateway. | Change caught, diff printed, altered description never enters context. Nothing leaks. |
| 3. Signed, verifiable fleet state | Fetch the ed25519 signed namespace root and verify it against the pinned store key; present a foreign key to prove the check is real. | One request proves exactly which tool and skill versions the fleet trusts. |
| 4. Skills are the new MCP | Approve a skill, then an attacker rewrites its SKILL.md. |
Same content addressed guarantee: the altered skill is refused at activation, with a diff. |
Each act writes machine checkable proof to ./evidence/ so a skeptic can validate without trusting the terminal.
Drops into the agents you already use
The gateway speaks plain MCP over stdio, and skill-lock is a one line pre-activation check, so this drops into any MCP or Skills capable agent without code changes. Point the agent's MCP config at the gateway instead of the server; wrap skill activation with skill-lock verify. Copy paste configs for Claude Code, Claude Desktop, goose, Cursor, OpenClaw, Hermes, VS Code, GitHub Copilot, and Gemini CLI are in docs/compatibility.md.
// e.g. Claude Desktop or Claude Code mcpServers entry, wrapping any server:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["gateway/gateway.mjs", "--pin", "weather.v1", "--",
"node", "vendor/weather-server.mjs"]
}
}
}
Quickstart
git clone https://github.com/humuhumu33/mcp-skills-integrity
cd mcp-skills-integrity
./demo.sh
Requirements: Node 20+ and a Rust toolchain (cargo) to build the trust store from source on first run, or Docker if you'd rather not install Rust (a self contained image is built for you). Linux or macOS: the content addressed store needs a POSIX filesystem.
Clean up with ./demo.sh clean.
How it works
agent <==MCP==> verifying gateway <==MCP==> vendor MCP server
|
| root of trust (this disk): approved manifest
+-- ./pins/ <-- the check reads THIS, not the store
|
+-- content addressed store (audit + distribution)
(verify on write blobs + ed25519 signed root)
The gateway is a normal MCP server to the agent and a normal MCP client to the upstream server, the same position as any MCP gateway. Three properties make the guarantee hold rather than merely appear to:
- The root of trust is local. On approval the full manifest is written to
./pins/on the verifier's own disk. Enforcement compares the upstream against that local record, never against a value fetched back from the shared store. So an attacker who can write to the store cannot change what "approved" means. - The whole read surface is pinned, not just descriptions. The manifest covers everything an MCP server can place into a model's context: server
instructions, and for every tool, prompt, and resource its name, title, description, schema, and annotations. A change in any of them is a new address. - The audit key is pinned. The store's ed25519 key is captured out of band at first approval; the signed root is later verified against that pinned key, so a substituted or attacker run store is rejected, not trusted on sight.
On every connection the gateway re-derives the address and compares to the local approval:
- match: forward the upstream unchanged (tools, prompts, and resources);
- change: refuse, print a unified diff of exactly what changed, and hand the agent nothing but a
pin_verification_failednotice.
The store is UOR-Foundation/kappa-registry, an OCI style /v2/ registry used off the shelf for audit and distribution: it verifies content on write (a blob whose bytes don't match its address is rejected with DIGEST_INVALID) and signs a deterministic namespace root over every pin. skill-lock applies the identical local approve and verify flow to SKILL.md files.
Adversarial gate
The three attacks that broke an earlier, naive design (overwriting the store pin, injecting outside the tool description, and forging the signed root with an attacker key) now run on every build in client/adversarial.mjs and must all stay defended. A regression flips one and fails CI.
Alignment with AAIF
| AAIF working group or roadmap item | What this demo shows |
|---|---|
| Security and Privacy: security by design, adversarial testing | The tool poisoning attack is reproduced, then blocked by construction. |
| Identity and Trust: delegation you can rely on | Approval is bound to content, not a mutable name. |
| Observability and Traceability: audit capabilities | A signed namespace root is a one request, verifiable audit of fleet state. |
| MCP 2026 roadmap: enterprise audit trails, Server Cards | The pinned manifest is a signed Server Card; the signed root is the audit trail. |
| Agent Skills: portable, cross product skills | The same guarantee extends to SKILL.md, unchanged. |
Design notes: whole skill trees and a decentralized substrate
Two explorations extend the same content-addressed model beyond the demo:
-
Encoding whole agent-skill directories. A Hermes skill is a directory (
SKILL.mdplusscripts/,references/,assets/), and its ecosystem trusts skills by comparing hashes against an unauthenticated origin, with no signing.skill-tree/skill-tree.mjscontent-addresses the whole tree into a Merkle manifest whose address is the skill's identity, catches a change in any file, and gains a signed provenance from the registry root. Details: docs/design/encoding-hermes-skills.md.node skill-tree/skill-tree.mjs encode skills-samples/document/pdf-fill node skill-tree/skill-tree.mjs verify skills-samples/document/pdf-fill -
A decentralized storage substrate (Filecoin and S3). Because addresses are the proof, the storage layer is untrusted and swappable: local disk, an S3-compatible object store (MinIO, R2, or Storj — decentralized and S3-compatible), or Filecoin/IPFS for durable archival, with reads verified on arrival. Details: docs/design/storage-substrate.md.
Honest limitations
- The guarantee protects the read surface an agent loads: tool and prompt and resource text, schemas, annotations, and server instructions. A server that behaves maliciously at call time without changing any of that is a separate problem (runtime behavior, not supply chain).
- The store's key is pinned trust on first use: the first approval records it. If the very first approval already talks to an impostor store, that impostor is what gets pinned. Distributing the expected key ahead of time closes this; it is out of scope for the demo.
- The store's authorization is permissive, but it is no longer load bearing: enforcement trusts the local
./pins/record, so store write access does not grant an attacker control over approvals (see the adversarial gate). The store still holds blobs and the audit root. - Single node store; horizontal deployment and external identity are out of scope for the demo.
- The "agent" in Acts 1 and 2 is a deliberately naive, model free stand-in that follows instructions found in tool descriptions, exactly the behavior that makes the attack real, so the demo is deterministic and safe to run in CI.
Repository map
| Path | Responsibility |
|---|---|
gateway/gateway.mjs |
The verifying MCP gateway (approve and enforce). |
lib/pins.mjs |
The local root of trust: approved manifests and the pinned store key. |
lib/manifest.mjs |
Capture the full server read surface as a stable, hashable manifest. |
lib/store.mjs |
Store client: content address, blobs, signed root verification, diff. |
vendor/weather-server.mjs |
A real MCP server; POISON=1 serves the altered variant. |
skill-lock/skill-lock.mjs |
Pin and verify a single-file Agent Skill by content address. |
skill-tree/skill-tree.mjs |
Content-address a whole skill directory as a Merkle manifest. |
client/adversarial.mjs |
The adversarial regression gate (attacks that must stay defended). |
docs/design/ |
Design notes: encoding skill trees, and the storage substrate. |
client/ |
The scripted acts and the naive agent stand-in. |
scripts/trust-store.sh |
Start and stop the content addressed trust store. |
docs/compatibility.md |
Copy paste configs for popular agents. |
Sources
- AAIF, AAIF projects, Linux Foundation announcement
- MCP security best practices, MCP roadmap, MCP TypeScript SDK, MCP Inspector
- Invariant Labs: MCP Tool Poisoning Attacks
- Agent Skills, goose, UOR-Foundation/kappa-registry
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。