Changelog¶
All notable changes to this project are documented here.
[Unreleased]¶
[0.22.4] — 2026-10-19¶
Docs and packaging only; no runtime changes.
Added¶
- Docs site and homepage at https://kaiser-data.github.io/kitsune-mcp/ (MkDocs Material, deployed from
main). PyPI's Homepage/Documentation links, npm'shomepageand the registry'swebsiteUrlnow point there. - Hot-reload demo GIF at the top of the README: a real Claude Code session, 59°C → fix →
reload("dev")→ 15°C, 31 s.
Changed¶
- Docs checked against the real tool surface. The tools reference is regenerated from the registered signatures and adds the missing
reload,compare,onboardandsetup, with lean vs full profile marked. Quickstart and agent patterns hadcall()arguments in the wrong order and usedshiftback(), which only exists in the full profile. - Shorter, plainer copy on the README, homepage, hot-reload tutorial and quickstart.
- The demo server pins mcp 1.x in PEP 723 inline metadata, so
uv run weather_server.pyjust works. - Makefile uses uv; CONTRIBUTING describes the real
kitsune_mcp/layout.
Removed¶
- Internal plans, drafts and the unrelated
evals/framework from the public repo; the staleagent_manifest.json.
[0.22.3] — 2026-10-18¶
Added¶
- Star hint. The README (also the PyPI page) asks for a GitHub star under the hero and shows a stars badge. The first time
kitsune-mcpstarts it prints a one-line star hint to stderr, once, recorded in$KITSUNE_HOME/.first_run_done. stdout stays the clean MCP channel.
Changed¶
- The
kitsune-mcpconsole script now points atserver:main(wasserver:mcp.run).
Removed¶
- The Smithery badge: the listing returns 404 and the badge image 500.
[0.22.2] — 2026-10-12¶
Fixed¶
- The first
call()no longer kills aconnect()ed server. The.envchange detector started from an empty baseline, so its first check always counted as a change and respawned every pooled process. The respawned process also lost itsconnect()name, soreload("dev")andrelease("dev")reported "No connection named 'dev'" after any call. - Every respawn keeps the
connect()name: after a.envchange and after the process dies mid-call, soreloadandreleasestill find it. ~/.envis loaded at startup, so a key that only lives there reachesconnect()ed servers without depending on a respawn.call()on aconnect()alias stays local. It used to look the alias up in the registries, so an unrelated npm package named e.g.devlent your server its credential requirements and a[source: npm]label. Only live connections count (rows restored fromstate.jsonare skipped), and a liveconnect()alias wins over a mounted server with the same name.
Added¶
examples/demo-hot-reload/: a weather server with one planted bug and a shot list for recording the agent fixing it withreload.
Docs¶
- The safety docs no longer claim
connect()requiresconfirm=True. It runs your command as your user, with no consent gate and no cage. - README rewritten around the MCP REPL (edit →
reload→call): 469 → 172 lines, one-lineclaude mcp addinstall, lean tool table now listsconnect/release/reload. - Detail moved out of the README into
docs/safety.md,docs/configuration.mdanddocs/performance.md. - One tagline across
pyproject.toml,package.json,server.jsonandsmithery.yaml; the Smithery tool list now matches the lean profile. - Internal plans and reports moved from the repo root to
docs/internal/. Dead Discord invite removed.
[0.22.1] — 2026-10-01¶
Fixed — guard approval prompts could hang, and IPv6 URLs were misread¶
- An approval prompt now times out after 2 minutes and counts as a no. Some clients never render the form and leave the request open, which stalled the tool call.
- Clients that don't advertise elicitation (e.g. Claude Desktop) are no longer sent a prompt. The call is refused right away, with a hint to allow the tool in the policy.
http://[::1]:8080/was read as the host[and blocked even when::1was allowed. Bracketed IPv6 literals are now parsed.
Fixed — guard read URL paths in commands as file paths¶
In free-text arguments only a URL's scheme and host were stripped, so
curl https://pypi.org/simple/ produced the path /simple and could fail the
write-root check. The whole URL is now removed before paths are extracted.
[0.22.0] — 2026-10-01¶
Security — tool calls can no longer switch off Kitsune's own safety gates¶
auth(), key() and auto(keys=) wrote any env var the agent named, into
both ~/.kitsune/.env and os.environ. That let a prompt-injected agent set
KITSUNE_TRUST=community (skip the trust gate), KITSUNE_SANDBOX=off (skip the
Docker cage) or KITSUNE_ALLOW_LOCAL_FETCH=1 (SSRF). The trust-gate message even
suggested auth("KITSUNE_TRUST", "community"). Because unsandboxed stdio servers
inherit the environment, launch levers (NODE_OPTIONS, LD_PRELOAD,
PYTHONSTARTUP, BASH_ENV) and supply-chain levers (PIP_INDEX_URL,
NPM_CONFIG_REGISTRY, DOCKER_HOST, proxies, CA bundles) were reachable too.
_save_to_env now refuses every KITSUNE_* name and those levers
(credentials.is_protected_env_var); the tools return a "protected variable"
message and setup(action="harvest") skips them. The hints now tell the user to
set KITSUNE_TRUST by hand. Ordinary credentials (GITHUB_TOKEN, *_API_KEY,
DATABASE_URL) are unaffected.
Added — action guard (opt-in, stage 1)¶
New kitsune_mcp/guard.py: a policy check in front of every downstream tool
call, off unless ~/.kitsune/policy.json exists. Deterministic rules only in this
release: per-server tool allow/ask/deny globs, read/write path roots with
symlinks resolved, a host allowlist (URLs, git@host:, IPs, hosts after network
commands), opaque encoded payloads, and always-denied credential paths plus
~/.kitsune. ask uses MCP elicitation, so only the user can approve. monitor
mode (default) logs to ~/.kitsune/guard.log.jsonl without blocking; an invalid
policy fails closed. status() shows the guard's mode and tally. The classifier
stage (a calibrated decision model asked a permission question, with the policy as state) is
specified but not wired; until then, calls it would decide ask the user.
[0.21.1] — 2026-09-07¶
Fixed — pip install kitsune-mcp was broken by mcp 2.x¶
The mcp dependency had no upper bound, so a fresh resolve picked up mcp 2.x,
which renamed the bundled FastMCP to MCPServer and removed
mcp.server.fastmcp — imported by app.py, shapeshift.py and
tools/shapeshift.py. Every fresh install died at import:
Now pinned mcp>=1.0.0,<2; the upper bound is load-bearing until a deliberate
v2 migration. Three things had hidden it: uv.lock was gitignored so CI
resolved fresh every run, local virtualenvs held older pins, and test.yml
triggered only on push/PR — CI had not run since the previous release.
Fixed — the wheel shipped the entire repo into site-packages¶
packages = ["."] silently made the include list a no-op, so the wheel
carried tests/, docs/, evals/, examples/, bin/, .github/,
.gitignore, Makefile and the plan_*.md files — installing them at the
root of the user's site-packages, where top-level tests/ and docs/
collide with any other package doing the same. The wheel now ships only the
package plus the two entry-point shims: 203 entries / 469K → 35 / 122K.
Fixed — websockets was an undeclared dependency¶
The WebSocket transport (ws:// / wss://) imports websockets, and three
test modules patch websockets.connect, but it was declared nowhere — it was
present only as a transitive dependency of fastmcp. Removing that unused
package (never imported anywhere; the code uses the mcp SDK's bundled
mcp.server.fastmcp, a different project) exposed the gap.
Added — [ws] extra¶
pip install 'kitsune-mcp[ws]' installs the WebSocket transport's dependency.
It stays optional because the production import is lazy and degrades with a
clear message; the runtime hint now names the extra. websockets is also in
dev, so the suite exercises the transport instead of failing on it.
Added — uv.lock is committed¶
Reproducible dev and CI environments. Previously gitignored, which is why every CI run re-resolved dependencies from scratch.
Changed — lint the whole repo, enforce formatting, weekly upstream canary¶
ruff check .replaces four hand-listed paths.evals/,examples/and.github/scripts/had been outside the lint scope and accumulated 37 errors; all fixed. Two wereB023loop-variable captures (inexamples/benchmark.pyand the race-condition grader's thread stress harness) — latent rather than active, since both closures ran within the same iteration; benchmark output verified byte-identical. TheSIM115in the hang-watcher's tailer got a documentednoqa: that handle must outlive its block by design.ruff format --checkis now enforced, and the tree was formatted to match (75 files, in a separate commit).test.ymlgains a weeklyscheduleandworkflow_dispatch. The job installs unpinned, so a scheduled run catches an upstream break like the mcp 2.x one before a user does.
Docs — remount lean floor to v0.21.0 measured (~1,774 / ~3,561)¶
PR C's tri-state sandbox docstring grew shapeshift's schema cost; live
python examples/benchmark.py now reads lean 1,774 / forge 3,561
(was ~1,685 / ~3,396 at the 0.21.0 cut). Regenerated every live reference:
README savings table, docs/benchmarks.md, article / demo-script /
linkedin_post, server.py docstring, token-cost + architecture SVGs.
Also closed the deferred hygiene backlog: rewrote examples/test_session.md
(lean + reload + default cage), stripped stray Chameleon/receive from
docs/compatibility.md + docs/transports.md, modernized issue templates,
and synced kitsune-dev / kitsune-gateway skills (prefer reload, lean
REPL + default cage + ~1,774 floor).
[0.21.0] — 2026-07-23¶
Added — lean MCP REPL: connect / release / reload in the default profile¶
The pitch's headline developer loop (edit → reload → test in one session) was
forge-only, so it was unreachable on a default pip install. The REPL trio now
ships in the lean profile:
connect,release, and the newreload(name)are in_LEAN_TOOL_NAMES.reloadfolds the manual release → connect → shapeshift cycle into one call: it kills the stale process, restarts the stored launch command as fresh code, and remounts so the client sees the new schemas — removing the "connect handed back the old process" footgun (it always releases first).- Lean floor rises 6 → 9 tools (~1,358 → ~1,685 tokens); still additive on Tool Search clients — this is reachability, not a token-savings claim.
Added — works-now signal on search() and demo/onboarding hygiene¶
- Each
search()result row now ends with| ready: high|mid|low— a no-probe heuristic (_works_now_labeloverregistry._works_now_score: creds resolved - source tier + local transport) so it's obvious which hits run without setup.
onboard()'s 3-step check ends withshapeshift()(lean unmount) instead of the forge-onlyshiftback(), so the copy-paste check is correct in both profiles.- Rewrote
examples/demo_wow.md(dropped legacy Chameleon/receive/cast_offand the token-savings framing; lean-only session across the three pillars) and realigneddocs/demo-realtime.mdAct 1 to the leanreloadloop.
Added — sandbox-by-default on the exec paths (auto() / call() / run())¶
shapeshift(sandbox=True) was opt-in, but the "magic" execution paths still
spawned community npx/uvx processes uncaged: auto() (the model picks the
server), ad-hoc call(), and run(). These now cage low-trust / unknown-source
local stdio servers by default, best-effort:
- Docker present → the launch is wrapped in the same hardened
docker runprofile automatically (credential names forwarded value-less, as withsandbox=True). - Docker absent → the server still runs (uncaged) and the result carries a
one-line nudge to install Docker — these paths never hard-fail on a missing
daemon (unlike
shapeshift(sandbox=True), which the user asked for explicitly). - High-trust registry sources (official / absorbed / mcpregistry / smithery /
glama) are not caged by default; an explicit
KITSUNE_SANDBOXpolicy still applies on top. Pooledshapeshiftforms reused bycall()keep the policyshapeshift()already applied. - New helpers in
kitsune_mcp/tools/_state.py(_sandbox_default_for_exec,sandboxed_stdio_transport,transport_for_exec); no tool-schema change. 14 new tests.
Changed — shapeshift/prewarm cage low-trust sources by default (tri-state sandbox)¶
The mount path now matches the exec paths: community npm/pypi/github
mounts cage automatically instead of only when the model remembered
sandbox=True. shapeshift(..., sandbox=...) is now tri-state:
sandbox=None(default) → policy decides: low-trust / unknown sources cage in Docker when it's present, medium/high-trust registry sources don't. Best-effort — Docker missing runs uncaged with a nudge note (never hard-fails).sandbox=True→ force the cage; still hard-fails if Docker is missing or the launcher isn't npx/uvx (the user asked explicitly).-
sandbox=False→ the per-call escape hatch (run uncaged). -
prewarm()gained the same tri-statesandboxparam and default cage, so a warm community process isn't left uncaged and then reused by a latercall(). KITSUNE_SANDBOX=offis the session-wide escape hatch (turns off the default cage everywhere — mount and exec paths).- Community trust-gate copy now teaches the default cage +
sandbox=Falseopt-out instead of prompting forsandbox=True; README Safety section unified around "caged by default when Docker is present". New resolver_sandbox_for_mount; tests across all three states + Docker present/absent.
Added — trust-on-first-use (TOFU) version pinning¶
Resolution-time pinning (pkg@1.2.3) makes one session reproducible but
"latest" still drifts across sessions, so the same shapeshift() could
silently execute a newer — possibly hijacked — release later. TOFU closes that:
- The first mount of an npm/PyPI server records its exact version in
~/.kitsune/pins.json(atomic,0600). Later mounts reuse the pinned version and, on drift, launch the pinned version while surfacing⚠️ pinned to X, registry now offers Yin the shapeshift output. KITSUNE_REPIN=1adopts the newer version and updates the pin.- Applied before
server_argsand before the Docker sandbox wrap, so the pinned version is what actually launches on host or in the container. - Honest limits (documented): pins a version, not a content digest;
github:targets and hand-writtenconnect()commands carry no version and pass through unpinned. New modulekitsune_mcp/pins.py; no tool-schema change (policy via env, likeKITSUNE_TRUST/KITSUNE_SANDBOX).
Added — Docker sandbox for local npm/PyPI servers (sandbox=True)¶
The hardened Docker profile was previously reachable only via explicit
docker: image IDs — npm/PyPI servers still ran as raw host subprocesses.
Now the profile applies to them too:
shapeshift(server_id, sandbox=True)wraps the localnpx/uvxlaunch in the locked-downdocker runprofile (--cap-drop ALL, read-only rootfs,--pids-limit,--memory, no host filesystem).npx/uvxcaches are redirected to the container tmpfs so the read-only rootfs doesn't break first-run package download.- Secrets never enter the argv. Credential env vars are forwarded by NAME
only (
docker -e KEY) — declared credentials plus the same host-var heuristicinspect()probes use. Values stay out ofpsoutput and the process-pool key. KITSUNE_SANDBOXsession policy:communitysandboxes every low-trust (npm/pypi/github) mount automatically;all/1sandboxes every local mount.- The community trust gate now teaches the safer path — it offers
confirm=True, sandbox=Truealongside plainconfirm=True. - Pre-flight checks fail fast (before the current form is shed) when Docker is missing, the server is HTTP-hosted, or the launcher isn't npx/uvx.
- New constants
SANDBOX_NPM_IMAGE(node:22-slim) /SANDBOX_PYPI_IMAGE(astral-sh/uv python3.13);DockerTransportand the sandbox share one_hardened_docker_flags()profile builder.
Security — hardened Docker profile + default version pinning¶
Responds to an external review noting that "an isolated OS subprocess is not a sandbox" and that execution was unpinned.
- DockerTransport is hardened by default. Every
docker runnow adds--pids-limit 512,--security-opt no-new-privileges,--cap-drop ALL, and--read-onlywith a writable--tmpfs /tmp. Per-call opt-outs:writable,cap_add,network,pids_limit,memory. - npm/PyPI versions are pinned at resolution.
NpmRegistry.get_serverandPyPIRegistry.get_servernow emitnpx -y pkg@<version>/uvx pkg==<version>using the exact version the registry reports, so a later run can't silently pick up a newer release. Falls back to the bare name when no version is known. - Docs: README "Safety & sandboxing" → "Safety model", split into what it
does and does not protect against;
confirm=Truedocumented as a model-settable signal, not a human-approval boundary. Newdocs/demo-realtime.md.
Tests/CI — Docker-independent suite + real-container E2E job¶
Sandbox-by-default made three tests silently depend on whether the HOST has docker on PATH (green on a docker-less dev machine, red on CI runners, which ship Docker). Fixed and made structural:
- Autouse conftest fixture forces
shutil.which("docker")→Nonesuite-wide; tests that fake Docker present still patch within their own scope, and tests that need the real daemon opt out via the newreal_dockermarker. - New
docker-e2eCI job runstests/test_docker_e2e.pyagainst the runner's real daemon (gated byKITSUNE_E2E_DOCKER=1): a sandboxeduvx mcp-server-timeserves a real tool call through the hardened wrap, andtransport_for_execcages a low-trust npm server end-to-end — closing the "container E2E deferred" gap from the sandbox PRs. - The job's first run caught a real bug the mocked tests couldn't: Docker's
default
--tmpfsoptions includenoexec, so npx/uvx could download a server into/tmpbut never execute it (Permission denied (os error 13)) — every real sandboxed launch was broken. The sandbox wrap now mounts/tmp:rw,exec,nosuid,size=512m;DockerTransport's scratch tmpfs keeps the stricter noexec default (its server ships in the image).
[0.20.8] — 2026-05-24¶
Fixed — #44 completed for Smithery servers (thin registry schemas)¶
v0.20.7 fixed the shapeshift() call-example hint for official/stdio servers
but Smithery mounts still showed arguments={}. Root cause: Smithery's
registry listing returns inputSchema with type/properties but omits the
required array, so neither the registered proxy nor the hint knew which
params were mandatory.
- New
_schemas_missing_required()detects the thin shape (properties present, norequiredon any tool). _commit_shapeshift's mount path now refetches the live schema viatransport.list_tools()when the registry listing is thin — for both stdio and HTTP transports. This corrects both the registered proxy schema (so the model itself fills required params) and the printed call hint.- Verified live:
@upstash/context7-mcpregistry listing hasrequired: None; after refetch,resolve-library-idcarriesrequired: ['query', 'libraryName'].
Docs¶
- README baseline framing rewritten to be explicit that Kitsune is itself an always-on MCP server with a fixed ~1,321-token floor that never drops to zero. Added a break-even note (a single sub-1,321-token server is cheaper always-on) and recomputed the Performance tables, which still used a stale 500/965 base.
- Added
NEXT.mdtracking remaining work (PyPI publish still blocked on a mis-scoped token; #43 logout pending live OAuth verification; #34 auto() capability filter).
[0.20.7] — 2026-05-16¶
Fixed¶
- #43 —
auth(name, "logout")did not actually log the user out. Local token deletion still happened, but the IdP's session cookie meant the nextauth()silently re-issued a token with no browser flow visible. Logout now: - Calls the IdP's RFC 7009 revocation endpoint (when advertised via
revocation_endpointin.well-known/oauth-authorization-server) to kill the refresh token server-side, then - Arms a per-origin force-login flag that the next
ensure_token()consumes by appendingprompt=loginto the authorization URL (OIDC §3.1.2.1) — so the IdP must re-prompt even if a valid session cookie exists. - #44 —
shapeshift()hint showedarguments={}for tools with required params. Beginners hit a schema-validation error following the hint verbatim. The hint now populates every required parameter with a typed placeholder (from_PARAM_EXAMPLESor a type default), so the printed call shape is always valid. - #45 — Alarming "⚠️ Could not identify the backing process" warning on
every HTTP unmount. Warning was gated on
has_process is not False, which fired for both the genuine stdio-lost-its-key case AND for HTTP transports (whereFalseis the by-design value). Now gated onhas_process is True— silent no-op for HTTP/SSE, warns only for the truly unexpected stdio case.
Changed¶
- #42 —
status()"Saved vs always-on" counter now sums measured schema costs across every server shapeshifted this session, not just inspected ones. Previously the counter only grew viainspect(), which doesn't exist in the 6-tool lean surface, so the figure under-reported by orders of magnitude. New session stattokens_avoided_shapeshift: {server_id → tokens}keyed by server to avoid double-counting re-mounts. Rendered alongside the legacy inspect figure instatus().
Tests¶
- 5 new tests for the OAuth logout flow: revocation endpoint discovery,
RFC 7009 POST shape, no-endpoint short-circuit,
logout()end-to-end, and force-login flag consumption. 600 total tests pass.
[0.20.6] — 2026-05-16¶
Fixed — Process-tree termination on unmount (zombie leak)¶
shapeshift() (unmount) and pool eviction were only killing the immediate
uvx/npx wrapper, orphaning the actual MCP server child. Every shapeshift
cycle leaked one zombie process. Confirmed with ps: after shapeshift(),
the wrapper PID was gone but its Python child kept running indefinitely.
- Each subprocess is now spawned with
start_new_session=True(POSIX) /CREATE_NEW_PROCESS_GROUP(Windows) so the whole tree shares a process group. - New
_kill_process_tree()helper sendsSIGKILLto the negated PGID on POSIX (os.killpg) andCTRL_BREAK_EVENTon Windows, then falls back toproc.kill(). Short-circuits on already-reaped processes. - All
proc.kill()callsites that close pool entries now route through the helper: atexit cleanup, idle eviction, LRU cap,.envrotation,release(),shiftback(),inspect()probes, and reconnect retry. - Live verification: cold start → 4 procs (wrapper + Python child + prior
leftovers); after
shapeshift()unmount, the whole tree for the current shapeshift is gone instead of just the wrapper.
Docs — Honest token cost numbers across README and graphics¶
Audit found stale "~965-token baseline" and "~500 tokens at rest" claims
predating the v0.20.5 TDQS rebuild. Updated to live-measured values
(examples/benchmark.py):
- README savings table baseline
965→1,321; recomputed percentages (GitHub: 70% → 62%; 3-server: 81-85% → 74-81%; 5-server: 88-95% → 89-94%). docs/token-cost-{light,dark}.svg: kitsune bar values 800/1,190/2,450 → 1,621/1,921/2,221; savings labels and bar heights re-laid to match.docs/architecture-{light,dark,base}.svg: "~500 tokens at rest" → "~1,321 tokens at rest"; version label v0.20.1 → v0.20.6.- "Five tools at rest" → "Six tools at rest" (lean profile is 6).
[0.20.5] — 2026-05-16¶
Fixed — TDQS Behavior/Usage/Completeness scoring for call and search¶
Glama's per-tool TDQS scorecard graded call at C (2.9/5) and search at B
(3.1/5) — both penalized for thin Behavior, Completeness, and Usage Guidelines
sections. The other 4 lean-profile tools (status, shapeshift, auth,
auto) scored A and remain untouched.
This release enriches only call and search with:
- 4-line "Returns / Behavior / Side effects" block
- 2-line "Use when / Avoid when" guidance
- 2-3 worked examples
- Field(description=...) on every non-obvious parameter
Cost: call 188 → 345 tok, search 129 → 328 tok. Lean profile total
965 → 1,321 tok. Still well under the 3,654-token v0.20.2 explosion,
still 70-95% cheaper than always-on for any non-trivial multi-server config.
CI — release workflow no longer "fails" on Glama display¶
npm and mcp-registry publish jobs are now workflow_dispatch-only (with
explicit publish_npm / publish_registry boolean inputs). PyPI is the
release source of truth on tag push and never fails. The two optional
targets require a one-time setup (Trusted Publisher on npmjs.com, both
ecosystems live for MCP Registry) and so are gated to manual invocation
rather than silently soft-failing every release.
This keeps the "Publish Release" workflow status fully green on tag push — no more half-red "1 of 3 jobs failed" display on Glama or in the Actions UI.
[0.20.4] — 2026-05-16¶
Fixed¶
pyproject.tomlnow pinspydantic>=2.10,<2.13. A fresh install on a system with an older or newer pydantic crashes at startup withPydanticUserError: A non-annotated attribute was detected: 'result = <class 'str'>'because mcp 1.26's bundled FastMCP uses acreate_model()pattern that pydantic <2.10 and >=2.13 reject. Caught via end-to-end smoke test against the published 0.20.3 PyPI artifact.- README updated from
~965 tokens(benchmark estimator) to~1,187 tokens(live JSON-RPCtools/listmeasurement). The benchmark'slen(json.dumps) // 4undercounts the envelope overhead; the live number is what the model actually sees.
CI¶
- npm publish + MCP Registry publish marked
continue-on-error: true. PyPI (via OIDC trusted publishing) is the source of truth and never fails; npm needs a one-time Trusted Publisher registration at https://www.npmjs.com/package/kitsune-mcp/access, MCP Registry needs both ecosystems live. Until those are sorted, those legs fail silently rather than blocking the workflow's overall status.
[0.20.3] — 2026-05-16¶
Fixed — token diet for the 6 lean-profile tools¶
v0.20.2's TDQS-grade docstrings ("Use when / Avoid when / Behavior / Examples"
sections + verbose Field(description=...) on every parameter) ballooned the
lean-profile resting cost from ~1,110 tok to 3,654 tok — undercutting the
whole "low context overhead" pitch.
This release trims the descriptions to a sweet spot:
- 1-3 line docstrings with a tight example block
- Field(description=...) dropped where the param is obvious from name + docstring
- examples=[...] arrays removed (they serialize into the schema)
- Verbose Use when / Avoid when / Behavior / Examples blocks lifted to
examples/scenarios/ instead of every tool's schema
Result (measured via examples/benchmark.py):
| v0.20.1 | v0.20.2 (verbose) | v0.20.3 (this) | |
|---|---|---|---|
| Lean profile (6 tools) | 1,110 tok | 3,654 tok | 965 tok |
| Forge profile (20 tools) | 2,873 tok | 5,765 tok | 2,677 tok |
Per-tool (lean): shapeshift 252 · auto 194 · call 188 · auth 151 · search 129 · status 50.
README "Token savings vs always-on" tables recalculated against the measured 965-token baseline — savings still meaningful (70% on a single GitHub MCP, 88–95% on a 5-server bundle).
All 595 tests pass.
[0.20.2] — 2026-05-16¶
Fixed — process leak in shiftback() (#38)¶
shiftback(kill=True)is now the default. Previouslykill=Falsesilently leaked the underlying server subprocess (~40-50 MB each). With 10 servers explored in a session that adds up to ~400 MB of dead-weight memory the user never asked for. Users who want to keep the pool warm for fast re-attach must now opt in withkill=False.- Docstring and
docs/agent-patterns.mdupdated to reflect the new default.
Fixed — Glama Tool Definition Quality Score (TDQS)¶
- All 6 lean-profile tools (
status,search,auth,shapeshift,call,auto) rewritten to score well on Glama's TDQS rubric. Every parameter now carriesAnnotated[type, Field(description=...)]so descriptions surface ininputSchema.properties[*].description. Docstrings restructured with explicit "Use when / Avoid when / Behavior / Examples" sections — TDQS heavily penalizes missing usage boundaries (per Glama's research, 89% of surveyed MCP tools fail this). glama.jsonmaintainersarray was malformed (objects instead of strings per the official schema athttps://glama.ai/mcp/schemas/server.json). Fixed to["kaiser-data"]; passes schema validation.
Maintenance¶
- Issues #35, #36, #37 confirmed fixed on main and closed.
- Issue #38 fixed in this release.
- Issue #34 (auto() ranker for generic web-search tasks) commented with fix plan; still tracked.
[0.15.0] — 2026-05-09¶
Fixed — auto() arg inference and routing (continued from v0.14.0)¶
- Issue #1 fixed — optional search params now filled. Many Smithery servers declare all params as optional (
required=[]) but still reject calls without the primary query argument._infer_args_from_tasknow fills the firstSEARCH_PARAM_NAMESproperty (query,q,text, etc.) even whenrequiredis empty, preventingquery=undefinederrors onweb_search_exaand similar tools. - Issue #3 continued —
"current"context queries blocked. Added"current","latest","today","now"to_NL_STARTERSsoauto("current time in Berlin")returns{}fortimezoneinstead of forwarding the full phrase. - Path params protected. New Rule 2a:
path/file/directoryparams are never filled unless the task string looks like a filesystem path (/…,~/…,./…). Preventsauto("web search for X")from routing tomcp-server-gitand callingsearch_files(path="web search for X"). - Issue #4 fully fixed —
onboard()added to lean profile.onboardis now in_LEAN_TOOLSso theauto("onboard") → "call it directly → onboard()"redirect actually works. Also distinguishes lean vs forge tool names in the redirect message.
Testing¶
- Updated
test_auto_args.pyandtest_tool_surface.pyto reflect new behavior. - Total: 481 tests (was 480).
[0.14.0] — 2026-05-09¶
Fixed — auto() routing¶
_simple_searchnow does word-by-word matching instead of full-string substring matching.search("what time is it in Tokyo")now returnsmcp-server-timebecause "time" appears in its name. Previously only Smithery (server-side full-text) found anything for NL queries; official/McpRegistry/Glama/npm all returned zero results, soauto()routed to random Smithery HTTP servers. (Closes issues #2 and #1.)auto()extracts keywords before searching. Raw NL task"what time is it in Tokyo"→ search query"time Tokyo". Official registry now surfacesmcp-server-timeat rank #1. Fallback to raw task if keyword extraction strips everything._infer_args_from_taskrefuses to forward NL sentences to structured params. New rule: if the task starts with a question word (what,how,where, …) AND the required param is a structured identifier (timezone,currency,language,city, …), return{}instead of forwarding the full sentence verbatim.get_current_timeno longer receivestimezone: "what time is it in Tokyo". (Closes issue #3.)_infer_args_from_taskcorrectly fills search-like and QA params.query,q,text,prompt,user_question, etc. are always filled unconditionally — these are payload params that expect free text. Multiple required string params →{}(ambiguous; let LLM supply explicit args).auto()guards against built-in Kitsune tool names.auto("onboard")now returns a redirect message instead of searching the registry for an external server named "onboard". (Closes issue #4.)status()verifies Smithery API key liveness. A 3-second ping toregistry.smithery.aidistinguishes "key set — verified ✓", "key set but INVALID", and "could not verify". (Closes issue #5.)
Testing¶
- 23 new tests in
tests/test_v014_fixes.py. - Total: 480 tests (was 457).
[0.13.0] — 2026-05-09¶
Security¶
- SSRF via redirect blocked —
_ssrf_safe_request()validates each redirect hop against_is_safe_url().fetch()andcraft()endpoint proxies now use it, closing the open-redirect bypass (a public URL redirecting to169.254.169.254was previously followed without checks). _is_safe_urlmoved fromonboarding.pytoutils.py— canonical, single definition, no circular-import risk.
Fixed¶
- Probe temp dirs now cleaned up —
inspect()wraps the probe subprocess intry/finallyand callsshutil.rmtree(tmpdir)after the probe exits. Previously everyinspect()call leaked akitsune-probe-*temp dir. - SSE multi-line events —
_parse_sse()now collects alldata:lines per event (separated by blank lines) before parsing, matching RFC 6455. Servers that spread a large JSON response across multipledata:lines are now handled correctly. datetime.utcnow()removed — replaced withdatetime.now(UTC)inonboarding.py(Python 3.12+ deprecation).- PyPI HTML search — switched from CSS class-name regex (
package-snippet__name) to stable/project/<name>/link extraction. Less fragile across PyPI redesigns.
Performance¶
- NpmRegistry + PyPIRegistry now cache responses — 60 s TTL for search, 300 s for
get_server. ColdMultiRegistrycalls no longer re-fetch npm/PyPI on every search miss. MultiRegistry.bust_cache()propagates the clear to individual registry caches.
UX¶
status()shows crafted tools — persistentcrafted_toolsare now listed in the CRAFTED TOOLS section with method + URL, so users know what survived restart.MCP_CLIENT_INFO.versionreflects actual package version — reads fromimportlib.metadatainstead of being hardcoded to"1.0.0".
Testing¶
- 28 new tests in
tests/test_audit_fixes.pycovering: SSRF redirect guard, SSE multi-line parsing, probe tmpdir cleanup,_restore_crafted_tools,bench(),test()quality scorer,run(), crafted tools instatus(), PyPI link-based search, NpmRegistry/PyPIRegistry caching. - Total: 457 tests (was 429).
[0.12.0] — 2026-05-09¶
Security¶
- SSRF protection in
fetch()andcraft()— both tools now block requests to private/loopback addresses (127.x, 10.x, 192.168.x, 169.254.x, localhost), consistent with the existing guard inskill(). Override withKITSUNE_ALLOW_LOCAL_FETCH=1for local development.
New Features¶
- Parameter aliasing in proxy tools —
from_timezoneis silently remapped tosource_timezone,to_timezonetotarget_timezone,from/totosource/target,src/dst/desttosource/target, and language variants (from_lang,to_lang,input_lang,output_lang). Applied only when the alias key is absent from the tool schema. Closes #9. - Shapeshifted tool registration failures now surface — failed
mcp.add_tool()calls are no longer silently swallowed; they appear as⚠️ N tool(s) failed to register: name: errorin theshapeshift()output. - Session persistence —
crafted_toolsandconnectionsmetadata (minus PIDs andstarted_at) now survive server restarts in~/.kitsune/state.json. Explored history is capped at 100 entries. Crafted tools are re-registered with FastMCP on startup. auto()prefers official stdio servers over Smithery HTTP —mcp-server-time(official, stdio, free) now beats a Smithery HTTP equivalent when both are returned by search. Ties broken bynot (transport == "stdio")as the 4th sort key.
CI / Infrastructure¶
- mcp-publisher bumped to v1.7.6 — fixes silent MCP Registry publish failures since v0.9.0 (OIDC audience binding changed in v1.7.6).
actions/checkout→ v6,actions/setup-python→ v6 (Dependabot #1, #2).
Reliability¶
_registry_lock(asyncio.Lock) prevents concurrent shapeshifts from leaving orphaned tools. Without it, two concurrent shapeshifts could interleave shed + register, leaving both servers' tools mounted while session only tracked one.atexithandler persists session state before killing pool processes on interpreter exit.- Probe processes from
inspect()/compare()are killed immediately afterlist_tools()returns. - Registry paginated fetch no longer caches empty/partial results on transient failures.
shiftback(kill=True)no longer mass-kills unrelatedconnect()sessions when pool key is missing — warns instead.source='local'on HTTP-only Smithery servers returns a clear error before shedding the current form.call()uses the shapeshifted session's pooled transport instead of re-resolving from registry.
UX¶
auto()surfaces registry fetch failures instead of silently returning "no tools listed".compare()shows—instead of?for unknown values; error labels are human-readable.search()warning uses actual exception class per registry.shapeshift()output includes wall-clock timing for cold npm installs.- Filesystem server shapeshift proactively hints about
server_argswhen no allowed dirs are passed. - macOS
/tmp→/private/tmpsymlink resolved in proxy args. - httpx INFO logging suppressed by default (
KITSUNE_DEBUG_HTTP=1to re-enable).
Correctness¶
tokens_senttracked inStdioTransport,PersistentStdioTransport,WebSocketTransport.key()masks secret value in response;.envwritten with0o600permissions._infer_args_from_taskreturns{}when tool has multiple required string params.__version__exposed viaimportlib.metadata.
Testing¶
tests/test_ssrf.py— SSRF guard tests forfetch()andcraft().tests/test_param_aliases.py— alias normalization logic tests.tests/test_session_persistence.py— crafted_tools / connections / explored persistence tests.- 6 integration tests for lean vs forge tool surface.
_registry_lockconcurrency tests.tokens_senttests for all three previously-broken transports.
[0.11.0] — 2026-05-04¶
Provider-aware onboarding — closes the rest of issue #8. Configure providers once up front, explore freely. Hermes-style agents now reach a working tool call in ≤3 steps with zero API keys.
Added¶
onboard()tool — first-run wizard. Shows provider auth state, recommends 5 zero-config servers (mcp-server-time,@modelcontextprotocol/server-memory,mcp-server-fetch,@modelcontextprotocol/server-filesystem,@upstash/context7-mcp), and ends with a 3-step verification flow. Optional Smithery upgrade path with a direct link to API key setup. (Issue #8 acceptance: "new user reaches working tool call in ≤3 steps".)- Multi-provider fallback in
auto()— when noserver_hintis pinned and the chosen provider returns auth-failure response ("Auth failed",401,unauthorized, etc.),auto()walks through the remainingsearch()candidates whose creds it can satisfy. The user asked for "web search", not Provider X. - Argument inference in
auto()— whenauto()implicit-selects a tool but caller passed noarguments, fills the primary string param (query/q/prompt/text/etc., or single required string) fromtask. Without this, every implicit-select search tool failed withquery: undefined. Existing explicitargumentsare never overridden.
Changed¶
_credentials_ready()returns three explicit tiers instead of the ambiguous "no creds declared":✅ free – no keyfor official sources without declared creds🔑 needs SMITHERY_API_KEY(or other key name) for Smithery-hosted servers AND any server with declared creds — Smithery is checked unconditionally because per-server creds may be empty butSMITHERY_API_KEYis always required⚠️ community — may need credsfor npm/pypi/github undeclared🔑 may need OAuth or registry keyfor mcpregistry/glama undeclared Closes the issue-#8 misread where "no creds" looked like "free".status()headline reordered —PROVIDERSsection now appears immediately after the title, before everything else. PID/memory/perf stats moved below. Auth state is the actionable info; perf stats aren't.shapeshift()pre-flight gate for Smithery-hosted servers — fails fast with❌ shapeshift failed: '...' is hosted on Smithery and needs SMITHERY_API_KEYbefore tools are loaded, instead of letting the user discover the auth wall on the first tool call. Bypassed withconfirm=True. Includes a direct link tosmithery.ai/account/api-keysand an offered workaround (source="local").
Acceptance criteria from issue #8¶
- ✅
oauth.pypresent and importable (v0.10.2) - ✅ OAuth 2.1 flow works for direct HTTP servers (v0.10.2)
- ✅ New user reaches working tool call in ≤3 steps with zero API keys (
onboard()+ free-tier list) - ✅ Search results never show Smithery-hosted as "no creds" (3-tier labels)
- ✅
shapeshift()warns before loading server with unconfigured creds (Smithery pre-flight gate) - ✅
status()shows provider auth state primarily (reorder) - ✅
auto()falls back across providers silently on auth failure (multi-provider walk) - ✅
auto()correctly passes args to npm-based servers (argument inference + bug verified absent)
[0.10.2] — 2026-05-03¶
Hotfix¶
kitsune_mcp/oauth.pyis now actually shipped. v0.10.0 and v0.10.1 wheels were missing the module on PyPI and npm.transport.pyimports it at line 13 and callsoauth.ensure_token/oauth.delete_tokens/oauth._originfromHTTPSSETransport(direct=True), so every freshnpx -y kitsune-mcpandpip install kitsune-mcphitImportError: cannot import name 'oauth' from 'kitsune_mcp'at startup. Local development was unaffected because the file existed on disk; the file was authored locally but never staged. Closes #8 (Bug 1).- OAuth 2.1 device/browser flow for direct HTTP MCP servers (
HTTPSSETransport(direct=True)) is now functional in installed packages. 29 tests cover the flow end-to-end (tests/test_oauth.py). - CI guard added —
tests/test_release_smoke.pyimports everykitsune_mcp.*module and assertskitsune_mcp.oauthexports the symbolstransport.pycalls. Any future missing-module-on-publish bug will fail CI before reaching PyPI/npm.
[0.10.1] — 2026-05-03¶
Fixed¶
_make_proxyno longer forwardsNonefor omitted optional params. When a client called a shapeshifted tool without supplying optional non-string args (integers, booleans, arrays), the proxy filled them withNonefrom its__signature__defaults and forwarded thoseNones to the inner MCP server, which rejected them withInput validation error: None is not of type 'integer'(JSON Schema doesn't permit null for typed params unless explicitly declared as["type", "null"]). Affected most non-trivial servers —mcp-server-fetch(max_length,start_index,raw), GitHub (per_page,page), Postgres/SQLite (limit,offset), Filesystem (head,tail). The proxy now dropsNone-valued kwargs so the inner server applies its own defaults instead. Falsy-but-not-None values (0,False,"") are preserved. Surfaced during the v0.10.0 setup-coverage tests againstmcp-server-fetch.
[0.10.0] — 2026-05-02¶
Added¶
- Auto-resolution of typo'd / wrong-namespace server IDs in
shapeshift()—shapeshift("@modelcontextprotocol/server-time")now silently resolves to the canonicalmcp-server-timeand proceeds, instead of returning a confusing "not found" error. Multiple plausible matches surface as aDid you mean: a, b, csuggestion list. Single high-confidence match auto-resolves on the first turn — agents recover without needing a retry. Closes #6 (the verified piece — see issue thread). - Machine-detectable failure marker — every
shapeshift()failure response now starts with❌ shapeshift failed:so callers can detect failure with one prefix check instead of parsing prose. Successful responses still start withShapeshifted into '...'. _PaginatedListRegistrybase class —McpRegistryIOandGlamaRegistryshare a hookable paginated-fetch loop. New paginated registries now cost ~10 lines of overrides instead of ~30 lines of fresh page-loop code.TTLDict[K, V]cache primitive — keyed time-to-live cache with lazy expiry; replaces the ad-hocdict[tuple, (value, expires_at)]pattern that was scattered acrossMultiRegistry._fastmcp_compatshim — single point of contact for FastMCP private-API access (_resource_manager._resources,_prompt_manager._prompts)._assert_internals(mcp)runs at app startup so any future FastMCP refactor fails loudly at import rather than silently breakingshiftback().- Per-registry timeout in
MultiRegistry—search()andget_server()wrap each registry call inasyncio.wait_for(..., TIMEOUT_REGISTRY_TASK)so one stalled DNS/TCP-connect can't block the others. _dotenv_revisioninvariant — documented as monotonically non-decreasing, with a guard test so future fixtures can't break pool eviction by resetting the counter.
Changed¶
kitsune_mcp/tools.py(1782 lines) →kitsune_mcp/tools/package with themed submodules:discovery.py(search/inspect/compare/status),exec.py(call/run/fetch/test/bench),morph.py(shapeshift/shiftback/craft/connect/release),onboarding.py(skill/key/auto/setup),_state.py(shared helpers + mocks-target namespace). All public imports preserved viatools/__init__.pyre-exports — no breaking changes.ServerInfois nowfrozen=True, slots=True— eliminates cache-poisoning via mutation and saves ~30% memory per instance.dataclasses.replace(srv, ...)continues to work._SmitheryAuthdataclass withasyncio.Lock— replaces 4 module-level globals; concurrentget_token()/get_namespace()callers serialize on a lock with double-check, eliminating thundering-herd token refresh._evict_stale_pool_entries()debounced to once per 30s on the call hot path (force=True still available for tests). Pool sweep is no longer O(N) per tool call.
Fixed¶
- Test mock-patch surface unified — all
kitsune_mcp.tools.Xpatch sites now route throughkitsune_mcp.tools._state.X, the canonical namespace for cross-cutting state. 133 patch sites updated; future submodule refactors won't silently invalidate tests.
Issue #6 follow-up (verification notes)¶
The reported "Bug 1" (current_form not set after shapeshift) does not reproduce against a verified-valid server ID. The agent's repro used @modelcontextprotocol/server-time — which doesn't exist in any registry. shapeshift correctly returned a "not found" error which the agent misread as success, then was confused when downstream call() had no current_form to use. The auto-resolution work above fixes the underlying UX failure (typo'd ID → confusing error chain) so the same pattern can't happen again. The architectural claim that ~80% of clients are blind to dynamic tool changes is partially incorrect — the standard mcp Python SDK honors notifications/tools/list_changed (which Kitsune already sends), and verification confirms tools become visible to spec-compliant clients. Specific clients that don't honor the notification are the right targets to fix once verified — defensive-only code (Solution 1 / refresh tool) deferred until verified reports exist.
[0.9.0] — 2026-04-12¶
Added¶
source=parameter onshapeshift()—"local"forces npx/uvx install (no Smithery key);"smithery"forces HTTP;"official"requires verified registry listing;"auto"(default) keeps current behaviorshiftback(uninstall=True)— optionally uninstalls the locally installed package; uvx packages fully removed (uv tool uninstall), npx cache auto-expiresKITSUNE_TRUSTenv var — set"community"to permanently bypass the community/local confirmation gate for trusted users and agents (key("KITSUNE_TRUST", "community"))- Credential status in
search()results — each row now shows✅ readyor✗ needs API_KEY inspect()next-step CTA — ends withNext: key("VAR", "...") then shapeshift("id")orNext: shapeshift("id")based on credential state- Lean hint after
shapeshift()— servers with >4 tools loaded without a filter show💡 N tools loaded (~X tokens). For lean mounting: shapeshift("id", tools=[...]) - First-run onboarding in
status()— clean sessions show a 5-step guide with example flow - Registry failure reporting in
search()— timed-out registries shown as⚠️ Skipped: name (timeout)so partial results are visible
Fixed¶
- Credential check before
_do_shed()— missing credentials no longer drop your active form before returning the error bust_cache(server_id)now works — cache uses(id, source_preference)tuple keys; oldpop(str)silently missed every entrysource="official"gate ordering — official-source check fires before the trust gate, giving the right error for non-official servers- Pool path
current_form_local_installleak — pool shapeshift clears local install record so stale data can't triggeruninstall=Trueon the wrong package
Changed¶
shapeshift()pool-path and registry-path share a single_commit_shapeshift()helper — ~70 lines of duplication removed_credentials_ready()calls_to_env_var(k)once per key instead of three timesMultiRegistry._reg_namesprecomputed at init instead of on everysearch()call
[0.8.5] — 2026-04-11¶
Fixed¶
- Circular import between
registry.pyandofficial_registry.py—_registryis now a lazy proxy;MultiRegistry()is deferred until first use - Ruff lint — 64 errors resolved (import ordering, unused vars, SIM105, B023, UP046); CI pipeline is now fully green on Python 3.12 and 3.13
Added¶
- Codecov coverage reporting (badge in README, uploads on every CI run)
- Automated GitHub Releases with CHANGELOG excerpt on tag push
- Glama registry listing (
glama.json) - Dependabot for weekly pip + GitHub Actions updates
- SECURITY.md and PR template
[0.8.2] — 2026-04-11¶
Added¶
- npm wrapper package —
npx kitsune-mcpdelegates touvx kitsune-mcp(Python) - Official MCP registry listing (
server.jsonformcp-publisher) mcp-nameownership tag in README for registry verification
[0.8.1] — 2026-04-11¶
Fixed¶
- Smithery transport rewritten — replaced dead
server.smithery.ai/{name}/mcp?config=b64URL with the new Smithery Connect API: namespace → service token → connection upsert →api.smithery.ai/connect/{ns}/{id}/mcp. Fixes 400 "Server configuration is incomplete" and "Invalid token" errors fromrun.tools. - Registry now reads
deploymentUrlfrom Smithery API response instead of reconstructing stale URLs _resolve_configalways writes all credential keys (None→ JSONnull) so Smithery's schema validator sees all expected keys even when optional vars are unset
Changed¶
morph.py→shapeshift.py(rename complete;morph.pydeleted)- Session keys:
morphed_tools/resources/prompts→shapeshift_tools/resources/prompts .chameleondirectory references →.kitsuneincredentials.py,session.py,transport.py- Docker label:
chameleon-mcp=1→kitsune-mcp=1
[0.8.0] — 2026-04-10¶
Breaking Changes¶
- Package renamed
protean-mcp→kitsune-mcp— updatepip installand client configs - Package directory renamed
chameleon_mcp/→kitsune_mcp/— update any direct imports - Env var renamed
CHAMELEON_TOOLS→KITSUNE_TOOLS— update any custom tool filters - FastMCP server name
"protean"→"kitsune"— affects MCP client display name
Deprecated (remove in v0.9)¶
protean-mcp,protean-forgeexecutables (kept as aliases)chameleon-mcp,chameleon-forgeexecutables (kept as aliases)
Migration¶
pip install kitsune-mcp
# update mcp.json: "command": "kitsune-mcp"
# update env: KITSUNE_TOOLS=... (was CHAMELEON_TOOLS)
[0.7.3] — 2026-04-08¶
Fixed¶
status()output header: "CHAMELEON MCP STATUS" → "KITSUNE MCP STATUS"
[0.7.2] — 2026-04-08¶
Fixed¶
- README: absolute image URLs so logo and diagrams render on PyPI
[0.7.1] — 2026-04-08¶
Changed¶
- New logo (
logo_kitsune-mcp.png) replacing placeholder SVG - README: removed "a new way" framing; architecture diagrams cleaned of chameleon references
docs/architecture.svg: removed 🦎 emoji from Kitsune MCP labeldocs/architecture-forge.svg: "chameleon-forge" → "kitsune-forge"
[0.7.0] — 2026-04-08¶
Breaking Changes¶
morph()renamed toreceive()— update any prompts or scripts that callmorph(...)shed()renamed tocast_off()— update any prompts or scripts that callshed()- Package renamed from
chameleon-mcptokitsune-mcp— updatepip installandpyproject.tomlreferences - Executables renamed:
chameleon-mcp→kitsune-mcp,chameleon-forge→kitsune-forge
Migration Guide¶
| Before | After |
|---|---|
pip install chameleon-mcp |
pip install kitsune-mcp |
"command": "chameleon-mcp" |
"command": "kitsune-mcp" |
receive("exa") |
receive("exa") ← no change |
morph("exa") |
receive("exa") |
shed() |
cast_off() |
Deprecated executables (chameleon-mcp, chameleon-forge) are kept as aliases in v0.7.x for backward compatibility and will be removed in v0.8.0.
Added¶
kitsune-mcpandkitsune-forgeas primary entry point executableschameleon-mcpandchameleon-forgekept as deprecated backward-compat aliases
Changed¶
- MCP server display name:
"chameleon"→"protean" pyproject.tomlkeywords: removed"smithery", added"mcp-registry"- Package description updated to reflect 7-registry architecture
[0.6.2] — 2026-04-08¶
Fixed¶
receive()cold-start: prefer registry results with cached tool schemas over those without (fixes Exa cold-start failure)- Live
tools/listHTTP fetch fallback when registry cache is cold - Smithery URL format:
/mcpsuffix +api_keyquery param (was using wrong format) - Doubled Smithery URL when
srv.urlwas already a full URL - Pool staleness: auto-evict subprocesses when
.envchanges mid-session
[0.6.1] — 2026-04-07¶
Added¶
- Frictionless credentials:
.envauto-reload without restart (tracks mtime changes) call()is mount-aware:server_idoptional afterreceive()call()added to lean profile (7 tools total)- WebSocket transport support (
ws://,wss://)
[0.6.0] — 2026-04-07¶
Added¶
receive()proxies resources + prompts in addition to tools- Install command validation (shell injection and path traversal blocked)
- Trust tier warnings in
receive()output - Credential warnings at mount-time (not just at
call()-time) examples/benchmark.py— reproducible token overhead measurement- Notification compatibility testing
- Provenance shown in all
search()/inspect()/receive()/call()output
[0.5.9] — 2026-04-06¶
Added¶
- Refactored into
kitsune_mcp/package structure OfficialMCPRegistry— seeds frommodelcontextprotocol/serversGitHub repoinspect()stores measuredtoken_costfrom actual tool schemasstatus()sums measured costs for inspected-but-not-mounted servers- Per-server trust tier tracking
Fixed¶
- Registry fan-out priority: official > mcpregistry > glama > github > smithery > npm
- PyPI registry is opt-in only (not in default fan-out — too slow)