Skip to content

Releases

ToolHome v0.7.4 — Call Ledger Identity

Choose a tag to compare

@crayonlu crayonlu released this 19 Oct 08:57
9101319

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

Choose a tag to compare

@crayonlu crayonlu released this 18 Oct 15:29
2f57aee

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.com

apk 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

Choose a tag to compare

@crayonlu crayonlu released this 18 Oct 14:22
7a9a0f1

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

Choose a tag to compare

@crayonlu crayonlu released this 17 Oct 17:09
45b7ac4

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 and required list, 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
    carries upstreamInstructionsTruncated. The definition fingerprint always covers the full
    contract, so exec still detects a stale hand-over against either form.
  • definition_too_large remains 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

Choose a tag to compare

@crayonlu crayonlu released this 16 Oct 05:35
c959017

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
    settings object), so web: typecheck failed. 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

Choose a tag to compare

@crayonlu crayonlu released this 16 Oct 05:32
d36991a

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 stdio publishes a report at startup and every 60 seconds; toolhome node report
    does it once, which suits cron. POST /api/v1/nodes/{nodeId}/report accepts 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 stale and is shown with its age instead of
    as current, and a server that has never reported shows no state rather than a bare unknown.
  • 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

Choose a tag to compare

@crayonlu crayonlu released this 16 Oct 05:17
3a138ff

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 node server row no longer shows a bare node badge: 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; node and nodeId are now part of ServerRecord, 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

Choose a tag to compare

@crayonlu crayonlu released this 13 Oct 06:46
c1e1c8b

ToolHome v0.6.15

Follow-up fixes found by running the new migration and adoption paths against a live deployment.

Adoption

  • cli adopt verified a hand-over by asking the CLI's status probe for loggedIn, which most
    platform CLIs never report: gh --version cannot say anything about a login, so a successful
    hand-over was reported as a failure. An entry can now declare how to check itself, and gh-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 as handed 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 own status probe reports version= and loggedIn= markers, so toolhome cli status gh-cli answers a real login state instead of always loggedIn: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 every gh invocation inside the hosted container hung until it timed out —
    including gh auth status, which made a stored token look invalid. The deployment host
    reaches api.github.com directly, verified from inside a container on it (gh api rate_limit answers in about a second). Set GH_HOST again 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

Choose a tag to compare

@crayonlu crayonlu released this 13 Oct 06:27
f877280

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 export now 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 dropped nodeId, so import failed placement validation with Node servers require a nodeId and rolled the whole transfer back.
  • config import --mode merge makes a restore idempotent: credentials are matched by name and reused when their payload is identical, records the target already holds are skipped, and --upsert updates 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 a 409 unless --upsert is given, so a re-run is a no-op instead of a silent overwrite.
  • config import --dry-run returns the same per-item report without writing. The response now lists every item with a status (created, reused, updated, skipped, conflict) plus counts.
  • create mode 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, and headers shapes), 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-run reports without writing, --planes mcp,cli narrows the scope, --issue-access-key <name> issues an MCP Access Key on the target and prints the harness snippet, and --with-state also 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] and toolhome cli sync move 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, or failed. The extracted secret goes to the target's exec stdin and is never printed, logged, or returned. gh-cli extracts with gh auth token (macOS keeps the token in the keychain, so hosts.yml holds none) and imports with auth login --with-token, which its allow list already permits.
  • toolhome cli export-state / import-state move one declared container volume as a base64 tar archive through GET|POST /api/v1/clis/{id}/state?volume=<name>. Only a docker-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-registration clears 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

Choose a tag to compare

@crayonlu crayonlu released this 15 Sep 05:52
4da25fa

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.