In short. After hand review, seven of the 50 MCP servers we read have code that names a third-party host no README mentions, in a sample our own audit found flawed. We think a README is often the only document people read before they connect, so we’d look at that gap first. Before you connect, check the hosts a server’s code names and the secrets it reads against its README.
What this is. A static study we ran ourselves on 50 npm packages, reading their code without running it. The scanner and its data are on GitHub, and the appendix gives the method and the sample’s limits.
Contents
The code can name what the README leaves out
Take nx-mcp. Its code names Google Analytics’ Measurement Protocol, with fields including user_id, editor and operating system. Its README shows a disableTelemetry setting, but names neither the host nor the fields. [1]
The hostinger-api-mcp package bundles a skill, a set of instructions for the AI app. That skill includes the command curl -s https://api.ipify.org, which returns the machine’s public IP. Its README doesn’t mention that host. [1]
Before you connect an MCP server, you usually have its README and its published package to go on. We don’t think a README is a security document. We do think it’s often the only document people read before they connect a server. What matters to us is the gap between what a tool needs and what its user is told.
Seven of the 50 name a host no README mentions
We checked each host the scanner found in the code against the README and package.json. After hand review, seven packages name a third-party host no README mentions, and five carry analytics or telemetry markers. [1] [4] The table shows which packages fall in each group.
The table sets each package’s code beside its README. Every row survived our hand review.
| Package | What its code names | What its README says | Host not in README | Analytics or telemetry marker |
|---|---|---|---|---|
| Hosts other than analytics | ||||
@upstash/context7-mcp | Microsoft Entra signing keys, for checking tokens | Doesn’t mention Entra | Yes | No |
hostinger-api-mcp | api.ipify.org, in a bundled skill | Doesn’t mention the host | Yes | No |
@sentry/mcp-server | Default endpoints from a bundled AI SDK | No README. We checked its package.json | Yes | No |
comfyui-mcp | Model providers | Doesn’t mention those providers | Yes | No |
| Analytics hosts (three of the seven) | ||||
nx-mcp | Google Analytics’ Measurement Protocol, with fields including user_id, editor and operating system | A disableTelemetry setting, but not the host or the fields | Yes | Yes |
figma-developer-mcp | A PostHog host and project key | Says nothing about it | Yes | Yes |
exa-mcp-server | An Agnost analytics SDK call | Says nothing about it | Yes | Yes |
| Telemetry the README documents | ||||
n8n-mcp | A first-party telemetry endpoint | Documents it | No | Yes |
@alisaitteke/photoshop-mcp | A Rybbit analytics host | Documents it, with an opt-out | No | Yes |
Outside our host check. The comfyui-mcp package also defines a tool, panel_add_mcp, whose code writes new MCP-server entries into the user’s ~/.claude.json. We read this in the code and didn’t run it. We checked its README for hosts, not tools, so we can’t say whether it mentions this one. [1]
A marker tells you where to read
The READMEs of figma-developer-mcp and exa-mcp-server say nothing about their analytics. Yet in both, the cited code is more careful with data than the bare marker suggests. In figma-developer-mcp, the code has two opt-outs, FRAMELINK_TELEMETRY=off and DO_NOT_TRACK, and redaction logic. In exa-mcp-server, the Agnost analytics SDK call switches off capture of tool inputs, outputs and errors. [4]
So we treat a marker as a reason to read the line, never as a finding about the package. A static marker doesn’t prove a runtime request, a behavior, intent or a vulnerability. We can’t say, for instance, whether the PostHog code in figma-developer-mcp runs in ordinary use. [1] The scanner can also miss a request that code builds from pieces it can’t join. [3]
In these 50, broad reach is common, and much of it is ordinary
Code in 24 of the 50 packages can read a secret-named environment variable, one whose name contains a string such as KEY, TOKEN or SECRET. In 20, it can start processes. In 16, it can find the user’s home directory. A package can fall in more than one group. These three are scanner counts. The seven and the five survived hand review. [7]
Many of these markers reflect ordinary engineering choices. A server that drives a local build tool may need to start processes. One that calls a hosted API usually reads its key from an environment variable. [1] So we’d look first where the code names what the README leaves out.
You can run the same check before you connect
- Open the server’s published package.
- List the hosts it names and the secrets it reads.
- Compare them with the README.
Where the code names a host the README leaves out, you have found the gap. Read that line before you decide whether to connect.
Scope and limits
- What we did
- We read the source in 50 npm tarballs without running it, including dependencies compiled into their bundles. We read every host-flagged package by hand at the cited line. The appendix gives the method.
- What we didn’t do
- We ran no package code, npm install or lifecycle script. We didn’t read transitive dependencies, and none of the 50 tarballs carried its own node_modules. We haven’t published the ranking step.
- What this can’t tell you
- It can’t show whether flagged code runs in ordinary use, or whether the user is told at runtime when it does. We checked only READMEs and package.json, and only for hosts. A vendor may document an endpoint elsewhere.
- Confidence
- High that each cited line holds what we report. Moderate in the counts, since the scanner can flag code that never runs and nobody has checked our review independently yet. Low in the sample as a guide to MCP servers. It rests on one month of downloads, a ranking of mostly zeros, and unrecorded exclusions.
- What would change our view
- A corrected sample, drawn from verified download counts with every exclusion recorded, that moves the counts. An independent check that overturns the hand review. A runtime study that drives each package through its documented tools in an instrumented sandbox and shows which flagged paths run and what they send.
- Corrections
- We log every correction, with its date, on How we work. Send one through a research enquiry.
Appendix
Our sample is the weak step
We called these 50 the most-downloaded servers among our candidates. [1] We were wrong to call them that.
How we drew it. We searched npm with four queries, listed in the report. We ranked the results by last month’s npm download counts. We removed clients, SDKs, frameworks, adapters, transports and platform binaries by hand. We kept 50 of the highest-ranked packages left. [2] Together they had about 19.6 million downloads in the month before the scan. [7]
The audit. Our own audit on 3 November found three problems.
- Many zeros were failed lookups. The ranking recorded zero downloads for most candidates, and many of those zeros were failed lookups. [1] One hid
chrome-devtools-mcp, an MCP server with about 8 million downloads in the month before the scan. [8] Of the packages we sampled, only@storybook/mcphad more, with nearly half the sample’s downloads. [1] - Hand exclusions went past our rule. We removed more packages by hand than our rule called for, and didn’t record why for each one. [1] Several of them call themselves MCP servers on npm, such as
@akutishevsky/lunchmoney-mcpand@ankimcp/anki-mcp-server. [8] - Our own counts don’t match. The report finds 703 unique packages in the search results, but the ranking file published with it,
ranked.json, lists 571. Nothing in our published materials explains the gap. [1]
The first repair is a new sample, drawn from checked counts, with every exclusion written down. [1] Until then, every count here holds for these 50 packages only. [1]
We read the code without running it
Collection. We pulled the tarballs from npm on 20 October 2026. Each went into its own isolated directory, and we neither installed nor ran it. [1]
What the scanner reads. The scanner reads the JavaScript and TypeScript files outside test, example and documentation directories. It skips node_modules, .d.ts and .map files. It reads only the first 20,000 characters of each line. [3]
What counts as documented. The scanner counted a host as documented if its registrable domain appeared in the README or in five package.json fields. Those fields are homepage, description, author, repository and bugs. A host also counted if it contained the package’s scope. For an unscoped package, the scope is the first hyphen-separated part of its name. [3] Sentry’s package ships without a README, and so does Supabase’s. For Sentry’s, package.json was the only baseline we checked. [4]
The markers. The scanner recorded eight kinds of marker. Five feed this essay, as defined below. [3] The other three, eval, network calls and file writes, are in the published per-package results. [5]
| Marker | What counts |
|---|---|
| URL | A URL outside comments |
| Analytics or telemetry host | A known analytics or telemetry host |
| Process start | A child_process import, or an execSync, spawnSync or execFileSync call |
| Home-directory lookup | Code that looks up the user’s home directory |
| Secret-named variable | Code reads it as process.env.NAME, and the name contains KEY, TOKEN, SECRET, PASSWORD, PASSWD or CREDENTIAL |
The hand review. Pattern matching can flag code that never runs, so we read every host-flagged package by hand at the cited file and line. That review dropped six cases, each with a reason. Among them were OAuth callback allowlists in two packages, a funding link, and a dashboard link in tool output that the scanner took for telemetry. A general rule covers the rest, such as documentation links, test fixtures, example domains and a vendor’s own hosts in its own package. [4]
The review cut both host counts hard. Packages with undocumented hosts fell from 13 to 7. Those with analytics or telemetry hosts fell from 7 to 5. [7] Nobody has checked the review independently yet. [4]
What we published. We published the scanner, the sample list, the ranking file, the hand review and the per-package results with the report. They’re also on GitHub, at Lugano-AI/mcp-source-study, under the Apache 2.0 license. Read its main table with care, since the Undocumented and Telemetry columns still list hosts for packages the hand review cleared. [1] Our evidence records give the file and line behind every observation. [6]
How to rerun it. Put the extracted packages in one directory and run the command below. Then run report.py to rebuild the report tables. We don’t bundle the tarballs. A rerun on fresh downloads may not match exactly, since versions and download counts keep changing. [1]
python3 -I scan.py <extracted_pkgs_dir> packages.txt ranked.json .
The open question
Which of these code paths run in ordinary use, and is the user told when they do?
A static read can’t answer that. Our own audit found the sample’s weak point, a download ranking with many failed lookups. So the next step is to redraw the sample from corrected counts and record every exclusion. After that would come a runtime study that drives each package through its documented tools in an instrumented sandbox. That study is only a proposal. We haven’t designed it in detail or run it.
Source materials
The report, the scanner and its raw results, for closer review. The report opens with a correction, dated 3 November 2026, to how it first described the sample. These are research artifacts, not evidence of a deployed service.
References
A selected review of published sources, with Lugano’s analysis and proposed work identified in the text. Source dates and conditions matter; historical results are not current-model rankings.
- 1
Static source observations from 50 sampled MCP server packages
The study report: sample, method, headline counts, per-package observations with cited files and lines, and limits.
Scope. Static code markers in a defined sample. It does not establish runtime behavior, intent, prevalence across MCP packages, or vulnerabilities. Its description of the sample as the 50 highest-ranked candidates rests on a ranking that recorded zero downloads for most candidates.Back to the essay
- 2
The 50-package sample (packages.txt)
The sampled packages, in download-rank order.
Scope. A list drawn from a one-month download ranking that recorded zero for most candidates; the reason for each exclusion is not recorded.Back to the essay
- 3
The static scanner (scan.py)
Records URLs, analytics hosts, process, home-directory and environment-variable markers in package source.
Scope. Pattern matching. It can miss behavior assembled at runtime and flag code that never runs.Back to the essay
- 4
Manual review record (review.json)
The findings that survived a hand reading at each cited location, and the false positives dropped with reasons.
Scope. An internal review; it has not been independently checked.Back to the essay
- 5
Per-package results (results.json)
The markers recorded for each sampled package.
Scope. Scanner output; host and telemetry entries include cases that manual review later dropped.Back to the essay
- 6
Cited source locations (evidence.jsonl)
One record per observation, with the package, file, line and source text.
Scope. Records of source text only; not runtime observations.Back to the essay
- 7
Scan summary and reviewed headline counts (summary.json, headline.json)
Scanner totals before review (summary.json) and the reviewed headline counts, including total monthly downloads (headline.json).
Scope. Aggregate counts for this sample only.Back to the essay
- 8
npm downloads API and registry metadata, checked 3 November 2026
Point download counts for 20 September to 19 October 2026 and package descriptions, queried during a Lugano audit of this study. chrome-devtools-mcp, recorded at zero in ranked.json, shows 8,032,928 downloads, and its registry entry calls it an MCP server. Hand-excluded candidates such as @akutishevsky/lunchmoney-mcp and @ankimcp/anki-mcp-server describe themselves as MCP servers.
Scope. Counts as served on 3 November 2026, for a window close to but not certainly identical to the study’s last month. The API returned no count for some candidates.Back to the essay