
Product
PHP and Composer Support Is Now in Beta
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.
@budgetary/mcp
Advanced tools
Model Context Protocol server for Budgetary: a portable pre-flight token-spend estimate tool for any MCP-capable host.
A single Model Context Protocol server that gives any MCP-capable host — Claude Code, Cursor, GitHub Copilot, Codex, and others — a pre-flight, probabilistic token-spend estimate for a coding task before you run it, and a best-effort, never-fabricated way to record what the task actually cost. Build it once; add it everywhere. It replaces the previously-planned per-host extensions.
The server exposes exactly one model-invokable tool, estimate. It talks to the hosted Budgetary API at https://api.budgetary.tools.
Pass your key as BUDGETARY_API_KEY and tag the host with BUDGETARY_HOST so ledger entries distinguish where the estimate came from. Always include -y with npx so the launch never blocks on an install prompt.
claude mcp add budgetary \
--env BUDGETARY_API_KEY=bg_test_... \
--env BUDGETARY_HOST=claude-code \
-- npx -y @budgetary/mcp
Automatic actuals need the plugin, not just this command.
claude mcp addwires the estimate tool only. The session-end hook that submits real actuals is wired by the bundled Claude Code plugin (via its manifest), so with a bareclaude mcp addyou get estimates but record actuals manually, the same as any other host. Install the plugin for the automatic loop — or add the hook yourself (below).You do not have to have read this to find out:
npx @budgetary/mcp doctorreports whether an automatic session-end submission has ever run on this machine, and prints the exact hook to add if none has. On an install taggedBUDGETARY_HOST=claude-code(as the command above does), the server also says so once, on its first estimate. Neither ever writes to your Claude Code configuration — nothing in this package reads or edits it.
To wire the hook without the plugin, add this to ~/.claude/settings.json yourself:
{
"hooks": {
"SessionEnd": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "npx -y @budgetary/mcp on-session-end",
"timeout": 30
}
]
}
]
}
}
Put your key in ~/.budgetary/config.json (see API key setup) rather than interpolating it into the command, so it stays out of the process list.
The hook proves itself on use: after your next session ends, npx @budgetary/mcp doctor shows that run under Last auto:. Until then doctor still reports that no automatic submission has been recorded — that is expected, not a sign the edit failed. Nothing in this package reads your Claude Code configuration, so it cannot confirm the hook any earlier.
.cursor/mcp.json (project) or ~/.cursor/mcp.json (global){
"mcpServers": {
"budgetary": {
"command": "npx",
"args": ["-y", "@budgetary/mcp"],
"env": {
"BUDGETARY_API_KEY": "bg_test_...",
"BUDGETARY_HOST": "cursor"
}
}
}
}
.vscode/mcp.jsonVS Code uses the servers key (not mcpServers) and can prompt for secrets via inputs:
{
"inputs": [
{ "id": "budgetary-key", "type": "promptString", "description": "Budgetary API key", "password": true }
],
"servers": {
"budgetary": {
"command": "npx",
"args": ["-y", "@budgetary/mcp"],
"env": {
"BUDGETARY_API_KEY": "${input:budgetary-key}",
"BUDGETARY_HOST": "copilot"
}
}
}
}
codex mcp add budgetary \
--env BUDGETARY_API_KEY=bg_test_... \
--env BUDGETARY_HOST=codex \
-- npx -y @budgetary/mcp
…or in ~/.codex/config.toml:
[mcp_servers.budgetary]
command = "npx"
args = ["-y", "@budgetary/mcp"]
[mcp_servers.budgetary.env]
BUDGETARY_API_KEY = "bg_test_..."
BUDGETARY_HOST = "codex"
This server is listed in the MCP registry as io.github.thriftell/budgetary. A host that installs from the listing prompts you for the fields the listing declares — BUDGETARY_API_KEY, which is required, and BUDGETARY_HOST, which is optional.
Set BUDGETARY_HOST anyway. It is what tags your estimates with the host they came from, and on claude-code it is what lets the server tell you, once, when nothing here is submitting your finished runs. Left blank, the host is recorded as mcp and that notice never appears — the same as any hand-written config that omits it. If the listing your client reads is an older one that offers no such field, add it to that server's entry in your host's own MCP config, exactly as in the sections above.
The listing also offers a remote endpoint,
https://api.budgetary.tools/mcp. It estimates only. There is no local process on that path — no pending store, no session-end hook, no transcript to read — so nothing there can measure what a run actually cost, and the endpoint deliberately has no tool that would accept a count, because it would have to be told one rather than measure it. An estimate made through it is never closed out by an actual, by any route. Install the npm package above if you want your runs to count.
The server resolves the key in this order:
BUDGETARY_API_KEY in the server's environment (set via the host config above).~/.budgetary/config.json → { "api_key": "bg_...", "base_url"? }.If neither is set, the estimate tool returns short configure-your-key guidance instead of an error — it never throws and never crashes the host.
mkdir -p ~/.budgetary
# Write the key with an EDITOR so it never lands in your shell history:
"${EDITOR:-nano}" ~/.budgetary/config.json # add: { "api_key": "bg_test_..." }
chmod 600 ~/.budgetary/config.json # owner-only
Prefer an editor over
echo '{...}' > config.json, which records the secret in your shell history, andchmod 600the file so it isn't world-readable.
Key prefixes denote the environment:
bg_test_ — the free testing tier. Works immediately for development.bg_live_ — production. A live key must be on an active plan; if it isn't, the API returns 403 and the tool says "Your Budgetary key isn't on an active plan." (This is distinct from a 401, which means the key itself was rejected.)The API key never appears in a tool result, in pending.json, or in any log line.
You can optionally tag each estimate with the language you're working in, so your estimate history is grouped by language. Set it in the server's environment:
--env BUDGETARY_LANGUAGE=TypeScript
or add a language field to ~/.budgetary/config.json (the environment variable wins if both are set):
{ "api_key": "bg_test_...", "language": "TypeScript" }
It's a free-form display name — TypeScript, Python, Go, and so on — that the server tidies up; you don't need an exact spelling. Like BUDGETARY_HOST, it is a benign tag you declare in the environment: the language model never sets it and it is never guessed from your task description. There is intentionally no language argument on the estimate tool. If you set nothing, the estimate is simply recorded without a language — it's never required, and it never changes the estimate itself.
A plain stdio MCP server only sees the messages your host sends it, not which file you have open, so this declared value (one per host/session) is the signal it can rely on. Hosts that expose no language at all just record the estimate without one.
BUDGETARY_SOURCE — an operator's label for a batch of runsOrdinary users have nothing to set here, and nothing changes if you don't. This exists for operators who drive this client from an automated harness (a benchmark, a load test, a scripted evaluation) and want those runs labelled so they can tell them apart from real ones afterwards.
Set it in the environment of the process you launch for that batch, so its lifetime is the batch:
BUDGETARY_SOURCE=my-harness-run <the command your harness runs>
Do not put this one in your MCP host config. Unlike
BUDGETARY_HOSTandBUDGETARY_LANGUAGE, this label should not go inclaude mcp add --env,~/.claude.json,.mcp.json, or~/.budgetary/config.json. Those are machine-wide and permanent: a label you set there for one batch silently outlives it, and every ordinary session on that machine is labelled with it afterwards — undetectably, because the rows still look perfectly normal.~/.budgetary/config.jsonis not even read for this variable, on purpose. A label that describes a run should not outlive the run.
Each actuals submission carries this label. It defaults to mcp_client. It is an opaque string — the client attaches no meaning to it, validates only its shape (up to 64 characters of A–Z a–z 0–9 . _ -), and ignores anything malformed, falling back to the default rather than failing your submission. (Run with BUDGETARY_DEBUG=1 to have it say so on stderr when it rejects a label; otherwise a typo is silent.)
Two things it is deliberately not:
BUDGETARY_HOST and BUDGETARY_LANGUAGE, it is declared in the environment. There is intentionally no source argument on the estimate tool.The label is resolved once, when the estimate is made, and stored on that estimate's pending entry — so if a submission has to be retried later (from a different session, with a different environment), it still reports the label of the run that actually happened.
A pre-flight estimate is only half the loop; calibration needs the realized token counts after the run. How those are recorded depends on what the host exposes:
Claude Code (with the plugin) — automatic. This host writes a real session transcript. The plugin's session-end hook reads the true tokens_in + tokens_out (cache-read tokens excluded) and submits them — together with a short behavior trace: which tools the run used (Read, Edit, Bash, …), roughly how many tokens each, and two raw measurements per step — which command it ran (a redacted descriptor: a common program name such as pytest or go test, plus a salted, non-reversible digest of the rest — never a raw path, argument, or command) and whether it succeeded. The trace is measured from the transcript, never model-supplied, and any field that can't be read reliably is simply omitted; the total still submits. No human action needed.
Claude Code (bare claude mcp add, no plugin) — manual, from the transcript. Without the plugin there is no session-end hook, so nothing submits for you — but Claude Code writes the same real transcript either way, so you never have to type counts. After a session ends, submit its measured totals (and the same behavior trace) with:
npx @budgetary/mcp on-session-end --transcript ~/.claude/projects/<project>/<session-id>.jsonl
Run it from the directory you estimated in, and name a finished session — a transcript still being written is incomplete, and the first submission for an estimate is the one that counts. Add --success or --failed to declare how the run turned out — with neither, the outcome is left unreported (see below). Better still, wire the hook above and stop doing this by hand.
Codex — manual, from the rollout. Codex ships no session-end hook, but it writes a rollout transcript. After a session, submit its real counts:
npx @budgetary/mcp on-session-end --transcript ~/.codex/sessions/rollout-<ts>-<uuid>.jsonl
Run it from the directory you estimated in.
Declaring the outcome is optional, and there is no default. --success / --failed say whether the run met its objective, and they are forwarded verbatim — but only a caller that actually checked has anything to declare, so with neither flag the field is omitted and the server records the outcome as not observed. That is a third state: it does not mean the run failed, and it does not mean it succeeded. Don't guess in either direction — a wrong verdict is permanent (only the first submission for an estimate is stored, and there is no update call), while the token counts you did measure submit either way.
A harness that spawned the session itself — and therefore observed how the run ended — can declare that too, with --censoring <category>: exactly one of natural, harness_watchdog, operative_cap, kill_switch (the API contract's closed vocabulary). The value is matched exactly and forwarded verbatim; anything else is omitted from the submission — never normalized into a category, and never an error. Don't pass it for a run whose ending you didn't observe: an absent field honestly records "unknown", while a guessed natural is a false claim the server has no way to detect.
Cursor / Copilot / other hosts — manual. These hosts do not hand a third-party server the token totals of a completed agent run, and the language model does not know them either. So you record them yourself when you have a moment:
npx @budgetary/mcp report-actual
It shows this project's most recent pending estimate and prompts you for the input/output token counts (read them from your host's usage UI, grouped numbers like 48,000 are fine), whether the task succeeded, an optional duration, and — if you observed it — how the run ended (a cap, a watchdog, a deliberate abort, or on its own). Pressing Enter on that last question records nothing for it: "not sure" is a first-class answer, and an unobserved ending is never turned into a value.
To see which estimates still await actuals at any time (read-only, no server call):
npx @budgetary/mcp pending
The model never supplies token counts. The only model-invokable tool is
estimate. Actuals are submitted only from (a) the session-end hook reading a real transcript, or (b) the human-enteredreport-actualcommand. A fabricated actual would poison calibration, so there is deliberately no tool a model can call to write counts.
Recording a run returns a measured breakdown of where its tokens went — exploration, generation, testing, retries, other, each with its share of the measured total — and the API's answer to "was that normal for a task like this?". Both are computed and reported by the API and printed exactly as received; where it reports none, nothing is printed rather than a guess. The breakdown is a measurement of that run's own counts, so it is exact and needs nothing to compare it against; the verdict may honestly be insufficient_data, which is that question answered truthfully — not an error.
Where it appears depends on how the run was submitted:
report-actual / on-session-end --transcript — printed in your terminal, directly below the submit confirmation.estimate, stamped with the id of the run it measures. Shown once, never repeated.For the predicted-vs-actual calibration dashboard, install Budgetary (budgetary.budgetary-vscode) from Open VSX — it runs in Cursor and other VS Code forks unchanged. This server does not re-implement it.
Only these things leave your machine, and only to https://api.budgetary.tools:
estimate.TypeScript) — a benign label, the same kind of thing as the host name. Never sent unless you opt in via BUDGETARY_LANGUAGE or the config language field.tokens_in, tokens_out), a success flag, and a duration.mcp_client unless an operator overrode it with BUDGETARY_SOURCE (see above). It says which client sent the row and nothing else: it is a fixed string, derived from no part of you, your machine, or your task.Read, Bash), its token count, a redacted descriptor of what it acted on, and whether it succeeded. The descriptor exposes a program name in the clear only when it is a common, non-sensitive tool (e.g. pytest, npm run) — a pasted credential or a private script name is never shown, only its salted digest; everything after the program (paths, arguments, the rest of the command) always lives inside the digest, or a bare path digest for a file tool. Custom/internal tool names (e.g. an org's private MCP tool) are reported generically as mcp:other, never verbatim. No file contents, absolute paths, command arguments, or output ever leave the machine — only an allowlisted program name and an opaque key. Set BUDGETARY_TRACE_TARGET=off to drop the descriptor entirely (the trace falls back to tool names + token counts); any value other than an explicit 1/true/on/yes is treated as off.Nothing else is transmitted. Both the descriptor's digest and the project_id attached to each estimate are salted, non-reversible hashes (HMAC-SHA256): the descriptor digest with a fresh per-submission salt, and project_id with a machine-local install salt persisted at ~/.budgetary/install-salt. The salts never leave the machine, so the server gets a stable key it cannot reverse back to a command or path. The pending store lives at ~/.budgetary/pending.json, shared byte-for-byte with the first-party Claude Code and Codex clients, so configuring once covers every host. The measured summary the API returns for a submitted run is buffered locally at ~/.budgetary/measured.json until an estimate shows it — at most a handful of recent records, dropped after a week; it is a copy of a response you already received, and nothing in it is ever sent anywhere.
For the v1 API contract — endpoints, error codes, scenario labels — see docs/api-contract.md.
Licensed under Apache-2.0.
FAQs
Model Context Protocol server for Budgetary: a portable pre-flight token-spend estimate tool for any MCP-capable host.
The npm package @budgetary/mcp receives a total of 106 weekly downloads. As such, @budgetary/mcp popularity was classified as not popular.
We found that @budgetary/mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Product
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.

Product
Socket is bringing experimental protection to Firefox, scanning 97,000+ extensions in Mozilla's official directory for malware and risky updates.

Research
/Security News
Three compromised Rust crates pulled in a malicious dependency that downloaded and executed cross-platform malware during Cargo builds.