notary-mcp

notary-mcp

An MCP approval gate that ensures agents execute only human-approved, hash-verified actions, providing fail-closed consent for consequential operations.

Category
访问服务器

README

notary-mcp

The human approves a hash, not a promise.

An MCP approval gate for agent actions. An agent proposes an action; the server normalizes it to canonical bytes and hashes them into a reviewed revision; a human sees a field-by-field manifest of exactly those bytes and approves that revision — and the agent may then execute only those bytes. Anything drifts by one character, the gate refuses.

Built for Push to Prod: Building at the Frontier (Bengaluru, 8 Aug 2026).

Why

Today, agent approval is a sentence in a chat window: the agent describes what it will do, you say yes, and then it does something — possibly not that thing, because it re-planned, or state moved underneath it, or a prompt injection rewrote its intent. The description you approved and the action that executes are not connected by anything.

notary-mcp makes consent content-addressed:

  • Canonical bytes. Every proposed action is normalized (RFC 8785 JSON canonicalization, NFC Unicode, surrogate rejection, whitespace collapse) so there is exactly one byte representation — nothing to smuggle in formatting.
  • A domain-separated hash of those bytes is the reviewed revision. The human's approval names this hash, not the draft, not the intent.
  • A complete manifest. The review step shows every leaf the hash covers — including values stored but hidden from the public, so approval is never blind.
  • Fail-closed execution. execute_post takes the revision as an argument. Wrong hash, drifted draft, superseded approval, consumed approval: refused. There is no code path from agent input to side effect that skips the human.
  • Idempotency. Operations carry a client operation_id plus a server-computed input fingerprint. Replays return the recorded result; the same id with different input is a terminal conflict. Retries can never double-fire.

The model explains; the hash authorizes. The model never gets to authorize.

Quickstart (zero credentials)

git clone https://github.com/salmanneedsajob/notary-mcp
cd notary-mcp
npm install
npm run prove      # 56 checks — every attack in the table below, refused
npm run demo       # the full loop over a real MCP stdio connection

Connect it to Claude:

claude mcp add notary -- npx tsx /absolute/path/to/notary-mcp/src/main.ts

Then ask Claude to draft a post with propose_post. Approve it yourself — the agent cannot:

npm run review              # list drafts awaiting review
npm run review <draftId>    # see the exact bytes + hash, approve y/N

Only after your approval does execute_post with that exact revision succeed. The published artifact — the approved canonical bytes — lands in out/<slug>.json.

The attack table

Every row is a check in npm run prove, run against the real executor and store:

Attack Result
Execute before any human approval APPROVAL_REQUIRED, no side effect
Flip one hex character of the approved revision STALE_REVISION
Edit the draft after approval, execute the old approved revision STALE_REVISION — approval names bytes, and those bytes are gone
Execute the drifted bytes without fresh approval APPROVAL_REQUIRED — every revision needs its own human
Replay a completed operation Served from the record, replay: true, no second side effect
Reuse an operation_id with different input OPERATION_ID_CONFLICT, terminal
Publish again with a consumed approval DRAFT_NOT_EDITABLE, terminal
Prompt injection inside a field ("IGNORE ALL PREVIOUS INSTRUCTIONS…") It's data. Hashed like everything else. Still needs a human.
Credential (bearer token) in the captured source PRIVACY_BLOCKED before any draft exists
Unicode smuggling (unpaired surrogates, formatting drift) Rejected or normalized before hashing
Two posts with identical titles Distinct permanent slugs — published URLs are never reused
Hidden-from-public field changed after review Different hash — hidden values are inside the approval boundary

Architecture

agent ──MCP──▶ server.ts ──▶ executor.ts ──▶ ports.ts ──▶ store.ts ──▶ out/
                (contract      (pure           (5-function   (JSON file;
                 boundary)      orchestration)  port)         swap for any DB)
                                    │
                    canonical.ts ───┤  RFC 8785 canonical JSON,
                    review.ts ──────┤  normalization, revision hash,
                    review-manifest ┘  input fingerprint, field manifest

human ──▶ scripts/review.ts  (manifest + y/N → approval of one revision)
  • src/contracts/v1.ts — the versioned tool contract. Strict validation schemas plus permissive transport schemas, so malformed input becomes a structured INVALID_ARGUMENT result instead of a protocol error.
  • src/canonical.ts — canonicalization and the two hash frames (revision + operation fingerprint), both domain-separated and length-prefixed.
  • src/executor.ts — pure orchestration. Every effect flows through the five-function port in src/ports.ts; capability checks, rate ceiling, and retry classification live here.
  • src/store.ts — the standalone port implementation (JSON file). In production this is a set of SECURITY DEFINER Postgres functions; the executor cannot tell the difference, which is the point.
  • The demo action is "publish a post" — the payload schema is an example. The gate pattern (propose → hash → human approves hash → execute exact bytes) applies to any consequential agent action: sending money, merging code, sending email.

Disclosure of prior work

This project comes out of Slopfolio, my invite-only portfolio platform for AI-assisted work, where this mechanism runs in production behind OAuth with a Postgres transactional boundary.

Existed before the hackathon: the underlying capture gateway — the canonicalization scheme, revision-hash and fingerprint framing, the V1 tool contract, the executor orchestration, the review-manifest builder, and the pure-logic checks in prove-contract.ts / prove-executor.ts (lifted from that codebase, renamed).

Built during Push to Prod: the hackathon work spans Slopfolio and this repo. On the Slopfolio side: the agent verification system built on top of the gate, and taking the MCP live for the public. This repo is the third piece — the standalone extraction of the gate mechanism, so it can be run and verified independently of Slopfolio: cutting it free of Supabase/OAuth/Next.js into a product-agnostic gate; the persistence port (ports.ts) and standalone store (store.ts); the stdio server (main.ts); the human review CLI (scripts/review.ts); the end-to-end adversarial gate suite (scripts/prove-gate.ts); and the wire demo (scripts/demo.ts).

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选