[0.8.23] - 2026-10-19
CLI papercuts and data-safety fixes reported on 2026-10-17, plus a presence channel so
a user who installed mcptoon and forgot can tell it is being used, by which agent, and
when. Each item was reproduced before fixing and pinned by a test. Held for the next
version per the one-release-per-topic rule.
Added
mcptoon searchprints a copyablecallline for every hit. It used to stop
atparams: message:string*, leaving the user to assemble the JSON arguments and
to spot the*that marks a required one. Each result now ends with a
ready-to-edit→ mcptoon call <server> <tool> '{"required":"..."}'built from the
tool's required arguments. Human output only;--jsonstays pure data.mcptoon callexplains how to fix malformed JSON instead of only quoting the
parser. A bad argument object printed the rawJSONDecodeErrorand nothing else.
It now follows with the correct invocation (required args filled in from the tool
schema when available, else a generic skeleton) and themcptoon inspectpointer.mcptoon completion <shell> --install/--uninstall.completiononly
printed a script and told the user to paste it into the right rc file.--install
writes it into that file inside# >>> mcptoon completion >>>markers, idempotently
(a re-run updates in place and reports "already installed");--uninstallremoves
exactly that block and leaves the user's own lines untouched. Printing the script
(no flag) is unchanged, soeval "$(mcptoon completion bash)"still works.mcptoon inspect <server> <tool>prints a copyablecallline. It printed the
schema and stopped; the reader still had to turnrequired/propertiesinto an
argument object. It now adds the ready-to-edit→ mcptoon call …line to stderr,
soinspect … > schema.jsonand every machine format stay pure data.mcptoon selectprints acallline for its top pick, next to the existing
Inspect one:hint — same shape assearch.mcptoon search --jsoncarries acall_hintfield per hit, so an agent driving
the CLI reaches the call as data, not just a human reading a terminal. An extra key;
existing consumers are unaffected.mcptoon completion … --install --drypreviews the write. Prints the target rc
file and the exact block that would be written, changes nothing.mcptoon callwarns about a missing required argument before it runs. When the
tool's schema is cached,call echo add '{"a":1}'now says
Warning: missing required argument(s) for add: band points atinspect, instead
of surfacing a downstreamKeyError/server validation text. Cache-only, so acall
never spawns a server just to pre-check.mcptoon policy setnames the scope in plain words — "every tool on 'server'
(current and future)" for the*wildcard, "only 'server:tool'" otherwise.mcptoon toggleprints the command that reverses it ("Re-enable it: …" /
"Disable it again: …"), so the undo is stated rather than remembered.mcptoon stats/mcptoon reportsay--headdoes not apply when it is given:
both are aggregates (sums over all servers), so a cap cannot shrink them without
making the totals lie. The note appears only when a cap was actually requested.mcptoon config get <near-miss>suggests the closest key ("did you mean:
footer?"), using the same matcher as the command/option typo hints.mcptoon activity— a readable ledger of what mcptoon actually did.usage
answers "how many, which servers" from a capped counter that cannot say which
agent or when. Every routed call now also appends one line toactivity.jsonl
(time, host,server:tool, ok, tokens);mcptoon activityshows the last 20
newest-first,--summarygives totals (today, last host, by host),--jsonis
machine-readable,--clearwipes the ledger (theusagecounters are separate
and untouched). Append-only and capped at 2000 lines, so it never grows without
bound.- Host identity — mcptoon records which agent is using it.
servereads the
client's self-reported name from the MCPinitializehandshake and stores it, so
the ledger andstatussay "Cursor" or "Claude Desktop" instead of only counting
calls. A client that sends no name leaves the host unknown (null), never guessed. mcptoon statusshows a "Last used" row — who last drove it, when, and how
many calls today, with a pointer tomcptoon activity. On a machine nothing has
routed through yet it says so plainly ("no routed calls yet"), which is itself the
answer a fresh install needs.status --jsoncarries the same fields
(activity_entries,activity_today,last_host,last_used_ts,
presence_enabled).- A one-line presence notice after a command that routed a call. When a command
actually used mcptoon, one short line goes to stderr:▸ mcptoon routed 3 calls for Cursor this command — see \mcptoon activity`. It fires only when something was really routed, somcptoon statusstays silent, and it is stderr-only so--json`/piped output stays pure data. - A
presencesetting (on|brief|off, defaulton), separate fromfooter.
footeris the token-savings broadcast;presenceis the "you were just used"
notice. Someone may want the usage notice without the big savings card, or the
reverse — one switch for two questions forced all-or-nothing.briefkeeps the
notice but drops the host clause. - The gateway's own tools are now in the activity ledger.
mcptoon_resolve_skills,
mcptoon_manifest,mcptoon_inspect,mcptoon_calland friends are answered
in-bridge and never passed through the router, sousage.track_callnever saw
them: a session that only ever distributed skills leftactivityempty — the
single most important "was I used" event was invisible. They are now recorded
under the reserved server namemcptoon, soactivityandstatusshow them
alongside upstream calls. - A one-per-session "distributed N skills" banner. The first time
mcptoon_resolve_skillsactually surfaces skills in a session, the result carries
one line —▸ mcptoon distributed N skills for this task (see \mcptoon activity`)— so the "mcptoon is handing your capabilities to the agent" moment is stated, not left for the user to infer. Gated onpresence` and on a non-empty shortlist; it
never repeats within a session. mcptoon quickstartnames the agents it registered mcptoon into. The install
summary used to say only "your agents can now see mcptoon"; it now lists the hosts
("Cursor, Claude Desktop …") and points atmcptoon activityfor watching it work.mcptoon doctorteaches how to confirm it is working. On a fully healthy run
with the gateway wired in, it now adds one line — let an agent use a tool, then run
mcptoon activity— so a clean bill of health also answers "how would I notice it
doing anything?".- The activity ledger now records which skills were distributed. A
mcptoon_resolve_skillscall used to record only that it ran; it now carries a
detailline naming the skill slugs it handed out (distributed: xiewen-shipin, duanju-juben), andmcptoon activityprints it under the call. "Distributing your
capabilities" is only legible if the ledger says which capabilities. mcptoon usageshows a by-host breakdown, andmcptoon activity --host <name>
filters to one agent.usagecould say which servers were used but never which
agent used them, and the ledger's host column was only filterable by hand-editing
JSON. Both now answer "was it Cursor or Claude just now?" on a multi-agent machine.statusanddoctorwarn when the gateway is wired in but silent. A gateway
registered in an agent that has recorded no call — ever, or for over a day — looks
identical to one that is quietly working. Both surfaces now name that possibility
in one soft warning line (never an error, never a changed exit code), so a broken
wiring is not mistaken for a healthy idle install.mcptoon activity --export json|csvdumps the whole ledger. The on-screen
view is a 20-line tail; an audit or a pasted bug report wants every line in a form
a spreadsheet orjqcan take.--export csvwrites a header plus one row per
call (chronological),--export jsona{count, ledger, entries}envelope. Both
honour--host. The ledger is a local file — export prints it, uploads nothing.- The activity ledger records why a failed call failed. A red line used to say
only that it was red; it now carries anerrfield (the error code, or
Type: messagefor an exception) capped at 200 chars, andmcptoon activity
prints it under the call. Full tracebacks are deliberately not stored — the ledger
stays one line per call. mcptoon statussplits "calls recorded" into routed vs the gateway's own
tools. The single number conflated calls proxied to upstream tools with calls
to mcptoon's own native tools (skill distribution, manifest, inspect). It now
reads "N routed to tools, M via mcptoon's own tools", andstatus --jsoncarries
calls_routed/calls_native— two different things a user deciding whether
mcptoon is doing anything cares to tell apart.- Each activity line records how long the call took, and slow calls are flagged.
The ledger gains anmsfield (wall-clock, whole call as the caller experienced
it);mcptoon activityprints it and marks anything over 3 sSLOW. A call that
hung for eight seconds used to look identical to one that took ten milliseconds. mcptoon activity --followstreams new calls live. Atail -ffor the
ledger: it starts at the current end and prints each new routed call as it
arrives (honours--host), so you can watch an agent drive mcptoon in real time.
Ctrl-C to stop.mcptoon activity --summaryreports the retention cap and the ledger's size.
The ledger silently keeps only the newest 2000 calls; that roll-off was invisible.
The summary now names the cap and the current file size, so a user treating it as
an audit log knows what it actually keeps.mcptoon statusnames the most recent failed call. A separate question from
"is it being used": when a call has failed,statusshows the agent, the tool,
when, and the short reason ("Last failure: Cursor fetch:boom at … (CALL_ERROR: …)").
status --jsoncarries the same aslast_fail.mcptoon activity --since <today|1h|24h|7d|YYYY-MM-DD>and--failures.
--sincenarrows the view (and--export) to a time window, applied before the
--headcap so--failures --head 20means "the last 20 failures";--failures
shows only calls that failed. Both compose with--hostand--export.mcptoon demonow records a line in the activity ledger. The demo drives a
raw MCP client and bypassed the router, so it left the ledger empty — the one
moment a brand-new user wants proof "it really is recording" was the one that
recorded nothing.mcptoon demo && mcptoon activitynow shows the demo's echo
call (server namedemo, clearly not a real upstream call).activity --summaryreports success rate and latency. It now shows
"Success: N/M (P%)" and "Latency: avg X ms · K slow (>3000 ms)", read off the
same window (using themsfield added last round) — "was it reliable?" is no
longer unanswerable from the tail view.mcptoon activity --top [N]ranks by host and by tool. "Which agent leans on
me most, and which capability?" — a counting question the per-line tail cannot
answer. Two bar tables, top N (default 10), honouring--since/--host/
--failures;--jsonreturns the counts.mcptoon config set activity_keep Ntunes the ledger cap. The 2000-line cap
was a fixed constant; a heavy user can now raise or lower it (validated
whole-number range 50–100000, bad values fail loudly). The trimmer reads it live.mcptoon activity --clear --before <when>prunes only old entries. A plain
--clearwiped everything; now you can drop calls older than7d/24h/a date
while keeping recent history. A torn line is kept, never silently eaten.mcptoon activitycolours failed/slow lines on a real terminal. Red for a
failed call, red for aSLOWone — but only on a TTY; a pipe orNO_COLORgets
plain text, so a captured transcript stays clean. The signal was already in the
text ("ERR"/"SLOW"); colour just makes it pop for a human watching--follow.statusandactivity --summaryreport today's skill distributions. The
number ofmcptoon_resolve_skillscalls that actually handed over skills today —
"did it give me a capability today?", distinct from a generic routed call.
status --jsoncarriesactivity_today_skills; summary carriesfirst_ts
("how far back does this history go?").activity --tool <server:tool|tool>and--search <keyword>filter the view.
--toolnarrows to one tool (server:tool or a bare tool name);--searchis a
case-insensitive substring match over host/server/tool/detail/err — "when did
that METHOD_NOT_FOUND happen?" no longer needs reading every line. Both compose
with--since/--host/--failures/--export/--top.activity --export csvnow includes themscolumn. It was in the json
export but missing from csv, so a spreadsheet dropped every call's duration.activity --summaryreports p50/p90 latency, not just the mean. One 8s call
among ninety 20ms calls barely moves the average yet is the one the user
remembers; p50 says what a typical call costs, p90 what a bad one costs
(nearest-rank, no interpolation — the honest reading for a small sample).mcptoon mode— which delivery mode you are in, and which is cheapest. A
fresh install can sit indirect(installed, but the agent still loads every
upstream schema) and save nothing, whilestatusandreporthappily showed
the definition-side "99%".modenames the three states side by side —cli
(shell, zero resident, cheapest),serve(MCP gateway, small fixed per-turn
cost) anddirect(most expensive) — flags the one you are in, and states the
honest headline: the big saving is definition-side (not loading every schema);
result compression is a rounding error (0.1–0.5%). Read-only, cache-only.mcptoon statusline— one savings badge, safe to run every prompt. A
single ≤80-column line (🎉 mcptoon · 9 tools (99% less) · ≈1.9B saved · 2,903 calls · cli) for a status bar or a turn header, so an agent that never runs a
mcptoon command still shows it is there. Reads only cached figures, never
spawns a server.--jsonfor hosts that prefer structured output.statusandreportnow name the current delivery mode.statusgained a
Moderow andreporta⑥ Delivery modesection, so the "99%" can no longer
be read without seeing whether this machine is in a mode that realises it.
Fixed
mcptoon import <path>accepts a bare file path.importonly ever read
--file/--from, so a positional path was silently dropped:import /nonexistent/x.jsonscanned every client and exited 0 as if the file had been
read. One positional is now the file; a conflicting/extra positional errors.mcptoon retrieve --list --jsonreturns structured store stats. It printed
human text regardless of format; scripts could not read count/bytes/ttl.mcptoon select --topwith a non-integer value fails loudly.--top abc
used to fall back silently to the default 3, so a scripted--top $Nwith a bad
value looked like it worked.mcptoon policy clearon a missing policy errors instead of faking a
"Cleared" report. It printed "Cleared: …" and exited 0 even when nothing was
set, somcptoon policy clear s t && echo donelied. A miss now exits 1.mcptoon policy list --jsonreturns the policy map as JSON. It printed human
text regardless of format; scripts could not read it.- A bare
mcptoon retrieveexits 1. It printed usage but exited 0, so
mcptoon retrieve && echo okreported success on a no-op.retrieve --help
still exits 0. - A mistyped command that is close to a real one now errors instead of running
the wrong command.mcptoon listtmatched "list" inside the word and ran
manifest;mcptoon healthhmatched "health" and ran the health check — both
exit 0, so a typo'd command in CI reported success. It now fails with a
did-you-mean hint (exit 1). Genuine natural-language phrases still route. --head/--max-charswith a non-integer value fail loudly.--head abc
used to be swallowed (head_n stayed 0 = unlimited) and printed everything, so a
scripted--head $Nwith a bad value looked like it worked.search/selectzero-match keep machine formats parseable. A query with no
hits printed human text + tips for every format, sosearch q --json | jq(and
select ... --json) crashed on non-JSON. Machine formats now emit an empty
structure; humans still get the tips.mcptoon select --head Ncaps the number of top picks shown. Only the json
branch forwarded--headto the renderer; the human output printed every--top
result regardless.mcptoon doctor --jsonreturns structured diagnostics. It printed human text,
so CI could not parse the result. JSON carrieschecks/issues/ok/
brokenand keeps the exit-code contract (1 on any issue).mcptoon config get/set/unsettrim surrounding whitespace on the key.
config get " footer "used to fail as an unknown key (get lowercased but never
stripped; set never stripped).mcptoon remove a bremoves every named server. It used to delete only the
first and silently ignore the rest; now all are removed, and any missing name
makes the call exit 1.mcptoon listhonours--json/--head/--max-chars.listhad no
access to the global output flags, so--jsonprinted human text and--head
did nothing — a script could not get the server list as data.mcptoon manifest --head Nlimits the number of servers shown. It was ignored
in the human/compact/toon paths (slim already capped tools via the renderer).mcptoon toggle --list --jsonreturns JSON. It printed human text regardless.mcptoon search --head Ncaps the result list in every output mode. Only the
json/toon renderers honoured it; slim/compact/human printed all 20 hits, so
search file --head 5(documented "limit to 5 results") showed 20.mcptoon call --envelopeexits non-zero on a tool error. The envelope result
carriesisError: true; that path printed it and exited 0 while the non-envelope
path already exited 1.mcptoon toggle <server> <unknown-tool>is refused. It used to write a dead
"disabled" entry and exit 0. The tool is checked against the cache when the server
is cached (same offline-only rule aspolicy set).mcptoon callrefuses a JSON argument and--stdintogether.--stdin
silently won, soecho '{...A}' | mcptoon call s t '{...B}' --stdinused A and
dropped B with no warning. Passing both is now an error; either alone works.mcptoon policy setrejects a non-existent server or tool.policy set zzz zzz rawused to exit 0 and persist a dead rule that matched nothing. The server
is checked against config, and the tool against the cache when one exists (an
uncached server is accepted rather than forcing a slow fetch).mcptoon healthexits non-zero when a server is dead, in both output modes.
The JSON path already exited 1; the human-readable path printed "1 dead" and
exited 0, somcptoon health && deploytreated an unreachable server as healthy.mcptoon callexits non-zero when the tool itself reports an error. A tool
that ran and returnedisError: true(e.g. a missing required argument) printed
its message and exited 0, somcptoon call ... && nextran the next step on a
failed tool. A tool-level failure is now exit 1; a payload that merely contains
anerrorfield (not the MCP error shape) is not misread as a failure.mcptoon add --headerrefuses a header with no:.--header "Bearer abc"
used to be dropped silently — the server was written with no Authorization header
and a cheerful "Added", so auth failed later with no clue why. It now errors and
exits 1 before writing.mcptoon doctorexits non-zero when it finds issues. It printed "✗ … cannot
start" and "N issue(s)" yet exited 0, so a CI health check read a broken machine
as healthy. Warnings (!, e.g. an empty cache dir) are not issues and keep exit 0.mcptoon inspect <server>(one argument) lists that server's tools. Its own
usage block advertised this "list all tools" form, but the code required two
arguments and exited 1, so the documented path was dead. Now a single argument
renders the server's tool list; an unreachable server still errors to stderr.mcptoon help <command>shows that command's own page. It used to ignore the
argument and print the whole global page, so the two documented routes to a
command's help (help <cmd>and<cmd> --help) disagreed. They now share one
renderer and produce identical output;helpwith no argument stays the global page.mcptoon --version --jsonprints structured JSON (version,python,
platform) for CI/scripts; the plain--versionline is unchanged.- Errors and usage hints go to stderr, not stdout. A bad argument, a missing
server, an unknown policy/setting, or a usage block was printed to stdout, so
mcptoon x >out 2>errmixed failures intooutand lefterrempty — a script
could not tell success text from a failure. These now go throughcli._err
(stderr) with a non-zero exit; legitimate empty results ("No servers
configured", a zero-match search) stay on stdout because they are the answer, not
an error.configwith an unknown action now also exits 1 instead of falling
through to 0. mcptoon config --helpshows config's own usage.configwas missing from
the per-command handler map even though_cmd_confighas aUsage:docstring, so
config --helpre-dumped the whole global page and never showed its
get/set/unset/reset syntax.mcptoon addno longer clobbers an existing server silently. A re-run or a
typo'd name used to replace a working server definition and print "Added". It now
refuses with "already exists — use --force" (exit 1) unless--forceis given.- Config writes are now atomic and keep a
.bak.save_configcalled
path.write_text(...)directly, so a crash mid-write, a full disk, or a reader
landing during the write could leave a truncated/emptyconfig.json— the user's
whole server list, which is not regenerable. It also overwrote a good file with no
snapshot (the other half of the 2026-10-02 "whole list replaced, no.bak"
incident). The write now goes through a unique temp file +os.replace, and the
last good file is copied to<config>.bakonce before the first overwrite.
set_settinguses the same atomic write. mcptoon remove(andinstall --remove) now drop the server's schema cache.
Removing a server from config left its per-server cache file on disk forever —
add demo; manifest; remove demostill showed 1 file undercache/schema_cache/.
cache.clear_server_cache()already existed anduninstallused it; the two
removedoors simply never called it. Both now do.mcptoon usage --jsonprints JSON, not human text._cmd_usageonly routed
toon/mcptoon/compactto the renderer, so--jsonfell through to the human
branch and printedTotal calls: ....jsonis now routed too.- A typo'd command or flag suggests the nearest real one.
mcptoon selecand
mcptoon manifest --jsnfailed with only "unknown ...". Both error paths now use
cli._closest(difflib) to add adid you mean: select, serve?line, while
keeping the exit codes (1 for a command, 2 for a flag).
Added
mcptoon config unset <key>andmcptoon config reset.config sethad no
inverse — once you setlang=enthere was no supported way back toautoshort of
hand-editing~/.mcptoon/settings.json.unsetforgets one override (back to its
default);resetforgets them all. Unknown keys fail loudly (exit 1), matching
config set.status --jsonreportsskills_liveandskills_stale. The human view already
warned "index may be stale" when the built index lagged the live catalog, but a
script reading--jsonsaw only the index count and trusted it. The JSON now
carries the live count and askills_staleboolean.
Changed
- Machine-readable
--jsonpayloads carry a schema version. The five
script-facing views (footer-facts,status,stats,usage,bench) now run
their payload throughoutput.stamp_schema, addingschema_version(bumped only
on a breaking shape change) andmcptoon_version. A consumer can now tell when a
field moved instead of silently mis-reading a rename. mcptoon <command> --helpis short again. The first per-command-help fix
printed the command's own usage followed by the whole global reference, so
skills --help(10,814 chars) ended up longer thanmcptoon --help(8,234). It
now prints the command's own usage, a compactOptions for '<cmd>':line, and a
one-line pointer tomcptoon --help— strictly shorter than the global page.
Docs
- The old GitHub Pages site now hard-redirects to the canonical site. The
in-repodocs/pages under the retired domain became redirect stubs pointing at
mcptoon.github.io, so an old bookmark or search hit lands on the live site
instead of a frozen copy. Two things this touched and had to be put back: the
token-tax calculator pages (docs/tools/token-tax/*.html), which the redirect
commit emptied while the tests still asserted their bodies, and the homepage
GitHub vX.Y.Zbadge thatscripts/bump.pystamps on every release. Both are
restored and pinned by the suite — the redirect keeps old URLs reachable, and the
calculator pages keep working.