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
Visit Server

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

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured