Docs
FRAYWICK is a public watchtower for MCP servers and Agent Skills. Every hour it reads public tool definitions, hashes every definition and anchors one merkle root on Robinhood Chain. It routes nothing and hosts nothing: it only reads and stamps.
What we watch
- MCP servers from the official registry (
registry.modelcontextprotocol.io/v0/servers, latest versions). For every public remote endpoint (streamable HTTP or SSE) we readtools/list, andprompts/list/resources/listwhen the server advertises them. Both the stateless 2026-07-28 style and the olderinitializehandshake are supported. - Packages named by those servers on npm and PyPI: latest version, file integrity hashes and maintainers. Metadata only, nothing is downloaded or installed.
- Agent Skills:
SKILL.mdfiles from a public list of GitHub repositories, read as text at a pinned commit.
Hashing
Item hash = keccak256(utf8(JCS(view))), where JCS is RFC 8785 canonical JSON and the view keeps only these fields (missing fields are omitted):
tool {name, title, description, inputSchema, outputSchema, annotations}
prompt {name, title, description, arguments}
resource {uri, name, title, description, mimeType}
List hash = keccak256(concat(sorted item hashes)). An empty list hashes to keccak256(0x); a list the server does not advertise, or that could not be read, is bytes32(0).
One leaf per tracked thing, all with the encoding ["string","string","bytes32","bytes32","bytes32"]:
server [serverId, version, toolsHash, promptsHash, resourcesHash]
skill ["skill:" + repo + ":" + path, commit, keccak256(SKILL.md bytes), 0x0, 0x0]
package ["npm:" + name | "pypi:" + name, version, integrityHash, maintainersHash, 0x0]
leaf hash = keccak256(bytes.concat(keccak256(abi.encode(values))))
integrityHash is keccak256(utf8(dist.integrity)) on npm, and on PyPI keccak256 of the sorted sha256:<hex> lines of every file of the release. maintainersHash is keccak256(utf8(JCS(sorted unique names))).
At minute :05 of every hour all leaves go into an OpenZeppelin StandardMerkleTree (sorted leaves, sorted pairs). The snapshot with every leaf and the item-level detail is published at /snapshots/<epoch>.json, and the root is posted with post(epoch, root, leafCount, snapshotURI), where epoch is the UTC hour number (unix / 3600). Proof rebuilds any root in your browser.
Scoring and labels
When a hash changes we keep both versions, compute a text diff and add up plain rules (0 to 100): new instructions aimed at the model ("ignore previous", "before using this tool", "do not tell the user", <IMPORTANT>…), invisible, bidi or tag unicode, new URLs and hosts (more for tunnels, paste sites, webhooks and raw IPs), new parameters that ask for secrets or free-form context (notes, context, ~/.ssh, .env, id_rsa, wallet, seed phrase…), shell pipes, description length jumps, wider tool annotations, and on packages a new maintainer or new file bytes under the same version.
- cosmetic under 20: wording only.
- behavior 20 to 59: what the tool asks for or does changed.
- suspicious 60 or more: also recorded on chain with
flag(epoch, subject, oldHash, newHash, severity, note)(at most 10 per hour).
A label describes a diff. It is not an accusation, and fetched text is never given to a model as instructions.
Bot policy
Our crawler identifies itself as FraywickBot/1.0 (+/docs#bot). It sends read-only MCP requests (tools/list, prompts/list, resources/list, and the handshake they need) once an hour, with at most 16 requests in flight overall and about one request per second per host. It never logs in: a server that answers 401 or 403 is recorded as login-required with its registry metadata only. It backs off on 429 and on repeated failures.
To opt out, add a group for FraywickBot to your robots.txt (we re-read it daily), for example:
User-agent: FraywickBot
Disallow: /
or ask us and we add your host to the opt-out list. Opted-out hosts keep their registry metadata only and are counted in /api/health.
CLI
npx fraywick verify https://example.com/mcp
npx fraywick verify npm:some-package
npx fraywick verify https://github.com/owner/repo/blob/main/skills/x/SKILL.md --epoch 497872
The CLI fetches the target, recomputes the hash on your machine, asks the API for the merkle proof and calls verify() on Robinhood Chain through https://robinhood-rpc.publicnode.com. Exit code 0 = matches the anchor, 1 = differs, 2 = could not check.
The command line verifier is not published on npm yet. Until it is, use verify in your browser on any server page, or the /mcp endpoint below.
/mcp endpoint
FRAYWICK is itself a read-only remote MCP server at /mcp (stateless 2026-07-28 style and the legacy handshake). Tools: check_server, check_skill, recent_changes, get_proof. Their descriptions are plain, and their hashes are in our own snapshots like everyone else's.
Contract
- ToolAnchor
- address published at deploy
- chain
- Robinhood Chain mainnet, chain id 4663
- views
anchors(epoch) → (root, leafCount, postedAt),latestEpoch(),anchorCount(),verify(epoch, leaf, proof) → bool- writes
postandflag, poster key only; epochs strictly increase and cannot be in the future. No funds, no token, no upgrades.- explorer
- robinhoodchain.blockscout.com
- source
- verified (Sourcify exact match, shown on Blockscout) · code, tests and deploy script: github.com/fraywick/fraywick
Public API
GET /api/health GET /api/stats GET /api/feed?cursor=&label=
GET /api/server/<id> GET /api/item/<id>/history
GET /api/anchors?limit= GET /api/proof?epoch=&leafKey=
POST /api/check {"target": "…"} GET /snapshots/<epoch>.json
JSON, read-only CORS, rate limited. /api/check accepts HTTPS URLs only, refuses private, loopback, link-local and metadata addresses (also after redirects), caps responses at 2 MB and gives up after 10 s.
Honest limits
- We see what a server returns to an anonymous client from our network. A server can answer differently to other clients, at other times or after login. Login-required servers are metadata only.
- Hourly means a definition can change and change back between two reads.
- We never run code. A package's behaviour can change without its metadata changing; we only see version, file hashes and maintainers.
- Scores are heuristics. A benign rewrite can score high and a careful attack can score low. Read the diff.
- The anchor proves what we observed and when. It does not prove the observation was complete.