Skip to content

Releases: Rendeverance/toolfunnel

v0.7.0

Choose a tag to compare

@Rendeverance Rendeverance released this 03 Aug 08:52

****** MAJOR UPDATE ******

Finalised MCP spec and hardening pass.

0.6.0 was built against the release-candidate cut of the 2026-07-28 MCP revision. The spec has since been finalised, so this is the promised reconciliation pass - all divergences between RC and final closed - plus I performed a defect-hunt hardening pass across the whole gateway and increased the CI test suite from 36 to 73 tests!

Spec reconciliation

  • Best-version negotiation, both directions. The gateway now speaks the newest revision each peer supports (four legacy revisions plus modern), replacing the oldest-dialect clamp. The -32022 version-retry loop a client could get trapped in is gone.
  • JSON-RPC batching for 2025-03-26 clients: 202 for notification-only batches, one JSON array for request batches, 400 for an empty batch. Era-gated by header, capped at 64 members.
  • Header enforcement. An unknown MCP-Protocol-Version value is refused with a 400 (a 2025-06-18 spec MUST), and Mcp-Param-* mirroring is validated against the body.
  • Era-aware upstream error relay. A legacy caller gets the upstream's JSON-RPC error verbatim - code, message and data. A modern caller gets spec-band codes with the original preserved under data.upstreamCode / data.upstreamData.
  • Metadata fidelity. title, annotations, outputSchema, _meta and execution forward through every definition path, and result-side _meta relays (identity keys stripped in funnel mode, not in wrap mode, wrap mode is true to the wrapped server).
  • Elicitation, both modes. URL-mode elicitations relay end-to-end in both eras, suspension requires the caller's declared elicitation capability, and the -32042 path relays with its payload intact.
  • Subscription lifecycle. Listen-agreed resource subscriptions are refcounted and released on re-listen, stream close, teardown and cancel. Previously they could leak forever.
  • Rename migration. Renaming or aliasing a surfaced tool migrates exact-literal hook matchers and per-tool state atomically, and a rename that would silently detach a non-literal matcher is refused with the hook named. Gates can no longer be shed by renaming.
  • Modern per-request logLevel restores era-correctly, a modern HTTP client closing mid-call now cancels the upstream work, a non-loopback Origin is refused 403, --version exists, and the config-home relocation hint actually prints.

Hardening

  • The UI's per-tool gate switch now tells the truth. It can no longer delete a broader hook, adopt a hand-authored entry and report a script nothing runs, or let two tools sharing a register name silently own each other's gate (that is now a 409).
  • The spawn isolation guard reads the environment exactly as the spawn does. Values are judged as the coerced string the child receives, a __proto__ key is refused outright as a carrier, and the store normalises configured environments to plain string-to-string maps. The guard covers args, cwd and env, with case-insensitive keys on every platform; option-string variables (NODE_OPTIONS and friends) are not path-checkable, so in funnel mode they are refused, with an explicit manually configurable per-upstream opt-out.
  • Every spawn path is guarded - including the wrap-time era probe, which previously built its client directly and ran no checks at all.
  • A disabled upstream is now off everywhere. It vanishes from the lean list, both run paths and the curated surface, and its tools can no longer be resolved or executed. Previously they still could.
  • A curated alias can no longer shadow a local tool. When a curated alias collides with a local tool's name, it is no longer advertised with the upstream's schema while the call runs the local tool instead.
  • Optional auth fails closed on a blank issuer. A blank configured issuer no longer silently disables issuer binding; it is refused at request time, matching the existing audience guard.
  • Reconnect backoff always escalates. An upstream that dies during a reconnect attempt's own tool-listing no longer re-arms the one-second timer and pins the gateway to a permanent one-second respawn loop. The backoff climbs 1s, 2s, 4s and on to the 30s keepalive as intended.
  • Local tools have a clock and a ceiling. Script and shell tools time out at 120 s, kill their whole process tree, and bound their output in real bytes. A hanging local tool no longer starves every later request.
  • Hooks fail closed. Hook output is capped, and a hook that floods past the cap reads as a refusal, not an allow.
  • The panel stays responsive. The optional jose install runs off the config-write chain on its own single-flight latch, config-mutating posts are serialised so concurrent writes cannot discard each other, and --config-dir=/path is accepted.
  • A refused panel add or update unwinds cleanly. A rejected upstream add or tool update leaves no orphaned script and no half-written row, so the natural retry works.
  • The activity log is bounded. It rotates at 5 MB with one older generation kept, and reading the tail reads a bounded slice rather than the whole file.
  • Config writes never strand a temp file. The atomic writers remove their own temp on a failed write and sweep stale temps left by a killed process.
  • Housekeeping: ~1,030 lines of unreferenced code removed. SECURITY.md sets out the trust model.

Verification

73 test files, all green. Verified beyond the unit suite against real SDK-based servers - wrap, funnel, gate and HTTP - with identity, tool relay, structured errors, notifications, pagination and renames byte-checked against a direct connection.

Node >= 18, zero runtime dependencies, as always.

v0.6.0

Choose a tag to compare

@Rendeverance Rendeverance released this 23 Jul 07:41

******** MAJOR UPDATE ********

ToolFunnel v0.6.0: both protocol eras, and the transparent wrap

The 2026-07-28 MCP revision is a breaking change. It removes the initialize handshake and sessions that every existing MCP client and server is built on. This release makes ToolFunnel speak BOTH protocols, in BOTH directions. And on top of that sits the new headline feature...

One command turns ToolFunnel into a transparent wrapper for a single MCP server:

toolfunnel wrap my-server
  • All four combinations work: legacy client to modern server, modern client to legacy server, or matched pairs. No configuration.
  • Invisible from both sides. The client sees the wrapped server's own identity, tools, results, errors and notifications, byte for byte. Verified against real published servers. The server sees your real client, not ToolFunnel.
  • Mid-call user prompts (elicitation) from a legacy server are translated into the modern retry pattern and back, tested end to end against real elicitation servers. Resource subscriptions survive a crash and reconnect. Progress tokens flow through. Cancels are translated into the server's own request ids, so abandoned calls actually stop.
  • The policy gate and per-tool switches still apply to every wrapped call.

Use it to keep an unmaintained server alive past the cutover, to hand a modern-only server to older tooling, or to put a policy gate in front of a server you didn't write.

Dual-era everywhere

  • ToolFunnel answers legacy and modern clients on the same endpoint at the same time. Each request is served in the era it arrives in. server/discover, subscriptions/listen, per-request metadata, header validation and the new result fields are all in.
  • As a client, ToolFunnel probes each attached server and speaks whichever era it understands.
  • legacyPin pins any upstream to the old protocol, per server, with loud warnings. Off by default.
  • Two optional hardening switches: "serveLegacy": false in toolfunnel.json makes the gateway refuse legacy-era clients once yours have moved to the new protocol (smaller attack surface; loud warning, off by default), and "modernOnly": true on an upstream refuses to speak legacy to that server instead of negotiating down. Defaults unchanged: both eras served, both directions.

Also in this release

  • Identity settings: toolfunnel.json now sets the client identity ToolFunnel presents to upstream servers (clientName/clientVersion) alongside the existing server identity. A new Settings tab in the web UI edits it all. Under a wrap on stdio, your real client's identity is mirrored upstream automatically.
  • toolfunnel_howto gains wrap and configure topics, so a plain agent with no special prompt can learn wrapping and the complete no-code configuration map from inside the protocol. legacyPin gets a UI toggle. The wrap security notice now shows in the UI as well as the CLI.
  • Wrapped servers may use outside paths. Wrapping suspends the path-isolation guard for the wrapped server only, with a clear warning and a manual section on locking it back down. Funnel mode keeps the guard.
  • Tool calls now wait up to 120 s, configurable per upstream with "timeoutMs", and a tool that reports progress keeps its call alive indefinitely. Slow tools (builds, searches, image generation) work through the gateway exactly as they do directly. The 10 s window now applies only to handshakes and listings, where a slow answer means a dead server - and that window is also configurable per upstream with "requestTimeoutMs", for servers that boot slowly. A session restore that needs 30 s to answer can now be attached at all.
  • Shell tools no longer block. A long-running shell tool used to stall the whole process; it now runs asynchronously and concurrent clients keep being served.
  • Every start prints the resolved config home, with a relocation hint when it defaulted to the package root. Running from a git clone can no longer write config into the repo without saying so.
  • CLI flags work in any position: toolfunnel --config-dir X wrap Y and toolfunnel wrap Y --config-dir X are equivalent. A mistyped command now refuses loudly instead of silently starting a server.
  • "Discover tools" works on a disabled upstream, so you can inspect before enabling.
  • Server-initiated pings are answered. Upstream keepalives no longer die at the gateway.
  • Hardening under the hood: cancel translation for in-flight calls (a cancelled call now correctly receives no response), subscription replay across reloads, identity continuity across reconnect windows, health-endpoint privacy while wrapped, a cap on concurrent listen streams. The test suite grew from 27 to 35 files, including wire-level tests over real spawned processes, a multi-client concurrency test, and interop tests against the official MCP SDK client.

Notes

  • Built against the 28-July release candidate; final-specification reconciliation follows as a point release.
  • The elicitation bridge currently serves modern clients. A legacy client behind a wrap has mid-call prompts declined automatically, never hung. Server-to-client sampling and roots requests are answered method-not-found for now.
  • Upgrading from 0.5.0: no config changes required. Everything you have keeps working.

v0.5.0

Choose a tag to compare

@Rendeverance Rendeverance released this 16 Jul 14:06

What is new in 0.5.0

Added

  • Full illustrated user manual (docs/MANUAL.pdf) - install, the web UI, writing tools, attaching MCP servers, the policy gate, packaging, OAuth, troubleshooting.
  • Build your own MCP server, no code and no SDK: assemble scripts and curated upstream tools into a gated MCP and ship it with tf_pack.

Fixed

  • Config hot-reload now works over the HTTP host (--http): a live hook, tool, or upstream change takes effect with no restart, using the same in-place reloaders as stdio (no upstream bounce).
  • Windows: an fs.watchFile polling fallback catches config writes that fs.watch misses.
  • Corrected the reference-mode gating docs (the instruction handoff is gated; only server-side execution is absent).

Changed

  • README slimmed: the how-tos moved into the manual.
  • Roadmap: 2026-07-28 MCP spec conformance (dual-era) is the 0.6.0 milestone, see docs/KNOWN_BUGS.md.

Requires Node >= 18. Zero runtime dependencies.

v0.4.0

Choose a tag to compare

@Rendeverance Rendeverance released this 06 Jul 13:15

0.4.0 - the packaging release: external config home (TOOLFUNNEL_HOME/--config-dir - npm updates can never eat your tools), tf_pack (package your live setup as a portable home or a publishable npm package), configurable identity + ports (toolfunnel.json), requires runtime preflight, register hot-reload (the one unwatched config), first-class tool inputSchemas, UI loopback hard-pin + CSRF guard, OIDC discovery origin-pin, clean -32700 on all over-cap paths - and the 60-second zero-code demo (see the README). Suite 26/26.

Commits:

  • Adversarial-review fixes + the demo GIF lands in the README
  • Demo-as-code: the 60-second zero-code MCP story (task #39)
  • KNOWN_BUGS board: mark items 6-9 DONE with their commits - the full 0.4.0 board is code-complete
  • Packaging docs + README: the full ship-your-own-MCP story; fix a launch-week README artifact
  • tf_pack + requires preflight: package the live setup, declare its runtimes
  • External CONFIG HOME (TOOLFUNNEL_HOME / --config-dir): config survives the package
  • Configurable identity + port defaults (toolfunnel.json) - the wrapped-MCP handshake
  • Register hot-reload + first-class tool schemas: close the two 0.4.0 verify items
  • 0.4.0 security batch: loopback-pin the config UI + fix the four 7/3 review findings
  • One-command release automation: scripts/release.js

v0.3.1 - packaging compatibility

Choose a tag to compare

@Rendeverance Rendeverance released this 05 Jul 08:58

Packaging-only release - no functional changes.

  • Added a no-op build script to package.json so deployment pipelines that unconditionally run pnpm run build (e.g. MCP directory inspectors) no longer fail. ToolFunnel is zero-dependency and runs from source; there is nothing to build.
  • serverInfo.version is now sourced from package.json (previously hardcoded, and had already drifted).
  • Runtime behaviour is identical to v0.3.0. Full test suite: 22/22 passing.

v0.3.0 - first public release

Choose a tag to compare

@Rendeverance Rendeverance released this 04 Jul 17:55

ToolFunnel 0.3.0, as published to npm (npm install toolfunnel) on 2026-06-30.

Zero-dependency MCP gateway - funnel multiple MCP servers through one endpoint.

  • Live attach/detach of upstream MCP servers
  • Tool filtering, gating and hide/expose per client (tf_tool_set, expose config)
  • Hot config reload
  • Upstream health tracking + auto-reconnect
  • HTTP/SSE transport with optional OAuth 2.1 resource-server validation (jose installed on demand - the core stays zero-dependency)
  • Observability metrics
  • PreToolUse gate hooks (deny-hook, fail-closed)

Requires Node >= 18. MIT.