In 2026, financial data terminals started shipping MCP interfaces. Data vendors and open-source wrappers now expose market data, fundamentals, and fund flows as MCP tools that any AI client — Claude Code, Cursor, Copilot — can call.
That is convenient. It also means every one of those MCP servers holds a paid API token in memory: data-vendor tokens, vendor keys, session credentials. Unlike a random weekend project, those tokens carry metered quotas, billing relationships, and often access to non-public data feeds.
We took seven open-source JS/TS finance MCP servers (npm packages and GitHub repositories) and ran our local static scanner, correctover-scan v1.7.2, over them. This post describes the four concrete leak patterns we saw, how often they showed up in published code, and the pre-release checklist that catches all of them. No projects or maintainers are named; findings have been privately disclosed to the affected maintainers.
How we scanned
The scanner runs entirely locally — the source code never leaves your machine:
npx correctover-scan --bundle ./path/to/mcp-server
For npm packages we ran it against npm pack tarballs (the exact build users install); for repositories we scanned the checked-out source. The bundle mode parses .js/.mjs/.cjs/.ts files and runs two layers of checks: a configuration layer (hardcoded secrets, dynamic code execution) and a code layer (plaintext outbound endpoints, credential flow into logs, MCP transport flags). Every machine-generated warning with a file/line was then manually reviewed against the actual source, because some classes of issues — CORS policy and network bind addresses — the scanner only flags indirectly and a human must confirm.
Honest scope note: all seven targets were JS/TS, the scanner's home turf. The Python fastmcp ecosystem (which includes several popular finance MCPs) was not in scope; the scanner does not parse .py files yet, and we did not paper over that gap. Closed-source vendor MCPs had no published server source to scan either.
What the numbers look like
Of the seven targets, five ship as published npm packages. In two of those five published packages (40%) — two distinct projects — we confirmed at least one of the four token-leak patterns below in the exact artifact users install. One project had three of the patterns; another had two. The other three published packages, plus the two repository-only targets, came back clean on manual review.
A note on severity labels: the scanner rates hardcoded credentials as fail, while plaintext HTTP, logging, CORS, and bind issues are rated warn. That understates them — a token sent over plaintext HTTP is a token disclosure, full stop. We went with manual review, not the score.
One detail worth flagging for anyone maintaining an MCP: for one project, both of its issues were already fixed on the repository's main branch — but the fixes had never been published to npm. The code your users npx can lag your own HEAD for months.
Pattern 1: The token goes out over plaintext HTTP
The data vendor's endpoint was hardcoded as an http:// URL, and the token rides along in the POST body:
// api client in a finance MCP server (anonymized)
export class DataClient {
token: string;
private apiUrl = "http://api.marketdata.example";
constructor(token?: string) {
this.token = token ?? process.env.DATA_TOKEN ?? "";
}
async call(apiName: string, params: Record<string, unknown>) {
const response = await fetch(this.apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
api_name: apiName,
token: this.token,
params,
}),
});
// ...
}
}
We saw this in 2 of the 5 published packages (40%) — in one case across three separate tool modules, each with its own hardcoded http:// fetch.
Risk: anyone positioned on the same network path — coffee-shop Wi-Fi, a shared office VLAN, a compromised hop — reads the bearer token straight off the wire. The token is a paid, quota-bearing credential.
Fix: use https://. The vendor endpoint supported HTTPS in every case we saw; this was a copy-pasted URL scheme, not an infrastructure constraint. Put the base URL in one config constant so it cannot drift per-file.
Pattern 2: The token gets logged to stdout
In stdio MCP mode, stdout is the protocol channel — it is captured by the MCP client's log files. One published package logged the entire request object, token included, right before sending it:
// tool module in a published package (anonymized)
const apiParams = {
api_name: "balancesheet",
token: CONFIG.API_TOKEN,
params: { code: code },
};
console.log("API request params:", apiParams);
const response = await fetch(CONFIG.API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(apiParams),
});
1 of the 5 published packages (20%) did this, in three tool modules.
Risk: the token lands in MCP client log files on the developer's disk, in CI logs if the server runs there, and in anything that scrapes the host's stdout. It is the same credential, now duplicated into log storage with different retention and access rules.
Fix: redact before you log. Log only the business parameters (params.params), or pass the payload through a small redactor that masks known credential fields:
const redactToken = (p) => ({ ...p, token: p.token ? "***" : undefined });
console.log("API request params:", redactToken(apiParams));
Pattern 3: Access-Control-Allow-Origin: * on a streamable-http transport
When an MCP server offers Streamable HTTP, it is a web server — and the browser same-origin rules apply to it. One published package enabled CORS with a wildcard and explicitly allowed token-bearing custom headers:
// http transport setup (anonymized)
const app = express();
app.use(
cors({
origin: "*",
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"Mcp-Session-Id",
"X-Api-Token",
],
})
);
1 of the 5 published packages (20%) had this.
Risk: any web page the operator visits can issue a cross-origin POST to http://localhost:3000/mcp and invoke the server's tools. In the package we saw, the server fell back to reading the token from request headers when no server-side token was set — meaning a page could potentially ride the server's own credential. CORS is not optional configuration here; it is the authentication boundary between the browser and a local privileged service.
Fix: an MCP HTTP endpoint meant for local clients should not use origin: "*". Restrict allowed origins to an explicit allowlist (or omit wildcard CORS entirely and document the local proxy setup), and never accept a credential from a request header you did not explicitly opt into.
Pattern 4: The server binds to all interfaces with no Host allowlist
The companion mistake. The same package started its listener without a host argument:
// default in Node/Express: binds 0.0.0.0
app.listen(PORT, () => {
console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
});
1 of the 5 published packages (20%). The startup message says localhost; the socket tells a different story — omitting the host binds 0.0.0.0, reachable from the entire LAN.
Risk: combined with pattern 3 (and no token gate), anyone on the local network can reach and invoke the MCP server — a paid data quota sitting on a laptop port. For comparison, the sibling project from the same maintainer had already done it correctly in its newer code: app.listen(PORT, "127.0.0.1") plus an ALLOWED_HOSTS Host-header check.
Fix: bind explicitly:
const HOST = process.env.HOST ?? "127.0.0.1";
app.listen(PORT, HOST, () => { /* ... */ });
If a deployment genuinely needs a non-loopback bind, require an explicit env var to opt in and add a Host-header allowlist.
A pre-release checklist for MCP authors
Everything above is caught before publish with five checks:
-
Grep your built artifacts for
http://— run the scanner against thenpm packtarball, not just the repo source. The build is what users run. -
Audit every
console.log/loggercall on a request or response path — no credential field should reach stdout/stderr in stdio mode. Add aredacthelper and route payload logs through it. -
If you offer Streamable HTTP, bind
127.0.0.1by default and require explicit configuration to expose anything wider. - No wildcard CORS on a transport that carries credentials — explicit origin allowlist, and never fall back to trusting client-supplied token headers.
-
Publish the fixes — main-branch patches do not protect users on the npm
latesttag. Make the release part of the fix.
Wire this into a prepublishOnly script and a CI step; the patterns are cheap to detect automatically and expensive to explain after the fact.
Try it on your own MCP
- CLI (local static scan, code never leaves your machine):
npx correctover-scan - Zero-install browser version: https://dshcorrectover.github.io/agent-audit/scan.html
- Project organization: https://github.com/DSHCorrectover
Disclaimer
Results are based on static analysis of publicly available package versions and repositories at scan time (2026-09-04). They are not a security rating or certification of any individual project; absence of findings in this scan is not a guarantee of safety, and findings reflect the versions we downloaded. The Python fastmcp ecosystem and closed-source vendor MCPs were outside this round's scope. All confirmed findings have been privately disclosed to the relevant maintainers.
Top comments (0)