toolhome 0.6.12 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/README.zh.md +24 -1
- package/dist/server/cli/adopt.d.ts +43 -0
- package/dist/server/cli/adopt.d.ts.map +1 -0
- package/dist/server/cli/adopt.js +170 -0
- package/dist/server/cli/adopt.js.map +1 -0
- package/dist/server/cli/main.js +126 -3
- package/dist/server/cli/main.js.map +1 -1
- package/dist/server/cli/migrate.d.ts +161 -0
- package/dist/server/cli/migrate.d.ts.map +1 -0
- package/dist/server/cli/migrate.js +405 -0
- package/dist/server/cli/migrate.js.map +1 -0
- package/dist/server/cli-plane/cli-service.d.ts +17 -0
- package/dist/server/cli-plane/cli-service.d.ts.map +1 -1
- package/dist/server/cli-plane/cli-service.js +48 -0
- package/dist/server/cli-plane/cli-service.js.map +1 -1
- package/dist/server/cli-plane/state.d.ts +29 -0
- package/dist/server/cli-plane/state.d.ts.map +1 -0
- package/dist/server/cli-plane/state.js +103 -0
- package/dist/server/cli-plane/state.js.map +1 -0
- package/dist/server/control/client.d.ts +10 -0
- package/dist/server/control/client.d.ts.map +1 -1
- package/dist/server/control/client.js +40 -2
- package/dist/server/control/client.js.map +1 -1
- package/dist/server/control/control-api.d.ts.map +1 -1
- package/dist/server/control/control-api.js +30 -1
- package/dist/server/control/control-api.js.map +1 -1
- package/dist/server/control/control-service.d.ts +94 -38
- package/dist/server/control/control-service.d.ts.map +1 -1
- package/dist/server/control/control-service.js +341 -24
- package/dist/server/control/control-service.js.map +1 -1
- package/dist/server/control/openapi.d.ts.map +1 -1
- package/dist/server/control/openapi.js +14 -1
- package/dist/server/control/openapi.js.map +1 -1
- package/dist/server/data-plane/gateway-server.d.ts.map +1 -1
- package/dist/server/data-plane/gateway-server.js +72 -31
- package/dist/server/data-plane/gateway-server.js.map +1 -1
- package/dist/server/market/catalog.d.ts +39 -0
- package/dist/server/market/catalog.d.ts.map +1 -1
- package/dist/server/market/catalog.js +38 -1
- package/dist/server/market/catalog.js.map +1 -1
- package/dist/server/market/market-service.d.ts +1 -0
- package/dist/server/market/market-service.d.ts.map +1 -1
- package/dist/server/node/local-gateway.d.ts.map +1 -1
- package/dist/server/node/local-gateway.js +20 -1
- package/dist/server/node/local-gateway.js.map +1 -1
- package/dist/server/upstream/oauth-service.d.ts +22 -0
- package/dist/server/upstream/oauth-service.d.ts.map +1 -1
- package/dist/server/upstream/oauth-service.js +26 -0
- package/dist/server/upstream/oauth-service.js.map +1 -1
- package/package.json +1 -1
- package/skills/toolhome/SKILL.md +46 -0
- package/skills/toolhome/references/cli-reference.md +98 -0
- package/skills/toolhome/references/troubleshooting.md +83 -0
|
@@ -126,11 +126,32 @@ toolhome market update <id> # update to the catalog pin (keeps
|
|
|
126
126
|
toolhome config export <file> # redacted export
|
|
127
127
|
toolhome config export <file> --include-secrets # full backup (0600)
|
|
128
128
|
toolhome config import <file> # restore (atomic transaction)
|
|
129
|
+
toolhome config import <file> --mode merge [--upsert] [--dry-run]
|
|
130
|
+
# merge keeps what the target already has
|
|
129
131
|
toolhome config import-harness <file> [--preview] [--upsert]
|
|
130
132
|
# import a Claude Desktop / Cursor mcpServers JSON;
|
|
131
133
|
# secrets become encrypted credentials (never echoed)
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
An export covers `credentials`, `servers` (including `nodeId` placement), `clis` (the hosted
|
|
137
|
+
CLI plane) and `installations` (Market provenance, reported but not written by the import).
|
|
138
|
+
`--mode create` (default) aborts when the target already has a slug and lists the conflicts;
|
|
139
|
+
`--mode merge` reuses credentials by name, skips records the target already holds, and updates
|
|
140
|
+
them only with `--upsert`. Credentials whose name matches but whose payload differs are a 409
|
|
141
|
+
unless `--upsert` is given, so a re-run is a no-op rather than a silent overwrite.
|
|
142
|
+
`--dry-run` returns the same report without writing. The response lists per-item status:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mode": "merge", "upsert": false, "dryRun": false,
|
|
147
|
+
"credentials": [{ "name": "Azure SP", "status": "reused" }],
|
|
148
|
+
"servers": [{ "slug": "firecrawl", "status": "created" }],
|
|
149
|
+
"clis": [{ "slug": "gh-cli", "status": "skipped", "changes": [] }],
|
|
150
|
+
"installations": [{ "entryId": "gh-cli", "targetType": "cli", "targetSlug": "gh-cli" }],
|
|
151
|
+
"counts": { "created": 2, "reused": 1, "updated": 0, "skipped": 1, "conflict": 0 }
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
134
155
|
Harness import accepts `{ "mcpServers": { name: { command, args, env } | { url, headers } } }`.
|
|
135
156
|
Secret-looking env keys (password/token/secret/key/authorization) and `Authorization` headers
|
|
136
157
|
are stored as encrypted credentials; everything else lands in the server transport.
|
|
@@ -201,6 +222,39 @@ toolhome cli exec <slug> --stdin-file ./input.txt -- <args...>
|
|
|
201
222
|
toolhome cli exec <slug> --stdin-file - -- <args...>
|
|
202
223
|
```
|
|
203
224
|
|
|
225
|
+
### Moving a login into ToolHome
|
|
226
|
+
|
|
227
|
+
A platform CLI keeps its login where it likes: `gh` in the macOS keychain, `az` in a token
|
|
228
|
+
cache, others in a container volume on the target. Each catalog entry declares how its state
|
|
229
|
+
moves, and `adopt` follows that declaration.
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
toolhome cli adopt gh-cli # one entry, against the configured instance
|
|
233
|
+
toolhome cli adopt gh-cli --to https://cloud.example.com --to-key tch_ctl_... [--dry-run]
|
|
234
|
+
toolhome cli sync --to https://cloud.example.com --to-key tch_ctl_... [--dry-run]
|
|
235
|
+
# every installed CLI entry on the target
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Outcomes per entry: `adopted` (credential handed over and accepted), `already-authenticated`,
|
|
239
|
+
`credential-only` (the entry authenticates from injected credential values, which travel with
|
|
240
|
+
a configuration export), `manual` (the login cannot move — the entry says what to run on the
|
|
241
|
+
target), `undeclared`, `failed`. The extracted secret is written to the target's `exec` stdin
|
|
242
|
+
and is never printed, logged or returned.
|
|
243
|
+
|
|
244
|
+
### Moving a state volume
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
toolhome cli export-state gh-cli --volume toolhome-gh-cli-state > gh-state.json
|
|
248
|
+
toolhome cli import-state gh-cli gh-state.json --volume toolhome-gh-cli-state
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Both go through the control API (`GET|POST /api/v1/clis/{id}/state?volume=<name>`) with the
|
|
252
|
+
archive as base64. Only a `docker`-mode record with a declared, writable volume of that name
|
|
253
|
+
is accepted; archives are capped at 64 MiB. **The archive contains the CLI's credentials.**
|
|
254
|
+
The gateway needs the host Docker socket (already required for `executionMode: docker`) and
|
|
255
|
+
pulls `alpine:3.20` on first use. Tools such as `offen/docker-volume-backup` cover the same
|
|
256
|
+
ground if you prefer to move volumes yourself.
|
|
257
|
+
|
|
204
258
|
`--stdin <text>` and `--stdin-file <path|->` are mutually exclusive. Put platform arguments after `--`; timeout is in milliseconds and cannot exceed the record's limit. Human output streams stdout/stderr; `--output json` emits NDJSON frames and an outcome. A failed or timed-out remote execution exits locally with code 1. Ctrl-C cancels the stream.
|
|
205
259
|
|
|
206
260
|
```bash
|
|
@@ -252,6 +306,50 @@ Semantics:
|
|
|
252
306
|
- **Audit**: every exec appends a `cli.exec` event (slug, argv, exit code, duration) visible
|
|
253
307
|
via `toolhome events` / `GET /api/v1/events`.
|
|
254
308
|
|
|
309
|
+
## migrate
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
toolhome migrate --to https://cloud.example.com --to-key tch_ctl_... [options]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Copies this instance into another one. The source is whatever the command already resolves
|
|
316
|
+
(`--url`/`--key`, `TOOLHOME_URL`/`TOOLHOME_CONTROL_KEY`, or `~/.config/toolhome/config.json`);
|
|
317
|
+
the target is `--to`/`--to-key`. Both keys must be admin-scoped.
|
|
318
|
+
|
|
319
|
+
| Option | Effect |
|
|
320
|
+
| --- | --- |
|
|
321
|
+
| `--planes <mcp,cli>` | Which planes to move (default both) |
|
|
322
|
+
| `--dry-run` | Report what would change without writing to the target |
|
|
323
|
+
| `--with-state` | Also move each hosted CLI's state volume (contains credentials) |
|
|
324
|
+
| `--reset-oauth-clients` | Clear OAuth client registrations that point at the source origin |
|
|
325
|
+
| `--issue-access-key <name>` | Issue an MCP Access Key on the target and print the harness snippet |
|
|
326
|
+
|
|
327
|
+
Order is deliberate: Market entries are **reinstalled first** (each one provisions its own
|
|
328
|
+
credential, record and installation record at its pinned version, using values harvested from
|
|
329
|
+
the exported credential), then the configuration import runs in `merge` mode and skips what
|
|
330
|
+
the Market already created. Reinstalling needs the entry's values; `--set`-style values are
|
|
331
|
+
harvested from `env` variables, `bearer` tokens, `api-key` values and `headers` entries. An
|
|
332
|
+
OAuth entry has no harvestable value, so it is reported as needing authorization on the target.
|
|
333
|
+
|
|
334
|
+
The report ends with what still needs a human: every `access-key` (stored as a digest, so it
|
|
335
|
+
cannot be copied), every hosted CLI that reports no login state, every Market entry that needs
|
|
336
|
+
a secret or failed, and every OAuth credential whose registration was left in place.
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"dryRun": false,
|
|
341
|
+
"source": { "servers": 2, "clis": 1, "credentials": 1, "installations": 1 },
|
|
342
|
+
"target": { "servers": 0, "clis": 0, "credentials": 0 },
|
|
343
|
+
"reinstalls": [{ "entryId": "gh-cli", "plane": "cli", "status": "installed" }],
|
|
344
|
+
"imported": { "counts": { "created": 2, "skipped": 1 } },
|
|
345
|
+
"stateTransfers": [{ "slug": "gh-cli", "volume": "toolhome-gh-cli-state", "status": "moved", "bytes": 412 }],
|
|
346
|
+
"verification": { "clis": [{ "slug": "gh-cli", "installed": true, "version": "gh 2.97.0", "loggedIn": true }], "mcpOk": true },
|
|
347
|
+
"oauthRegistrationsReset": [],
|
|
348
|
+
"accessKey": null,
|
|
349
|
+
"manual": ["Access keys are stored as digests and cannot be moved: …"]
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
255
353
|
## global options
|
|
256
354
|
|
|
257
355
|
```
|
|
@@ -132,6 +132,89 @@ toolhome server refresh <server-id>
|
|
|
132
132
|
|
|
133
133
|
**Fix**: Update to the latest version: `npm install -g toolhome@latest`.
|
|
134
134
|
|
|
135
|
+
## The MCP Client Times Out on `toolhome-local` (70s), Then Never Recovers
|
|
136
|
+
|
|
137
|
+
**Symptom**: the client reports `MCP server 'toolhome-local' timed out after 70s`. The remote
|
|
138
|
+
`toolhome` entry (same instance) keeps working, and `toolhome mcp stdio` started by hand works
|
|
139
|
+
too.
|
|
140
|
+
|
|
141
|
+
**Cause**: `toolhome-local` is a stdio entry — `toolhome mcp stdio` — so a timeout means the
|
|
142
|
+
launcher's startup did not finish, not that the instance is down. Startup makes one request
|
|
143
|
+
(`GET /api/v1/servers`) plus one per node-placed server (`/runtime`). Whatever the transport
|
|
144
|
+
reason — a proxy switching nodes, a fake-IP mapping going stale, a stalled VPN, a brief network
|
|
145
|
+
drop — the client has already given up at 70s and **does not retry**, so the session stays
|
|
146
|
+
broken until the client restarts the server. The client's captured stderr is the evidence:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
cat ~/.grok/logs/mcp/toolhome-local.stderr.log # e.g. "cannot reach https://… : ETIMEDOUT"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Check the order that isolates it**:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
toolhome status # control plane reachable with this config? (env overrides the file)
|
|
156
|
+
toolhome node status # the local mirror's view of this machine's servers
|
|
157
|
+
toolhome mcp stdio # by hand: it must print `serving node "<label>" over stdio`
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Read the error text: `ENOTFOUND`/`EAI_AGAIN` is DNS, `ECONNREFUSED` is nothing listening,
|
|
161
|
+
`ETIMEDOUT` is a stalled path, and a plain 401/403 means the control key, not the network.
|
|
162
|
+
|
|
163
|
+
**On a host with a TUN-mode proxy** (Clash, Surge, sing-box), DNS returns a fake IP from the
|
|
164
|
+
proxy's pool and every connection is tunnelled, which is the usual reason a path that works one
|
|
165
|
+
minute stalls the next. Node's `fetch` ignores `HTTP_PROXY`/`HTTPS_PROXY` unless
|
|
166
|
+
`NODE_USE_ENV_PROXY=1` is set, so the launcher uses the TUN path even when the proxy's HTTP port
|
|
167
|
+
works for `curl`. To make the launcher use the proxy port that is already proven working:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
NODE_USE_ENV_PROXY=1 HTTP_PROXY=http://127.0.0.1:10808 HTTPS_PROXY=http://127.0.0.1:10808 \
|
|
171
|
+
NO_PROXY=localhost,127.0.0.1 toolhome mcp stdio
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**If a launcher is wedged**, it holds the local mirror while the client is no longer using it:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
ps -ax -o pid,command | grep 'toolhome mcp stdio' # a stuck one shows a high CPU time
|
|
178
|
+
kill -9 <pid> # the client respawns it on demand
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The mirror is a cache: deleting `~/.config/toolhome/node.sqlite*` while no launcher runs is safe
|
|
182
|
+
and forces a clean rebuild on the next start.
|
|
183
|
+
|
|
184
|
+
## A Migrated Hosted CLI Reports `loggedIn: false`
|
|
185
|
+
|
|
186
|
+
**Symptom**: after `toolhome migrate`, `toolhome cli status gh-cli` on the target says the CLI is
|
|
187
|
+
installed but not logged in.
|
|
188
|
+
|
|
189
|
+
**Cause**: a platform CLI keeps its login where it chooses, and none of those places is the CLI
|
|
190
|
+
record. `gh` stores its token in the macOS keychain by default — `~/.config/gh/hosts.yml`
|
|
191
|
+
contains `user`, `git_protocol` and an empty `users:` entry but no token — so copying that file
|
|
192
|
+
moves nothing. `az` keeps a token cache under `/root/.azure` inside its state volume. Others log
|
|
193
|
+
in by device flow and write state inside the gateway.
|
|
194
|
+
|
|
195
|
+
**Fix**: run `toolhome cli adopt <slug>` from the machine that is already logged in (it extracts
|
|
196
|
+
with the CLI's own command, e.g. `gh auth token`, and hands the value to the target's `exec`
|
|
197
|
+
stdin), or migrate with `--with-state` to move the whole state volume. `toolhome cli adopt <slug>
|
|
198
|
+
--dry-run` prints what it would do without touching anything. Entries whose login cannot move
|
|
199
|
+
report `manual` and name the command to run on the target; `lark-cli` is one of those. Access
|
|
200
|
+
keys cannot move at all (only their digests are stored), so re-issue one with
|
|
201
|
+
`toolhome access-key create <name>` and re-point the harnesses.
|
|
202
|
+
|
|
203
|
+
## OAuth Upstream Fails After a Migration
|
|
204
|
+
|
|
205
|
+
**Symptom**: an MCP server that worked on the old instance answers `credential_authorization_required`
|
|
206
|
+
or "Invalid client" on the new one.
|
|
207
|
+
|
|
208
|
+
**Cause**: the OAuth client registration is bound to the instance that created it — the client
|
|
209
|
+
metadata URL and the callback both carry `TOOLHOME_PUBLIC_URL` and the credential id
|
|
210
|
+
(`/oauth/upstream/client/<id>`, `/oauth/upstream/callback/<id>`). A copied credential therefore
|
|
211
|
+
holds a registration the authorization server cannot match. Refresh tokens still work (the
|
|
212
|
+
refresh grant sends no `redirect_uri`), but a new authorization needs a new registration.
|
|
213
|
+
|
|
214
|
+
**Fix**: re-run the migration with `--reset-oauth-clients`, or call
|
|
215
|
+
`POST /api/v1/credentials/{id}/reset-oauth-registration`, then
|
|
216
|
+
`toolhome credential authorize <name>` on the new instance.
|
|
217
|
+
|
|
135
218
|
## Aggregate `/mcp` Only Shows `search` and `exec`
|
|
136
219
|
|
|
137
220
|
**Cause**: Compact tool exposure is enabled. `compact` intentionally replaces the full tool list with the `search`/`exec` discovery pair.
|