The release that makes "installed" mean "saving tokens". A mounted gateway used to
add itself alongside a host's servers and save nothing; now it withholds the
upstream tool list by default, sync --takeover makes it the connection instead of
another server, and quickstart offers that switch at the one moment the tradeoff
is legible. Measured against a real serve handshake on a 12-server / 96-tool
machine (tiktoken cl100k_base): 18,686 → 2,607 tokens per turn (−86.0%).
Changed
- The gateway withholds the upstream tool list by default.
serveused to
enumerate every upstream tool with a simplified schema — the cost that made a
mounted gateway save ~0 on this machine once the host also listed its servers
directly.tools/listnow returns only mcptoon's own tools; upstream tools stay
fully callable (mcptoon_manifestnames them,mcptoon_inspectshows one
schema,mcptoon_callruns it) but are no longer enumerated. Measured against a
realmcptoon servehandshake on a 12-server / 96-tool machine,tiktoken cl100k_base: 18,686 → 2,607 tokens per turn (−86.0%). This is the "compact
manifest" ADR 0004 originally promised ("Claude Code 只看到 1 个 MCP server")
and ADR 0006's layered schemas had not delivered. - The handshake now explains where the tools went, in a second directive
independent of the savings line:mcptoon config set footer offno longer also
hides the tool-location note, since under the new default that note is the only
thing telling the model the user's MCP servers still exist. - Roll back with one command — no host config edit, no reinstall:
mcptoon config set exposure full. Both the setting's value and its default are
validated, so a typo fails loudly instead of persisting a mode nothing honours.
Added
-
Both legs now land on a host that can carry them.
sync --selfalready
mountedmcptoon servein every agent whose config holds MCP servers, and wrote
a skill pointer for Codex. Claude Code can do both — it mounts servers in
~/.claude.jsonand reads~/.claude/CLAUDE.mdas machine-wide context on
every session — so it now gets the mount and the pointer. The two are
independent: the mount gives the host native tools, the pointer gives it the
catalog in words, and neither is as good alone.mcptoon offremoves both,
restoring the memory file byte for byte (the block is heading-anchored and cut at
the next##).
A host is included only on evidence that its file is read every session:
Codex (AGENTS.md) and Claude Code (CLAUDE.md). Cursor is deliberately left
out —~/.cursorrulesis the legacy form, so a pointer there could be a silent
no-op, which is worse than not writing one. Windsurf, Cline and VS Code Copilot
expose no dedicated global instruction file and stay MCP-only rather than risk
editing a file the user shares with everything else. -
mcptoon's own skill now ships in the wheel and installs itself. The skill
that explains the exposure presets, and what to do when a tool panel looks
empty, existed in the repo but never reached apip install: every builtin pack
carried"skills": [],install_packonly wrotePROMPT.md, and the only
channel that shipped it was the Claude Code plugin.quickstartnow copies it
into each agent's skill folder (best-effort — never fails onboarding), and
mcptoon skills install-self [--view DIR] [--dry]repairs a machine where it is
missing. Only a file whose frontmatter declaresname: mcptoonis treated as
ours: a missing one is created, and our own stale one is refreshed — otherwise
an upgrading user keeps the copy from the release they first installed and never
reads the new defaults. A view holding a different skill is still left
completely alone, because another manager may own that folder and two managers
writing one view is how a catalog gets clobbered.
tests/test_skill_package.pypins the three distribution copies byte-identical
and pinsskill/SKILL.mdintopackage-data. -
The first command on a machine finishes the install
pipcould not. A
pip installruns no code, so it landed the package and nothing else: the skill
sat in site-packages where no agent reads it, and no skill index existed, so a
user who never ranquickstartstayed half-installed while the docs promised
"pip install, then just use it". Now the first real command (anything but
serve/off/uninstall/demo) installs the agent-visible skill and builds
the skill index, then stops. It is the additive half of the write-consent
rule: it creates a missing skill, refreshes our own stale copy, and never touches
a skill that is not ours; it is silent for pipes and machine formats, like the
welcome it rides beside. The marker records the installed skill's fingerprint, so
a release that ships a new skill heals once more instead of being suppressed by
the previous release's marker.uninstallclears the marker, so a clean
reinstall self-heals again. -
exposuresetting (compact|full).compactis the default and the
cheaper preset;fullrestores the previous enumeration for a host whose tool
panel readstools/list, or a model that will not ask for the manifest first.
Switching takes effect on the host's next connection. -
sync --takeover: the gateway becomes the connection, not another server.
quickstartandsync --selfregisteredmcptoon servealongside every
upstream server, so a host that already listed its servers kept loading all of
their tool schemas directly — the gateway added a line and saved nothing, which
is the opposite of what the tool exists for.mcptoon sync --takeoverremoves
each server mcptoon manages from the host config and reaches it through the
gateway instead (a server mcptoon does not manage is left alone, since the
gateway cannot serve it). Implies--self. The change is reversible: the
existing<config>.bakwritten on first change is the undo, andmcptoon off
still removes only the gateway entry.sync --dryand the sync report now name
how many direct entries were replaced (−N direct, now via gateway).Measured on a real 12-server / 96-tool catalog (this repo's own dev box, same
tiktoken caliberstatusandbenchuse): mounting the upstreams directly
costs 21,074 tokens of tool schemas, and the gateway entry added on top
costs another 2,607 under the default compact exposure (18,686 if
exposure=full) — the same tools reaching the agent twice. Takeover drops the
direct half, so the host's tool context goes 23,681 → 2,607 instead of
growing. That is the difference between "installed" and "installed and worth
installing".
Takeover is a subtraction — it removes server entries the user wrote into a
host config — so it now lists exactly what it will drop and asks once before
doing it.--dryprints the plan and stops,--yesskips the prompt for
scripted runs, and a declined or unanswerable prompt writes nothing. The
additive--selfstays silent: additions are reversible and lose nothing,
removals are not. -
quickstartnow offers takeover instead of quietly saving nothing. The
additive default above is safe but near-worthless on a machine whose servers are
already registered directly — the host keeps loading every upstream schema, so the
install that promised savings delivers ~0.quickstarttherefore ends onboarding
with a red warning that says so, lists exactly which direct entries it would
replace, and asks once: answering yes routes them through the gateway (via
sync --takeover --yes), anything else keeps the safe default. It is an offer,
never a default flip — the additive path is unchanged — and it obeys the same
consent bounds as every other prompt here: silent under--dry, machine formats,
--no-self, an empty removal plan, andMCPTOON_NO_TAKEOVER_OFFER; a
non-interactive stdin is never asked and is pointed atmcptoon sync --takeover
instead. The colour comes from one new choke point,welcome.paint, which returns
plain text for a pipe orNO_COLOR, so no escape codes reach a captured
transcript. -
The skill catalog is now reachable over MCP.
servepublished tools and
mcptoon skillsmanaged skills, but an agent connected to the gateway could not
see or resolve a skill without shelling out to the CLI. Two first-party tools close
that gap:mcptoon_skillslists the catalog (slug+ one-line description), and
mcptoon_resolve_skillstakes a task and returns the best-matching skills from the
same offline BM25 the CLI uses. Both are read-only and need no upstream servers, so a
gateway that only ever proxied tools can now answer "which skill should I load?".
The projection lives inskills.catalog_rows/skills.resolve_shortlist, shared by
the CLI and the tools, so the two surfaces cannot rank differently;resolvealso
records the same usage counts, somcptoon skills list --usagereflects MCP calls.
Fixed
-
mcptoon_inspectandmcptoon_callnow actually exist. The compact
exposure's whole safety argument is that withholding the tool list withholds
visibility, not capability — "mcptoon_manifestnames them,
mcptoon_inspectshows one schema, andmcptoon_callruns it". The last two
were named in the handshake directive, in the skill, and in ADR 0012, but had
never been implemented: a realtools/callfor either returnedUnknown tool.
Under the default preset an agent could therefore learn an upstream tool's
name but not its parameters, so "the tools still work" held only if it
guessed the arguments. Both are implemented now —mcptoon_inspectreturns one
upstream tool's full input schema, andmcptoon_callroutes{name, arguments}
back through the bridge's own call path, so it inherits schema validation,
danger checks, output compression and usage tracking rather than forking them.
The lesson is the test that was missing, not the code: the old test asserted the
tool index still held the tool and never called one, so a green suite proved
nothing about callability.tests/test_native_tools.py::TestCallBridgenow
performs real calls. The two tools cost 469 tokens per turn on the default
path (2,138 → 2,607), which is why the skill and the ADR quote the new number. -
mcptoon config setnow exits non-zero when it rejects a value. A bad value
left the setting unchanged but still exited 0, so
mcptoon config set exposure compakt && restartread as a successful preset
change. A refusal is a failure and now reports like one. -
syncverifies the gateway entry survived its own write. A successful
write_textis not proof the entry is there: Claude Code rewrites
~/.claude.jsonon its own schedule, and an observed race dropped the
mcptoonentry back out seconds after a sync that reported success. The write
is now read back, and a lost gateway entry returnswritten: Falsewith a
warning naming the file — a silent loss only surfaces later, as a user
wondering why their tools vanished. -
skills syncno longer mistakes an agent view for the catalog. With no
source argument the command took the first default root (~/.claude/skillsand
friends) as its source — but those paths are exactly where the agent views
live. On a machine whose view is a real directory rather than a link back to the
catalog, a sync would read the view and republish it into every view, freezing a
copy as if it were the truth, with nothing in the output to say so. A default
root now counts as a source only when it is a link to the real catalog; a plain
directory there is refused, and the caller is asked to name the source. -
21 skills were invisible to the catalog, and a deleted skill never left it.
Found by running the new staleness probe against this machine's real 407-skill
catalog rather than the fixtures:- The walk matched the manifest as
child / "SKILL.md". On Windows that found
the 21 skills that spell itskill.md— but only because NTFS folds case;
the same code on Linux would have dropped every one of them, and a stricter
== "SKILL.md"comparison drops them everywhere. The manifest name is now
matched case-insensitively, so the catalog is the same on every platform. - Staleness was "is any SKILL.md newer than the index file", which cannot see a
deletion (the files that remain are not newer) — a hand-removed skill kept
answering from the index. The index now stores a signature over the whole
listing (path, size, mtime of every manifest) and compares against it, so an
add, an edit and a delete are all detected. Verified against the real
catalog: deleting a skill and re-resolving removed it (408 → 407) with no
manualskills index.
- The walk matched the manifest as
-
Concurrent skill resolves lost their usage counts.
record_skill_usewas a
bare read-modify-write, and two callers reach it now — the CLI (resolve/route)
and the MCP tool (mcptoon_resolve_skills). Measured 2026-09-24: 30 concurrent
resolves recorded a count of 1, each thread having read the old file and written
back the same+1. It is now serialized in-process and written atomically (unique
tmp name +os.replace), the same disciplineusage._save_usagealready used, so a
reader can never catch a torn file either. The count is whatlist --usageshows,
so losing 29 of 30 was a visible lie about which skills earn their keep. -
The skill index no longer needs a manual step, and the CLI-only hosts now
actually get the pointer. Four gaps that together meant "installed" did not
mean "working":mcptoon skills indexwas required beforeresolve/route/the MCP tools
would answer anything, and a skill added afterwards stayed invisible until it
was re-run. The index now builds itself on first use and rebuilds when the
catalog on disk no longer matches the signature it was built from — a
stat-only probe (measured 0.25s cold, 0.03s warm for this machine's 1,217
manifests, against 0.4s for a full parse), so it rides every resolve.
list/statsstay strict readers and never build one as a side effect.
mcptoon quickstartalso builds it at the end of onboarding, so the catalog
is answerable the moment the install finishes.- The
codexsync target wrote toPath.cwd()/AGENTS.md— the pointer landed
in whichever project the user happened to be in, or nowhere — and its text
named onlymcptoon manifest, never the catalog. It now targets
~/.codex/AGENTS.md, writes a skill pointer, is idempotent, and is gated on
--selflike the gateway registration. The undo (mcptoon off) removes
exactly that block, byte-for-byte. codexwas not indetect_installed_agents, so the automatic path never
reached it. It is now detected by the presence of its AGENTS.md.- Claude Code was not a sync target at all, though it is the host where the
MCP handshake was verified working.~/.claude.json(flatmcpServers, the
same shape as Cursor) now syncs, and it is detected when the file exists.
Changed
- The handshake now points at the skill tool, and costs less. The
instructions
field of the initialize result is the one channel that reaches the model without a
tool call, and it talked only about the savings line. It now opens by naming
mcptoon_resolve_skills: a list of tools says nothing about when to reach for one,
so an agent that never sees a skill list in its prompt had no reason to call a
resolver. The same edit tightened the rest of the paragraph, so the whole block went
from 351 to 314 tokens — the pointer was paid for out of prose, not added on top. The
directive names the mounted tool rather than the shell command, because the reader
always has this gateway and may have no shell.