A transparent MCP proxy that re-encodes tool-call
results as TOON, a compact format that costs a model fewer
tokens to read than the equivalent JSON. You point your MCP client at toonfmt instead
of the real server, and toonfmt forwards every message untouched except the result of
tools/call, which it converts to TOON on the way back.
Your client speaks stdio to toonfmt. The upstream server can be either a stdio
child process or a Streamable HTTP MCP server, with bearer-token or OAuth auth.
Prebuilt binaries for macOS (Apple Silicon and Intel), Linux x86-64, and Windows x86-64.
The installer drops a single toonfmt binary on your PATH.
# macOS / Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/jesse-smith/toonfmt/releases/latest/download/toonfmt-installer.sh | sh# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/jesse-smith/toonfmt/releases/latest/download/toonfmt-installer.ps1 | iex"Or build from source with a Rust toolchain:
cargo install --git https://github.com/jesse-smith/toonfmtThere is no runtime to install: the HTTP client is compiled in, so HTTP upstreams need no
Node or mcp-remote sidecar.
If you installed via the shell/PowerShell installer above, upgrade in place:
toonfmt updateIt checks GitHub Releases and re-runs the installer only if a newer version exists;
otherwise it reports you're already current. If you installed another way — cargo install, or from source — toonfmt update says so and exits without changing anything;
update the way you installed (e.g. re-run cargo install).
toonfmt --help prints the full flag reference and toonfmt --version prints the version
— the quickest way to see every option.
Declare toonfmt as the command in your client's MCP config (.mcp.json for Claude
Code). Pick the recipe that matches your upstream.
Prefix the real server command with --. toonfmt spawns it and pumps JSON-RPC over
its pipes.
--http <url> connects over Streamable HTTP. --bearer-env VAR names the environment
variable holding the token, so the secret never lands on the process argument list. An
unset or empty variable fails fast at startup rather than sending an unauthenticated
request.
{
"mcpServers": {
"remote-sql": {
"command": "toonfmt",
"args": ["--http", "https://host/mcp", "--bearer-env", "REMOTE_SQL_TOKEN"]
}
}
}For servers that require an OAuth 2.1 authorization-code grant, log in once by hand:
toonfmt login --http https://host/mcpThis opens your browser to the consent page, completes the token exchange, and saves the
credentials under ~/.toonfmt-auth/. Then serve with --oauth, which loads the cached
token and lets the proxy refresh it. The serve path never opens a browser, so it never
blocks your client's startup on a human.
{
"mcpServers": {
"oauth-svc": {
"command": "toonfmt",
"args": ["--http", "https://host/mcp", "--oauth"]
}
}
}The --http URL must byte-match the URL you passed to login: the credential store is
keyed by the exact string. If you use --profile (below), the profile must match too —
the store is keyed by URL and profile together.
By default, all logins to one URL share a single token file — keyed by the URL alone. Add
--profile <name> to give a URL multiple independent identities that don't overwrite
each other. The same --profile must appear on both login and serve.
toonfmt login --http https://host/mcp --profile work
toonfmt login --http https://host/mcp --profile personalTwo common uses:
- Same URL, different accounts —
--profile workvs.--profile personalkeep their tokens in separate files. - Project-scoped tokens — pass
--profile ${CLAUDE_PROJECT_DIR}in a project-scoped.mcp.json(Claude Code expands the variable). Each project then keys its own token while a user-scoped server (no--profile) keeps sharing one. The path only feeds the store key (it is hashed) — credentials always live under~/.toonfmt-auth/, never in your repo.
{
"mcpServers": {
"oauth-svc": {
"command": "toonfmt",
"args": ["--http", "https://host/mcp", "--oauth", "--profile", "${CLAUDE_PROJECT_DIR}"]
}
}
}--profile is meaningful only for OAuth upstreams (--oauth / --oauth-interactive); it
is rejected on a bearer-token, no-auth, or stdio upstream rather than silently ignored.
--oauth-interactive skips the separate login. On a missing token it runs the
authorization-code flow inline on first serve, opening the browser before initialize.
A present token reuses the --oauth path with no browser. This can block your client's
startup on the consent page, so it is opt-in.
{
"mcpServers": {
"oauth-svc-interactive": {
"command": "toonfmt",
"args": ["--http", "https://host/mcp", "--oauth-interactive"]
}
}
}See how many bytes the TOON transform saves. It is off by default — the proxy stays a
silent, zero-overhead passthrough until you opt in, and nothing is ever written to disk
otherwise. Turn it on with the --stats serve flag (or set TOONFMT_STATS=1):
{
"mcpServers": {
"sql": {
"command": "toonfmt",
"args": ["--stats", "--", "uvx", "some-sql-mcp", "--db", "..."]
}
}
}While enabled, each converted tools/call result records its byte delta to a small
SQLite store at ~/.toonfmt/stats.db. Read the cumulative, per-project total any time:
toonfmt statstoonfmt — token-savings stats (bytes of JSON the model didn't read)
/home/you/projects/app 128 results 142.6 KB → saved 84.1 KB ( 59.0%)
/home/you/projects/api 14 results 12.0 KB → saved -390 B ( -3.2%) (2 grew)
TOTAL 142 results 154.6 KB → saved 83.7 KB ( 54.1%) (2 grew)
The figures are exact bytes and a bytes-based percentage — never a token count
(Anthropic's tokenizer isn't public, so any token number would be fabricated). Only
savings the model actually reads are counted, deltas are signed (a block TOON makes
larger counts against you, and is flagged as "grew"), and toonfmt stats run before you
ever enable stats just prints a friendly "no stats recorded yet" — it never creates the
store. See ARCHITECTURE.md for the full rationale.
toonfmt matches tools/call requests to their responses by JSON-RPC id and rewrites
only those results. For each text block in a result it parses the JSON (strict, then
JSON5 for trailing commas and single quotes), re-encodes it as TOON, and writes it back
to the same block. Blocks it cannot parse pass through unchanged, so one odd block never
breaks a multi-block result. When a result carries both a TOON-converted block and a
duplicate structuredContent, the proxy drops the redundant copy only after checking
that the two hold structurally equal data. Everything that is not a tools/call result
(initialize, tools/list, resources, prompts, notifications) forwards as-is.
See ARCHITECTURE.md for the full design: the structuredContent
shadowing rule, the equality-gated strip, the two transport legs, and the OAuth
lifecycle.
MIT. See LICENSE.
{ "mcpServers": { "sql": { "command": "toonfmt", "args": ["--", "uvx", "some-sql-mcp", "--db", "..."] } } }