Repository navigation
Releases
ToolHome v0.7.4 — Call Ledger Identity
ToolHome v0.7.4
The call ledger now names what actually ran, and the console ships with each deploy.
Calls: the tool that ran, not the wrapper that exposed it
Compact endpoints expose every upstream tool as exec, so a ledger row led by the exposed name
read exec for everything and hid the only useful part. Rows now lead with the identity — the
server slug and the upstream tool:
penpot / execute_code
penpot / export_shape penpot_export-5fshape
CLI gh-cli / auth status
The compact wrapper (exec, search) is dropped from the row; a differing aggregate identifier
is still shown muted, since that is what a caller sent on a direct aggregate call. Hosted CLI
calls read as slug and command. Statistics already grouped by the upstream name and are unchanged.
Deploys ship the console
The deploy job runs in its own checkout while dist/ and web/dist/ are gitignored, so the
artifact step copied nothing and the deployment host kept rebuilding from whatever console build
it already had. This went unnoticed because a manual transfer had seeded it. The docker job now
uploads both directories and the deploy job downloads them before shipping, so the console in a
deployed image matches the commit.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.7.4. CLI: npm install -g toolhome@0.7.4.
No schema or API changes.
Validation
Web typecheck, 65 tests (4 new: compact row identity, direct aggregate identifier, CLI row, and no
bare exec label), production build. Server typecheck and 393 tests unchanged. The deployed
bundle hash is checked against the local build after the deploy.
ToolHome v0.7.3 — Deploys in Minutes
ToolHome v0.7.3
Deploys stop moving a gigabyte, and dual-stack upstreams connect reliably.
The app image is built on the server
The deploy job used to docker save the app image (~1.45 GB) and scp it from the GitHub runner,
which took 20+ minutes on the Aliyun link. It now ships the 3 MB build context (dist/,
web/dist/, the manifest and the Dockerfile) and runs docker build on the deployment host. The
npm, apk and uv layers are cached there, so a normal deploy rebuilds only the artifact layers.
Two build arguments make the first and every later build workable on a slow international link,
and can be set per deployment in /opt/mcp-home/.build-env:
BUILD_APK_MIRROR=mirrors.tuna.tsinghua.edu.cn/alpine
BUILD_NPM_REGISTRY=https://registry.npmmirror.comapk add curl through the default Alpine CDN took minutes there; through the Tsinghua mirror it
takes 3.5 s.
The image is 394 MB instead of 1.45 GB
The Go toolchain (~400 MB installed) was in the runtime image although no catalog entry uses the
go installer. It is now opt-in: build with --build-arg WITH_GO=true for a custom Go-based
entry.
Dual-stack upstreams: IPv4 first, with a longer first attempt
0.7.2 disabled happy-eyeballs to fix ETIMEDOUT on upstreams with AAAA records, which fixed
Penpot but broke Notion: with autoselection off, only the first A record is tried, and one of
Notion's refuses connections.
The image now sets:
NODE_OPTIONS="--dns-result-order=ipv4first --network-family-autoselection-attempt-timeout=2000"
Ordering keeps IPv4 first, the longer attempt budget lets a healthy IPv4 connection win before an
IPv6 attempt is started and fails (which used to abort the whole connect), and happy-eyeballs
stays on so multiple A records are still tried. Measured in the deployed container, five
consecutive fresh processes: design.penpot.app 200 ×5, mcp.notion.com 401 (auth challenge,
connection fine) ×5.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.7.3. CLI: npm install -g toolhome@0.7.3.
No schema or API changes. The deployment host needs the Docker CLI (present) and outbound access
to npm and the Alpine CDN (or the mirrors above).
Validation
Server typecheck and 393 tests. The server-side build was run twice: 394 MB on the first build,
55 s on the second (only the npm layer, invalidated by the version bump, rebuilt). Both upstream
checks above were run in the deployed container.
ToolHome v0.7.2 — Dual-stack Upstreams
ToolHome v0.7.2
One deployment fix, found while adding a Penpot upstream.
Remote upstreams on dual-stack hosts connect reliably
0.6.19 set --dns-result-order=ipv4first so upstreams with AAAA records resolve over IPv4. That
was not enough: with Node's happy-eyeballs still enabled, a fast IPv6 failure can abort the whole
connection before the working IPv4 attempt finishes. The symptom is ETIMEDOUT after roughly half
a second from a fresh process, for a host that curl reaches in 300 ms — and it is intermittent,
because a long-lived process reuses a connection that once succeeded. Measured inside the deployed
container against design.penpot.app:
NODE_OPTIONS |
result |
|---|---|
--dns-result-order=ipv4first (0.6.19) |
ETIMEDOUT ~0.7 s |
--no-network-family-autoselection |
HTTP 200 |
| both | HTTP 200 |
The image and compose file now set
NODE_OPTIONS="--dns-result-order=ipv4first --no-network-family-autoselection". This also explains
the intermittent fetch failed seen earlier on Notion (about 4 of 10 fresh processes failed while
a warm process succeeded 10 of 10).
Override NODE_OPTIONS on an IPv6-native deployment; keep both flags on an IPv4-only one.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.7.2. CLI: npm install -g toolhome@0.7.2.
No schema, API or configuration changes beyond that environment value. The manifest's accidental
self-dependency (toolhome on itself) was removed.
Validation
Server typecheck and 393 tests. The flag combination was measured against
design.penpot.app inside the deployed container before and after, and a remote MCP server that
had been reporting Version negotiation probe failed: fetch failed connects with it.
ToolHome v0.6.19 — Compact Discovery Fixes
ToolHome v0.6.19
Three fixes observed in real use of the compact discovery surface and a deployed upstream.
Compact describe no longer dead-ends on oversized definitions
A tool whose complete contract exceeds the 16 KiB describe budget (observed: Firecrawl's
firecrawl_search at 19 KB, its scrapeOptions block alone being 2.4 KB) returned
definition_too_large at every turn: find with detail=definition marked it
needsDescribe/definitionTooLarge, and describe failed with the same budget — the caller
could never obtain the argument contract through compact discovery at all.
describe now degrades instead:
- The schema is shrunk to a faithful outline — every top-level argument kept with its type,
clamped description andrequiredlist, nested objects collapsed one level. Firecrawl's
contract compresses to about a third, which fits. - Oversized upstream instructions are truncated at 400 characters with a marker instead of
failing the whole request. - Both degradations are explicit: the schema carries
x-toolhome-schema: "truncated", the payload
carriesupstreamInstructionsTruncated. Thedefinitionfingerprint always covers the full
contract, soexecstill detects a stale hand-over against either form. definition_too_largeremains the last resort, for a tool whose shrunken form still cannot fit.
The find(detail=definition) budget path is unchanged.
Compact exec accepts a client-prefixed tool name
A harness that connects to several ToolHome entries namespaces every tool with its own label,
turning firecrawl_firecrawl-5fsearch into toolhome-local__firecrawl_firecrawl-5fsearch; the
gateway answered Tool not found because no upstream slug contains __. resolve now strips an
outer entry prefix (both the __ and single-underscore forms) before matching, and never touches
a plain aggregate name like github_search.
Remote upstreams with AAAA records resolve over IPv4
An upstream that publishes an IPv6 address (Notion) made the whole gateway-side request fail with
Version negotiation probe failed: fetch failed on hosts without an IPv6 route, while curl -4
to the same URL answered immediately: Node's fetch tries the AAAA first and does not fall back.
The image and compose file now set NODE_OPTIONS=--dns-result-order=ipv4first; override it if the
deployment network is IPv6-native. A troubleshooting entry covers confirming the diagnosis with
docker exec.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.19. CLI: npm install -g toolhome@0.6.19.
No schema or API changes; the compose change adds one environment line.
Validation
Server typecheck, build and 390 tests; web typecheck and 61 tests; both builds. New tests cover
the degraded describe (schema survives, instructions truncated with markers, fingerprint intact),
the still-oversized error path, and entry-prefix stripping in both separators — including that a
plain aggregate name is never mangled. The pre-existing deepwiki oversized-instructions test now
asserts the truncation contract it previously expected to fail wholesale.
ToolHome v0.6.18 — Reported State
ToolHome v0.6.18
Carries everything described in docs/release-v0.6.17.md — node status reported by the machine
that runs it, and hosted-CLI status from the probe instead of the enable flag — plus the fix that
stopped that release from building.
Fixed
- A web test fixture for the node report did not satisfy
ServerWithRuntime(an incomplete
settingsobject), soweb: typecheckfailed. The 0.6.17 image was never built, no container
was deployed and nothing was published to npm from it; the tag and its release notes are kept
as the record of the attempt.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.18. CLI: npm install -g toolhome@0.6.18.
The node_reports table is created on first open and no existing data is rewritten. A client
machine that has not updated yet simply never reports, and its servers read "not reported by
<machine> yet" until it runs toolhome node report, or toolhome mcp stdio from 0.6.18.
Validation
Server typecheck, build and 383 tests; web typecheck, 61 tests and production build — each step
run and reported separately, which is what the previous release's pipeline skipped.
ToolHome v0.6.17 — Reported State
Superseded by v0.6.18. This release failed its web typecheck step, so no image was built, nothing was deployed and nothing was published to npm. The same changes ship in v0.6.18.
ToolHome v0.6.17
Both planes now report state that the host can actually know.
Node servers: reported by the machine that runs them
A server placed on a client machine was permanently unknown: the host never connects to it, so
it had nothing to record. The console could not tell such a server apart from a broken one.
toolhome mcp stdiopublishes a report at startup and every 60 seconds;toolhome node report
does it once, which suits cron.POST /api/v1/nodes/{nodeId}/reportaccepts only servers
actually placed on that label, so a machine cannot report another one's servers.- The report carries what only that machine can answer: the last connection state and error,
whether the launch command exists there, and how many tools the server exposes. - A report older than five minutes comes back marked
staleand is shown with its age instead of
as current, and a server that has never reported shows no state rather than a bareunknown. - The servers list says where a state came from — "reported by crayons-air 2 minutes ago · 26
tools" — and warns when the launch command is missing on that machine.
Hosted CLIs: the probe's answer instead of the enable flag
The CLIs page rendered the record's enabled flag as Ready, which claimed more than ToolHome
knows: an enabled CLI can be missing or not signed in, and that is exactly what the probe reports.
- The page now shows Configured until a probe answers, and Check / Check all runs the
probe on request — it executes the CLI, so it is not run on page load. - The result distinguishes Signed in, Not signed in, Not installed, No status reported
(the probe ran and printed no markers — inconclusive rather than failed), No probe (the record
declares none) and Not checked.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.17. CLI: npm install -g toolhome@0.6.17.
The node_reports table is created on first open; no existing data is rewritten. A client machine
that has not updated yet simply never reports, and its servers keep showing "not reported yet".
Older servers ignore reports for a node they do not know, so updating the CLI first is safe.
Validation
Server typecheck and 383 tests, web typecheck and 61 tests, both builds. Node reports are covered
end to end: what a machine reports reaches the server list, another machine's servers and
host-run servers are rejected, and a backdated report comes back stale. Web tests cover the
report line (machine, age, tool count, missing launch command), the probe state machine, both
locales defining every key, and the CLI page claiming only Configured before a check.
ToolHome v0.6.16 — Servers by Placement
ToolHome v0.6.16
The console's servers page separates placement.
Servers page
- A second-level menu under the plane switch groups MCP servers by where they run: All, the
ToolHome host (remote and home servers), and one entry per node machine. Counts sit on
each entry, and the menu only appears once a node machine exists, so a single-machine
installation looks exactly as it did. - A
nodeserver row no longer shows a barenodebadge: it names its machine (node · crayons-air), on the list and on the detail header. Previously a client-machine server was
indistinguishable from a remote endpoint at a glance. - The web types knew only
remote | home;nodeandnodeIdare now part ofServerRecord, so
the console can classify a server it used to render as an untyped extra kind.
Selecting a machine the instance no longer has falls back to showing everything rather than an
empty list.
Known gap, unchanged here: the server form still offers only remote and home, so a node
server is created through the CLI or the API.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.16. CLI: npm install -g toolhome@0.6.16.
No schema, API or environment changes; the console ships inside the image.
Validation
Web typecheck, 45 web tests (11 new: grouping, counts, ordering, per-machine filtering, and the
menu driving the list), production build. Server typecheck and 380 tests unchanged. Grouping was
also run against the live instance's 19 servers: 12 host, 6 on crayons-air, 1 on R9000P, and
no node server in the host group.
ToolHome v0.6.15 — Adoption and Deployment Fixes
ToolHome v0.6.15
Follow-up fixes found by running the new migration and adoption paths against a live deployment.
Adoption
cli adoptverified a hand-over by asking the CLI'sstatusprobe forloggedIn, which most
platform CLIs never report:gh --versioncannot say anything about a login, so a successful
hand-over was reported as a failure. An entry can now declare how to check itself, andgh-cli
does:verify: { args: ['auth', 'status'], expect: 'Logged in to' }
- A probe that reports no markers at all is now inconclusive rather than a failure: the
hand-over is reported ashanded over, but this entry reports no login state to confirm it,
and it never causes the upload to be skipped as "already authenticated". gh-cli's ownstatusprobe reportsversion=andloggedIn=markers, sotoolhome cli status gh-clianswers a real login state instead of alwaysloggedIn:false. Existing
installations keep the probe stored in their record; update it with
toolhome cli update <id> '{"probe":...}'or reinstall the entry.
Deployment
- The GitHub CLI image shipped by the deploy job no longer bakes in
GH_HOST=toolhome-gh-relay.crayon-6eb.workers.dev. That Cloudflare Worker relay stopped
answering, so everyghinvocation inside the hosted container hung until it timed out —
includinggh auth status, which made a stored token look invalid. The deployment host
reachesapi.github.comdirectly, verified from inside a container on it (gh api rate_limitanswers in about a second). SetGH_HOSTagain only if that direct route is lost.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.15. CLI: npm install -g toolhome@0.6.15.
No schema or environment changes. The probe and verification changes live in the catalog and in
the adopting CLI; a CLI record installed earlier keeps the probe it was created with.
Validation
Server typecheck and 380 tests, web typecheck and 34 tests, both builds. Adoption is covered for
the four verification outcomes (a declared verify command, an already-authenticated target, an
inconclusive probe, and a probe that reports a version but no login), plus the catalog invariant
that every hosted CLI entry declares how its login state moves.
ToolHome v0.6.14 — Instance Migration
ToolHome v0.6.14
ToolHome can move itself. toolhome migrate copies both planes — MCP servers and hosted CLIs, with their encrypted credentials — into another instance, and toolhome cli adopt moves a platform CLI's login from the machine that already has it. Configuration export/import grew the CLI plane, placement, and Market provenance, and learned to merge instead of refusing.
Export and import
config exportnow covers the CLI plane (clis), server placement (nodeId), and Market provenance (installations). Previously a configuration containing a node server could not be restored at all: the export droppednodeId, so import failed placement validation withNode servers require a nodeIdand rolled the whole transfer back.config import --mode mergemakes a restore idempotent: credentials are matched by name and reused when their payload is identical, records the target already holds are skipped, and--upsertupdates them field by field (transport, args, env, placement, settings, hosted CLI runtime and allow-list). A credential whose name matches but whose payload differs is a409unless--upsertis given, so a re-run is a no-op instead of a silent overwrite.config import --dry-runreturns the same per-item report without writing. The response now lists every item with a status (created,reused,updated,skipped,conflict) plus counts.createmode keeps its old behaviour: it aborts on a slug the target already has and now names every clash instead of failing at the first one.
Migration
toolhome migrate --to <url> --to-key <key>copies this instance into another one. Market entries are reinstalled on the target first, at their pinned versions, with values harvested from the exported credentials (env,bearer,api-key, andheadersshapes), so the target keeps its own installation records and version tracking; the configuration import then merges and leaves them alone. Per-entry reinstall failures are reported, not fatal.--dry-runreports without writing,--planes mcp,clinarrows the scope,--issue-access-key <name>issues an MCP Access Key on the target and prints the harness snippet, and--with-statealso moves each hosted CLI's state volumes.- The report ends with the work that needs a human: access keys (digests only, never copyable), hosted CLIs that report no login state, Market entries awaiting a secret, and OAuth credentials whose client registration still points at the source origin.
Hosted CLI logins
toolhome cli adopt [slug]andtoolhome cli syncmove a locally authenticated platform CLI into an instance. Each catalog entry declares how its state moves, so the outcome is explicit per entry:adopted,already-authenticated,credential-only(values travel with a configuration export),manual(the entry names the command to run on the target),undeclared, orfailed. The extracted secret goes to the target'sexecstdin and is never printed, logged, or returned.gh-cliextracts withgh auth token(macOS keeps the token in the keychain, sohosts.ymlholds none) and imports withauth login --with-token, which its allow list already permits.toolhome cli export-state/import-statemove one declared container volume as a base64 tar archive throughGET|POST /api/v1/clis/{id}/state?volume=<name>. Only adocker-mode record with a declared, writable volume is accepted, archives are capped at 64 MiB, and every transfer is audited. The archive contains the CLI's credentials.- New admin endpoint
POST /api/v1/credentials/{id}/reset-oauth-registrationclears an OAuth client registration bound to another origin while keeping tokens.
Fixed
- A configuration export containing a node-placed server can be imported again; the transfer no longer aborts with
Node servers require a nodeId. - A secondary import into a non-empty instance reports its conflicts instead of a bare
slug_conflict.
Upgrade
Server image: ghcr.io/crayonlu/toolhome:v0.6.14. CLI: npm install -g toolhome@0.6.14.
No schema migration and no environment changes. The export format is additive and version stays 1, so new files load in older instances with the new sections ignored — but the target instance must run this version or later to receive clis and installations. POST /api/v1/config/import accepts the new mode, upsert, and dryRun fields; without them it behaves exactly as before.
Validation
Server typecheck and 349 tests (including a two-instance transfer suite and the migration orchestrator), web typecheck, 31 web tests, and both production builds. Covered end to end: node placement and the CLI plane surviving export/import, merge idempotency, dry runs, credential-conflict refusal, the state-transfer guard rails, the migration order (install before import), value harvesting, and adopt outcomes.
Not covered by CI: the state archive itself needs a Docker host, so the tar helper is tested with an injected process and the full volume round trip is a manual step.
ToolHome v0.5.0 — MCP and Hosted CLI
ToolHome v0.5.0
ToolHome now manages MCP servers and hosted CLIs as equal planes. Install platform CLIs on the server once, reuse encrypted credentials, and execute allowed commands remotely with streamed output.
- Hosted CLI registry commands, status probes, argv execution, stdin, cancellation, timeouts, and output limits.
- Market contains 26 MCP entries and 9 CLI entries: Azure, GitHub, Tailscale, Lark, Firecrawl, Wrangler, Vercel, Aliyun, and opt-in host shell.
- Shared npm, Go, GitHub Release, uv, and Docker installers; host and Docker execution modes. Go and uv currently have no curated CLI products. Aliyun's release asset targets linux-amd64.
- GitHub CLI supports device-flow login and persistent state. Fresh self-hosted installations can build the pinned image when no preloaded image is available.
- Updated agent skill covers both planes, working CLI examples, authentication, deployment prerequisites, and installer coverage. The npm package includes the skill.
- Web console includes MCP/CLI plane switching and consolidated navigation. Managed deployment transfers images through CI artifacts.
Upgrade
Requires Node.js 24+ for the management CLI: npm install -g toolhome@0.5.0.
Server image: ghcr.io/crayonlu/toolhome:v0.5.0. Docker-backed entries require the Docker socket mount and matching group permissions; persist /data and declared CLI state volumes.
Environment variables now use TOOLHOME_*. API keys use tch_ctl_ (control) and tch_mcp_ (MCP access). Update old environment names and key configuration before upgrading. The CLI reads the legacy ~/.config/mcp-home/config.json if no current config exists.
Validation
Server typecheck and 160 tests; web typecheck and 31 tests; production builds; skill validation; real Market installation/execution for the hosted CLI plane and Fetch MCP handshake. CI additionally builds and probes the GitHub CLI fallback image.