Skip to content
Lugano Labs Discuss a project

What access comes with an AI tool?

The README is what you’re told. The code is what you install. For 50 MCP servers, the add-ons that give AI apps tools, we checked the code against the README and package.json.

Lugano LabsEditor: Brian D. EvansStatic source study7 min read

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.

What each package’s code names, beside what its README says
PackageWhat its code namesWhat its README saysHost not in READMEAnalytics or telemetry marker
Hosts other than analytics
@upstash/context7-mcpMicrosoft Entra signing keys, for checking tokensDoesn’t mention EntraYesNo
hostinger-api-mcpapi.ipify.org, in a bundled skillDoesn’t mention the hostYesNo
@sentry/mcp-serverDefault endpoints from a bundled AI SDKNo README. We checked its package.jsonYesNo
comfyui-mcpModel providersDoesn’t mention those providersYesNo
Analytics hosts (three of the seven)
nx-mcpGoogle Analytics’ Measurement Protocol, with fields including user_id, editor and operating systemA disableTelemetry setting, but not the host or the fieldsYesYes
figma-developer-mcpA PostHog host and project keySays nothing about itYesYes
exa-mcp-serverAn Agnost analytics SDK callSays nothing about itYesYes
Telemetry the README documents
n8n-mcpA first-party telemetry endpointDocuments itNoYes
@alisaitteke/photoshop-mcpA Rybbit analytics hostDocuments it, with an opt-outNoYes

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/mcp had 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-mcp and @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]

The five markers this essay uses
MarkerWhat counts
URLA URL outside comments
Analytics or telemetry hostA known analytics or telemetry host
Process startA child_process import, or an execSync, spawnSync or execFileSync call
Home-directory lookupCode that looks up the user’s home directory
Secret-named variableCode 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.

Discuss this question

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. 1

    Static source observations from 50 sampled MCP server packages

    Lugano Labs · 2026

    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. 2

    The 50-package sample (packages.txt)

    Lugano Labs · 2026

    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. 3

    The static scanner (scan.py)

    Lugano Labs · 2026

    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. 4

    Manual review record (review.json)

    Lugano Labs · 2026

    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. 5

    Per-package results (results.json)

    Lugano Labs · 2026

    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. 6

    Cited source locations (evidence.jsonl)

    Lugano Labs · 2026

    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. 7

    Scan summary and reviewed headline counts (summary.json, headline.json)

    Lugano Labs · 2026

    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. 8

    npm downloads API and registry metadata, checked 3 November 2026

    npm, Inc. · 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

About this report. First prepared 20 October 2026; revised 4 November 2026. This study reports static source markers in a defined sample of published packages.

Our theses, sources, interpretation and study designs may change with new evidence and are open to correction. Send a research enquiry.

Download text

Lugano Labs Start a conversation

What would you like to work on?

A short note is enough. Please leave out credentials, secrets and sensitive source material.

What brings you here?

Choose one or more.