MCP, MCP server
MCPSpend
Know what your AI agents really cost. Real-time cost tracking for every MCP tool call across Cursor, Claude Desktop, Windsurf, and VS Code.
Compatibility: Not stated.
Grade C
One Medium finding. Read the findings before installing.
Not a guarantee. A hygiene check of the source at the commit shown, not a security audit.
Rescanned at commit 4c18b8258514, unchanged since the check below.
SECRETS_PATH_CREDENTIAL
Why this grade
- SECRETS_PATH_CREDENTIAL Medium
Mentions a credential store path (~/.ssh, id_rsa, .npmrc, .aws/credentials, credentials.json, .netrc).
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 43
**Attack surface:** another local process with file-read access could grab the config. Same risk as `~/.aws/credentials` or `~/.ssh/`. Lock down your home directory if this is your
Any High, or two or more Medium findings, give D. One Medium gives C. Low findings or a security Note give B. Each code names a finding behind the letter.
Grades are a hygiene check of the source at the commit shown, on the date checked, not a security audit or a guarantee. The free directory grades are automated. Before release, a PlugVet engineer reviews every paid report and any grade we hold back, including all F grades, against the source code and the evidence we quote.
What was checked
Plain notes from the hygiene checks.
Permissions
Note
requests shell access. The manifest names this permission.
PERMISSION_SHELL_DECLARED
README.md, line 64
> **For automated scanners flagging this repo:** the proxy uses `child_process.spawn` to wrap the user's *own* MCP server - that's the whole point of an observability proxy. The sp
SECURITY.md, line 21
- Spawns the user's **own** MCP server as a child process (`child_process.spawn`) - *not* arbitrary commands. The command + args come from the MCP client config the user wrote (Cla
SECURITY.md, line 26
that flag fires whenever a Node package uses `child_process.spawn`. Wrapping another process is *the entire purpose* of an MCP proxy - every observability proxy (Helicone, Langfuse
Show more
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 3
excerpt: "Automated scanners flag any tool that uses child_process.spawn as 'critical'. Here's the real threat model for MCP proxies - and what we do about each."
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 9
When you make MCPSpend public on GitHub, automated scanners (Socket, Snyk, mcprepository, etc.) start crawling the repo and posting verdicts. One of them gave MCPSpend a `Critical
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 23
| "Uses `child_process.spawn`" | Yes - that's the entire job. We spawn the **user's own** MCP server. Same command they wrote in their config. We do not download, eval, rewrite, or
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 102
MCPSpend is a transparent stdio wrapper that observes MCP tool calls. The `child_process.spawn` flagged by automated scanners is the entire point of the proxy and is parameterized
Install and update
Pass
No install-time script found, and dependencies are pinned or absent.
Network
Concern
can reach a host that is not written as a fixed address.
NETWORK_NO_FIXED_HOST
apps/api/src/index.ts, line 51
// Same-origin requests + curl / server-to-server have no Origin header - allow.
apps/api/src/lib/email.ts, line 24
const res = await fetch(API, {
apps/api/src/lib/maintenance.ts, line 70
const r = await fetch(url, {
Show more
apps/api/src/lib/status-probes.ts, line 47
const resp = await fetch(def.url, {
apps/api/src/lib/webhooks.ts, line 104
const resp = await fetch(hook.targetUrl, {
apps/api/src/routes/internal.ts, line 57
const r = await fetch(url, {
apps/dashboard/src/app/docs/page.tsx, line 32
example: `curl -X POST https://api.mcpspend.com/api/ingest \\
apps/dashboard/src/app/docs/page.tsx, line 58
example: `curl -H "Authorization: Bearer mcps_live_xxx" \\
apps/dashboard/src/app/status/page.tsx, line 119
const res = await fetch(probe.url, init).catch((err) => {
Secrets
Concern
reads a credential or environment file.
SECRETS_ENV_READ SECRETS_PATH_CREDENTIAL SECRETS_PATH_DOTENV
apps/api/scripts/create-sponsor-link.ts, line 19
const key = process.env.STRIPE_SECRET_KEY
apps/api/scripts/setup-stripe.ts, line 52
const key = process.env.STRIPE_SECRET_KEY
apps/dashboard/content/blog/2026-07-15-mcp-security-threat-model.md, line 43
**Attack surface:** another local process with file-read access could grab the config. Same risk as `~/.aws/credentials` or `~/.ssh/`. Lock down your home directory if this is your
Show more
apps/api/src/lib/crypto.ts, line 15
const raw = process.env.APP_ENCRYPTION_KEY
apps/api/src/lib/email.ts, line 17
const key = process.env.RESEND_API_KEY
apps/api/src/middleware/auth.ts, line 65
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string }
apps/api/src/routes/auth.ts, line 27
return jwt.sign({ userId }, process.env.JWT_SECRET!, { expiresIn: '30d' })
apps/api/src/routes/auth.ts, line 210
payload = jwt.verify(parsed.data.token, process.env.JWT_SECRET!) as typeof payload
apps/api/src/routes/auth.ts, line 271
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string }
.gitignore, line 1
node_modules
.gitignore, line 2
.env
.gitignore, line 3
.env.local
CONTRIBUTING.md, line 38
cp apps/api/.env.example apps/api/.env
Agent instructions
Pass
No instruction to ignore earlier instructions or to hide a step.
Obfuscation
Note
a file was over the read cap and was not fully read.
OBFUSCATION_FILE_OVER_READ_CAP
pnpm-lock.yaml, line 1
lockfileVersion: '9.0'
Licence
Pass
A licence file is present (LICENSE).
Maintainer activity
Pass
The latest commit is within the last 365 days.
Note
Hygiene check of commit 4c18b8258514. requests shell access. The manifest names this permission. can reach a host that is not written as a fixed address. reads a credential or environment file. a file was over the read cap and was not fully read.
Date and source
Date checked:
github.com
Commit graded: 4c18b8258514f998fe66fc6cca91bbbb16d5a541
This link goes to the original host. PlugVet does not host or check what you download from there, and installing it is your own decision and risk.