deedroll MCP server
Commit Oct 17, 2026Verifies MCP server code against its registry declarations to detect undeclared credentials, network egress, and tool description changes.
Also known as antharmaya/deedroll
- Stars
- 0
- Tools
- 1
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Oct 17, 2026
- License
- MIT
What is deedroll MCP?
deedroll verifies MCP server code against its registry declarations to detect undeclared credentials, network egress, and tool description changes. It exposes 1 tool, such as uses.
Summary generated from the project's README
About deedroll MCP server
Verifying the security and integrity of MCP servers before installation is what deedroll enables for developers and security-conscious teams. It inspects server code to detect undeclared credentials, hidden network egress, and unauthorized changes to tool descriptions. By comparing what a server actually does against its official registry declarations, it provides evidence-based reports that point to the exact file and line of any discrepancy.
Users can run scans via a web browser or a command line interface using Node.js. The tool works locally by reading npm or PyPI packages in memory without executing install scripts, and it probes hosted servers using a read-only approach that never calls actual tools. It can also audit existing configurations for clients like Claude Desktop, Cursor, and Gemini CLI to identify risks in already installed servers. Because it performs static analysis and read-only probes, no API keys, scan results, or tool descriptions are ever sent to external services.
What you can do with it
- 1Detect hidden credentialsIdentify if a package is accessing environment variables that were not declared in its registry listing.
Written from the project's README. Every use case is checked against a quote from it.
deedroll MCP tools 1
Detected from the READMEuses
| uses | — |
README
View on GitHubdeedroll
A deed roll is a register of deeds: what was actually done, written down and kept. deedroll keeps one for MCP servers.
Check an MCP server before you trust it. deedroll reads what a server's code actually does and compares it with what the server tells you, then reports every difference with the file and line that proves it.
It never installs, extracts to disk, or runs what it inspects. Packages are read in memory, so install scripts never run. Hosted servers are asked for their tool list, and how their sign-in works, and nothing else: no tool is ever called.
What leaves your machine: public package names and versions (sent to npm or PyPI, the MCP registry and OSV.dev); a probe contacts the server you name. No scan result, config, key or tool description is ever sent anywhere. No account, no API key.
Try it in a minute
In a browser: https://deedroll.antharmaya.com (the same engine, running in the page).
To run it locally instead: node scripts/serve.js, then open http://localhost:4173/web/.
Type a server URL, a package or a registry name, click an example, or paste what your config
says (npx -y …, uvx …).
Most hosted servers don't let web pages read their answers (25 of 44 sampled, 2026-09-28). For
those, the page offers deedroll's relay: the same read-only probe, run from the server behind the
page. It asks first, sees only the URL, and stores nothing. scripts/serve.js includes the relay
for local use.
The page does everything the command line does:
| In the page | Command line |
|---|---|
| Scan a package, a server URL or a registry name | deedroll <target> |
| Check this computer: which MCP servers on common ports a website could reach | deedroll --local (every port, and --subnet) |
| Servers your agents trust: choose your config files; read in the page, never uploaded | deedroll --installed |
| Download the report as JSON, SARIF, an egress allowlist or a registry block | --json, --sarif, --egress, --registry-meta |
| Why each finding matters, and every check | deedroll explain |
| The registry history, and verifying it in your browser | snapshot.js --status, --verify |
Not in the page: --deps, the agent-judge protocol (--semantic=agent), and tool-name
collisions (which needs every configured server's tools fetched at once, not one at a
time on click) — these need a command line.
On the command line (Node 22+):
npx @antharmaya/deedroll io.github.owner/some-server # any official-registry listing
npx @antharmaya/deedroll npm:@modelcontextprotocol/server-filesystem
npx @antharmaya/deedroll pypi:mcp-server-fetch
npx @antharmaya/deedroll https://learn.microsoft.com/api/mcp
npx @antharmaya/deedroll --installed # everything your agents already trust
npx @antharmaya/deedroll explain # what every check means
From a clone, node bin/deedroll.js works the same way.
What it can scan
| You give it | What happens |
|---|---|
A registry name, io.github.owner/server |
Looks the listing up in the official MCP registry. If it ships an npm or PyPI package, reads it; if it is hosted only, probes it. Also reports when the registry has deprecated or removed the listing. |
npm:<package> |
Downloads the tarball, verifies npm's hash, reads it in memory. |
pypi:<package> |
Reads the wheel pip install would pick (pure-Python first), or the sdist when there is no wheel, and verifies PyPI's hash. |
https://<host>/mcp |
Lists the server's tools read-only, never calls one. Speaks the current protocol (2026-07-28, stateless) and older ones. Pins the tools and reports changes on later runs. If it requires sign-in, checks how that sign-in is built, from public metadata only. |
--installed |
Reads the MCP configs of Claude Code, Codex, Claude Desktop, Cursor, Devin and Gemini CLI, scans each npm and PyPI server, and with --remote probes hosted ones. Nothing is launched. |
--local |
Finds MCP servers listening on this machine (every listening port, with the owning process) or, with --subnet 192.168.1.0/24, on a private network you own. Checks each for network exposure, sign-in, and whether it rejects requests from other websites, as the specification requires. |
Not yet: Docker images, .mcpb bundles, NuGet and Cargo packages; the tool lists of servers that
require sign-in (their sign-in is checked, their tools are not listed, since deedroll uses no
account); and tool lists over the deprecated HTTP+SSE transport (detected and reported). Each is
reported as "not scanned" with the reason, never as clean.
Reading a result
pretrip-mcp@1.0.1
in registry · no env vars declared · 2 file(s) scanned
registry: listed as agency.kesey/pretrip (found via index of 2026-09-28)
provenance: none
HIGH undeclared-env reads PRETRIP_API_KEY (looks like a credential) but the registry entry does not declare it
index.mjs:23 const API_KEY = process.env.PRETRIP_API_KEY || "";
MEDIUM provenance no repository field: the published code cannot be traced to source
package.json repository: absent
LOW undeclared-env reads PRETRIP_API_BASE but the registry entry does not declare it
index.mjs:22 const API_BASE = (process.env.PRETRIP_API_BASE || "https://scan.kesey.agency/api/v1")…
INFO network-egress contacts scan.kesey.agency
1 high · 1 medium · 1 low · 1 info
What these mean: deedroll explain undeclared-env · deedroll explain provenance
| Severity | Meaning |
|---|---|
| high | Do not install until you have looked. |
| medium | Worth understanding before you rely on it. |
| low | Good to know. |
| info | Context, not a problem. A filesystem server writing files is the product working. |
Every finding names a check. deedroll explain <check> says why it matters and what to do; the web
page shows the same text under each finding; docs/checks.md lists all of them.
The exit code is 1 when anything at or above --fail-on (default high) is found, so it fits in CI.
Output for tools
| Flag | For |
|---|---|
--json |
Scripts and pipelines. Schema deedroll/v1 (docs/schema-v1.md): stable check ids, and a stable id per finding for baselines and suppressions. |
--sarif |
GitHub code scanning, Azure DevOps and security dashboards. SARIF 2.1.0, validated against the official schema. |
--egress |
Egress proxies. The hosts the server's code names, as a starting allowlist (static, so a starting point). |
--registry-meta |
Registries and marketplaces. A _meta block under com.antharmaya/deedroll that a subregistry can attach to a listing: the mechanism the official registry documents for "security scan results". |
GitHub code scanning, with the action in this repository (it needs no install: deedroll has no dependencies):
- uses: antharmaya/deedroll@v0
with:
target: io.github.owner/server # or npm:<package>, pypi:<package>, https://host/mcp
fail-on: never # or high, medium, low
- uses: github/codeql-action/upload-sarif@v4
with: { sarif_file: deedroll.sarif }
It also writes the plain report to the job summary. To check a release after it is published,
run it on a schedule or on release.
What it checks
Grouped here; every check, with why and what to do, is in docs/checks.md.
- The code against the listing. Credentials and settings the code reads that the registry entry never declares, code that runs at install (including PyPI packages with no wheel), hosts it contacts, powerful capabilities (running programs, evaluating code, writing files), and tool descriptions written to instruct the model.
- Where the code came from. No linked source, a download that does not match the registry's hash, provenance present on earlier releases and missing now, and a release built from a different repository than the one it links to. Deprecated npm versions and yanked PyPI releases.
- The listing itself. Deprecated or removed by the registry, or claimed by several listings.
- Known vulnerabilities, from OSV.dev (includes GitHub's advisories). A failed lookup is reported as failed, never as clean.
- Hosted servers. A tool description that changed since the last probe is high, with the old and new text: a hosted server can rewrite what its tools tell the model at any time. For servers that require sign-in: whether they publish how to sign in (as the spec requires), PKCE, issuer and token-audience checks, and registration only through deprecated mechanisms.
- Upstream status. Reference servers the MCP project has archived, which PyPI does not mark.
- Your own configs. Plaintext secrets, launches that always pull the newest version, and two different servers offering a tool with the same name (a client resolves tools by name, so either could silently answer a call meant for the other).
How it compares
Checked against each project's own README on 2026-09-28; rechecked 2026-10-02.
| deedroll | Snyk Agent Scan | Cisco mcp-scanner | |
|---|---|---|---|
| Runs the server to inspect it | Never | Starts stdio servers from your config | In its stdio mode (it also has static modes) |
| Sends tool descriptions elsewhere | Never | To Snyk (cannot be disabled) | Only with its API or LLM analyzers |
| Account or API key needed | No | Snyk token | Only for its API, LLM and VirusTotal analyzers |
| Reads npm and PyPI packages | Yes | No | Yes |
| Registry listing compared with the code | Yes | No | No (compares docstrings with code, through an LLM) |
| Provenance and publisher checks | Yes | No | No |
| Rug-pull pins for hosted tools | Yes | Yes | No |
| SARIF output | Yes | Not documented | No |
| Runs in a browser | Yes | No | No |
| Model judgment of descriptions | Optional, by your own agent | Yes, by Snyk | Optional |
Others worth knowing: Glama grades each tool's description and keeps a schema changelog (and awesome-mcp-servers asks for a Glama score before listing a server); Docker's MCP Catalog signs the images it builds. Neither reads what a package's code does.
Where they are ahead: both judge tool descriptions for prompt injection with a model by default; Cisco traces dataflow across files in ten languages; Snyk also covers agent skills. deedroll's place is the check you run before installing, with evidence for every finding and nothing sent anywhere. An independent audit that ran three scanners on 33 servers found about 78% of their pattern detections were false positives (AppSec Santa, July 2026); deedroll only reports what it can point at.
Hosted servers: probe, pin, and catch the rug pull
deedroll https://learn.microsoft.com/api/mcp # probe, check descriptions, pin every tool
deedroll https://learn.microsoft.com/api/mcp # later: reports anything that changed
deedroll <url> --update-pins # accept the changes and re-pin
deedroll --installed --remote --auth-from-env # every hosted server your agents trust
- Both protocol generations. Revision 2026-07-28 made MCP stateless: no
initialize, no session, version and client identity on every request. deedroll sends a current request first and falls back to the older handshake only when the reply shows an older server, as the specification prescribes. On 30 random registry endpoints (2026-09-28): 2 answered on 2026-07-28, 18 on older revisions, 5 needed sign-in, 5 were not live. - Read-only by construction: only
tools/list(plusinitializefor older servers), nevertools/call. The server does see the connection. - Never auto-accepted: a changed server is reported on every probe until
--update-pins. - Hostile-server limits: a 15 s timeout, a 5 MB response cap, and redirects not followed (a redirect would carry the request, and any credential, somewhere else).
- Credentials are opt-in:
--auth-from-envresolves${VAR}header references and sends them only to that server.
The registry history
A hosted server can change what its tools say after you approved them, and the official registry keeps no guarantees about its own data. So deedroll keeps a record: once a day, every listing's declarations (packages, settings, endpoints, status), a diff against the day before, and the tool lists of a rotating slice of hosted servers. Each day's entry is hash-chained to the one before, so the history cannot be quietly rewritten.
node scripts/snapshot.js # take today's snapshot (a daily systemd timer runs this)
node scripts/snapshot.js --status # the last runs, and a warning if the record went stale
node scripts/snapshot.js --verify # re-hash every file and check every link in the chain
Published at https://deedroll.antharmaya.com/history/chain.jsonl (Cloudflare R2), and
verifiable from the web page. The local copy is under archive/ (not in git). Measured on the first run: about 4 MB a day (3.4 MB of listings, 0.6 MB of tool lists), roughly 1.5 GB a year before deduplication. At 500 endpoints a day, every hosted endpoint comes round about every 46 days.
The ledger: every MCP server's history
The daily snapshots fold into a timeline per server, like git log. Each server's timeline
records:
- when the record first saw it;
- every release;
- every change to its description, status, repository, packages, declared settings (including secret flags), endpoints and headers;
- every change in what its hosted tools say;
- when it disappeared or came back.
GET /api/servers?q=github&limit=25 search all servers (exact name, then prefix, then text)
GET /api/servers/<registry name> one server: its latest listing, probes and full log
GET /api/servers the ledger's span and size
Live at https://deedroll.antharmaya.com/api/servers. JSON, open to any origin, cached at the
edge for ten minutes.
Follow one server without an account:
GET /feed/<registry name>.atom its log in plain sentences, newest first
GET /badge/<registry name>.json a shields.io endpoint badge
The badge states one measured fact, how what the code reads compares with what the listing declares: "declares what it reads", "1 credential in README only", "2 credentials not declared", or "on record" for a hosted server. It never says safe or verified. For a README:
[](https://deedroll.antharmaya.com/web/?q=io.github.owner/server)
The page builds this for any listed server: scan it, then Copy README badge.
node scripts/build-ledger.js folds any new day into the saved state;
--rebuild replays every snapshot from scratch and must give the same result
(test/ledger.test.js holds it to that); --upload publishes changed files to R2 under
ledger/. The nightly service runs it after each snapshot.
Design notes:
- The snapshots are the source of truth; the ledger is a view of them, so its storage can change for the price of a rebuild.
- 64 shard files, because each upload is one wrangler call: about 9 minutes a night today.
- Search scans plain ASCII texts with
indexOfand parses only the lines that match, so it fits a Worker's CPU budget (2–6 ms a query on 37,892 servers). Words in non-Latin scripts inside descriptions are not searchable yet. - Each search file starts with its build stamp, and the Worker never combines files from two different builds.
Package facts. node scripts/scan-packages.js [--limit 1500] scans every distinct npm and
PyPI package version the registry points at (about 14,300), once each, without running it, and
with no tarball cache. Each run takes new versions of already-scanned packages first, then the
rest, then retries failures older than a week.
- A package's facts are package-level: what its code reads, contacts and can do, install scripts, provenance, known vulnerabilities.
- Whether a read is declared belongs to each listing, so the ledger judges it per server.
Every record carries
signalsper package: credentials mentioned nowhere, ones its README explains, other settings, hosts, capabilities. - It logs a
scannedentry when those facts change, such as a release that starts reading a payment key. - A scan waits until its own day's snapshot is in the ledger.
test/build-ledger.test.jsruns the real script night by night and against--rebuild, and requires identical output.
"Seen" means seen by this record, which began on 2026-09-28. A server listed that day may be
much older; its publishedAt says when.
Audit what you already trust
npx @antharmaya/deedroll --installed # --all for every server, --remote to probe hosted ones
Your config files contain live keys, so values are dropped the moment they are parsed: every later stage sees a variable's name and whether it was a literal, never its value. A test plants a fake secret in every place a config can hold one and fails if it appears in any output.
Use it from Claude Code or Codex
The static scan knows what the code can do. Whether the descriptions say so is a language question, and the agent you already run can answer it, with no API key:
deedroll npm:<package> --semantic=agent > request.json # what to judge, and the rules
deedroll --answers answers.json # the agent's answers, checked and applied
The descriptions were written by the publisher, so the request treats them as untrusted data, a "yes" must quote the disclosing sentence verbatim (checked), and answers are bound to a hash of the exact request. Install the skill so your agent scans before installing any server:
claude plugin marketplace add ./deedroll && claude plugin install deedroll@antharmaya # Claude Code
cp -r deedroll/skills/deedroll ~/.codex/skills/ # Codex
--semantic does the same through TypeSafe's API instead (TYPESAFE_API_KEY); its thresholds are
not calibrated yet. The default scan never calls a model.
Speed, cache and the registry index
- The official registry's search matches listing names only, so deedroll ships an index from
package names to listings (
src/data/registry-index.json: 36,906 listings, 9,796 npm and 3,868 PyPI packages, built 2026-09-28 byscripts/build-index.js). A complete index under seven days old is trusted on a miss, and says so ("no listing as of "). The web page does not use this copy: each night the ledger build rebuilds the index from that day's registry snapshot and the site serves it from there, so the page's lookup is never more than a day behind. - npm tarballs are cached compressed under
~/.cache/deedroll/tarballs, keyed by their sha512 and re-verified on every read.--no-cacheturns it off. --depsalso reads a vendor's own dependencies (one level, bounded). On 27 vendor servers it changed the capability picture for 10, at about 60% more download, so it is off by default.- Median scan: 0.9 s over 40 random PyPI listings. The registry itself is sometimes slow (7 s per lookup on 2026-09-28); the web page says so rather than timing out.
What it does not do
It is static. It reads code, not behaviour: a server that builds a hostname at runtime or
downloads code after starting will not be caught. It does not follow dataflow across files, so it
reports that a credential is read, not where it goes. info findings are not accusations.
It does not trace whether a tool's input can reach a shell, a file path or a URL fetch. That class (input validation) is the largest among publicly listed MCP vulnerabilities: 17 of 50 in the Vulnerable MCP Project (checked 2026-10-02). Cisco's mcp-scanner does trace it, with dataflow analysis plus an LLM reading the code; run it alongside if that is your question. deedroll reports that a server can run programs or write files, not whether a caller can steer it to. A difference between a listing and the code is a question for the publisher, not a verdict; a credential the package's README documents is reported as such, at low severity.
Measurements
The whole registry, every night (since 2026-10-15). Every listing that ships an npm or PyPI
package is scanned (new versions first) and the count is redone from the ledger each night; the
current figure, its date and its denominator are on the site and in /api/servers
(ledger.census). The rule, in scripts/ledger-views.js: of the listings present in that day's
registry snapshot that ship at least one npm or PyPI package and have at least one successful
scan, count those where some package's code reads a credential-like name (outside a string) that
the listing does not declare and the README never mentions; separately, those not in that count
where the README is the only place it is explained. Each server counts once. Shares are truncated
to one decimal, never rounded up. Listings that ship only OCI, MCPB, NuGet or Cargo packages, and
packages that could not be read, are reported apart, not counted as clean.
The sample below came first and is kept as history.
npm, 2026-09-26, re-run 2026-10-01. A seeded random sample of 60 npm-backed registry servers
(node scripts/registry-sweep.js 60, seed 20260926): 57 scanned across 53 publishers. 12
(21%) read a credential their registry listing does not declare. 8 of those 12 explain it in their
README; 4 (7%, 95% CI 3–17%, 4 publishers) mention it nowhere.
The 2026-10-01 re-run is the biggest correction yet. Until then the scanner never read a package's
README: its readers kept code files only. So it reported credentials as undeclared that the
README spells out, in a config example or an env-var table. One maintainer pointed this out after
being contacted; their key was a documented public free-tier key. The README is now read (and
kept apart from the code checks, so its examples are never cited as code). A credential it
documents is reported as low, a listing gap rather than a hidden read. Every one of the 8 was
re-checked by hand against its README. The credential rule was tightened
three times (locator names like *_URL on 2026-09-27; words inside other words, like KEY in
KEYCLOAK, on 2026-09-28; singular quantity suffixes like _HOUR/_DURATION on 2026-09-29; names that describe a credential rather than hold one, like _CREDENTIAL_BACKEND or _KEY_PREFIX, on 2026-10-01, found across 5,707 credential-flagged reads in the full registry scan, which left this sample unchanged) and
the evidence itself was tightened twice more on 2026-09-29, before any of the sample's publishers
were contacted: the scanner no longer cites a package's own comments (a docstring mentioning
process.env.X in prose is not a read) or its own test fixtures (*.test.ts stubbing a fake env
var to test config-loading is not the shipped server reading a credential) as evidence. Recomputed
by re-running the full sample fresh each time, the count moved from 14 to 13 to 12, then to 4
mentioned nowhere. Raw rows: findings.json (re-run 2026-10-01; each row now lists
readmeOnlySecrets apart from undeclaredSecrets). Limits: npm-backed
servers in the first 12,000 registry entries only.
PyPI, 2026-09-28. 40 random PyPI-backed listings (node scripts/pypi-benchmark.js 40): all 40
scanned, none errored. A benchmark of the scanner, not a published statistic.
deedroll MCP questions
What tools does deedroll MCP provide?
It exposes 1 tool, including uses.
Does deedroll run the code it inspects?
No, it never installs, extracts to disk, or runs what it inspects. Packages are read in memory to ensure install scripts never run.
Is my data or API key sent to any external services?
No. Only public package names and versions are sent to registries like npm or PyPI. No scan results, configurations, keys, or tool descriptions are sent anywhere.
How does deedroll handle hosted MCP servers?
It performs a read-only probe that only asks for the tool list and sign-in methods; no tools are ever called.