server.json
1 field changed
- version
-
0.130.00.130.14
dev.mcphost/mcphost
78 changes recorded. MCP Harbor re-reads this listing from the official registry every six hours and asks the live server what it exposes, and keeps what changed between one look and the next.
− 2. `host.docs.put(name, content)` — once per document (this recipe's own− corpus: 8 documents, ~22 KiB total, well under the 2 MiB per-document− cap).− | tools:write | tools:read, tools:call | 182 |− 1. Declare the table (typed columns: text, integer, real, timestamp, boolean, json):− ```− 2. Append your rows in batches of 200 -- five calls covers 1,000 rows:− ```− | `resources` | yes | yes | `resources` | `host.table.list` | `resources/list`, `resources/read`, `resources/templates/list`, `resources/subscribe`, `resources/unsubscribe` | - |− Generated by `scripts/protocol-frames-check.sh` from `FRAME_SEQUENCES`; do not hand-edit. A response is `application/json` when it is one frame and `text/event-stream` when more than one frame is sent (`{1}` exactly one, `{0,1}` optional, `{0,n}` any number). Take the last `result` or `error` frame. Per request type, in order: `initialize` = `result` {1}; `tools/list` = `result` {1}; `tools/call:keyed` = `notifications/tools/list_changed` {0,1}, `notifications/prompts/list_changed` {0,1}, `resul− 3. `host.sync.get(sync_id="<step1.sync_id>")` -- the sync row; polled until the first receipt (`last_run`) exists− 4. `host.sync.list()`− 5. `host.sync.delete(sync_id="<step1.sync_id>")` -- stops the schedule; the `orders` table stays− Expected cost: 6 calls (the 1 signup included), within 90 s.+ An operator pauses and resumes signups without a shell with the admin+ tool `admin.signup_pause` (`message` to pause, optional `until`;+ `resume: true` to lift), which writes the same pause file.+ ### Sub-agents+ <!-- quickstart-recipe:sub_agent_key -->+ 1. As the lead: `host.key.delegate` `{"can_delegate":true,"label":"helper","scopes":["tools:call"],"ttl_s":3600}` returns `key`, `label`.+ 2. As the helper, with the `key` call 1 returned: `host.key.delegate` `{"scopes":["read","write","tools:write","state:read","state:write","secrets:write","keys:manage","admin"],"ttl_s":60}` fails `scope_escalation` (asking for `read`, `write`, `tools:write`, `state:read`, `state:write`, `secrets:write`, `keys:manage`, `admin`).+ 3. As the lead: `host.key.revoke` `{"cascade":true,"label":"helper"}` `revoked` >= 1.+ Hand the helper the `key` call 1 returned, never your own. It holds `tools:call` only: any other scope fails `scope_escalation`, as call 2 shows.+ Call 3 revokes it and its children; the helper's next call fails `key_revoked`.+ <!-- /quickstart-recipe:sub_agent_key -->+ Documents are text the host indexes itself; a vector index built elsewhere (LlamaIndex, Chroma, pgvector) is not importable: export its source documents and put them.+ 2. `host.docs.put_many(documents)` — up to 50 documents per call, each+ `{name, content}` (or `content_base64`); this recipe's own corpus is one+ call: 8 documents, ~22 KiB total, well under the 2 MiB per-document cap.+ Every document gets its own `stored` / `replaced` / `skipped` / `refused`+ row, so one refusal never undoes another.+ | tools:write | tools:read, tools:call | 184 |+ 1. One call, no file tool needed -- the server fetches the 1,000-row fixture itself:+ host.table.create(name="expenses", source_url="https://mcphost.dev/examples/sample-1000.csv")+ ```+ 2. Or declare the table yourself (typed columns: text, integer, real, timestamp,+ boolean, json) and append your rows in batches of 200 -- five calls covers 1,000 rows:+ ```+ | `resources` | yes | no | `resources` | `host.table.list` | `resources/list`, `resources/read`, `resources/templates/list`, `resources/subscribe`, `resources/unsubscribe` | - |+ Generated by `scripts/protocol-frames-check.sh` from `FRAME_SEQUENCES`; do not hand-edit. A response is `application/json` when it is one frame and `text/event-stream` when more than one frame is sent (`{1}` exactly one, `{0,1}` optional, `{0,n}` any number). Take the last `result` or `error` frame. Per request type, in order: `initialize` = `result` {1}; `tools/list` = `result` {1}; `tools/call:keyed` = `notifications/tools/list_changed` {0,1}, `notifications/prompts/list_changed` {0,1}, `resul+ - `host.docs.put_many`+ - `host.probe.progress`+ - `host_docs_put_many`+ - `host_probe_progress`+ | `chart_invalid_mark` | no | none | 1 | fix-and-resend |+ | `handle_not_found` | no | none | 1 | fix-and-resend |+ | `handle_quota_exceeded` | no | none | 1 | do-not-retry |+ | `lineage_blocked` | no | none | 1 | fix-and-resend |+ | `not_found` | no | none | 1 | fix-and-resend |+ | `quota_exceeded` | no | none | 1 | do-not-retry |+ | `table_already_exists` | no | none | 1 | fix-and-resend |+ | `table_bound_exceeded` | no | none | 1 | fix-and-resend |+ | `table_column_not_found` | no | none | 1 | fix-and-resend |+ | `table_not_found` | no | none | 1 | fix-and-resend |
− mcphost.dev is the server's address; the Python module is 'import mcphost' (mcphost.state, mcphost.table, mcphost.docs, mcphost.blob, mcphost.lineage, mcphost.drift, mcphost.channel, mcphost.msg, mcphost.elicit).− 4. Publish a tool. A later call on this SAME connection without a key is− refused, naming the tenant you already are -- reconnect through− `onboarding.url` first, then call `host.tool_publish` there.− `host.quickstart()`'s own `starter_tool` is a ready-to-publish `python`− spec; its `publish_call` is exactly:− Two real kinds: submit code (`python` -- source required,− `args_schema`/`requirements` inferred if omitted) or wrap an API you− already use (`http` -- url and method required, `args_schema` inferred− if omitted). `echo` is a stub for testing the pipes, not a real tool.− client_secret, scopes)` -- `preset: "slack" | "github" | "google"` fills− `auth_url`/`token_url` from a built-in table; an explicit `auth_url:`/− Generated by `mcphost capabilities-section` from the `CAPABILITIES` registry -- the same rows `initialize.capabilities` and `GET /capabilities.json` are built from; do not hand-edit. A method of a primitive marked `no` answers `-32601` with `data.help_url` (`/help/capability_unsupported`) and `data.alternative`.− | primitive | implemented | list_changed | probe | alternative | methods |− |---|---|---|---|---|---|− | `tools` | yes | yes | `tools` | - | `tools/list`, `tools/call` |− | `resources` | no | no | `resources` | `host.table.list` | `resources/list`, `resources/read`, `resources/templates/list`, `resources/subscribe`, `resources/unsubscribe` |− | `prompts` | no | no | `prompts` | `host.quickstart` | `prompts/list`, `prompts/get` |− | `completions` | no | no | `completions` | `host.help.search` | `completion/complete` |− | `sampling` | no | no | `sampling` | - | `sampling/createMessage` |− | `elicitation` | yes | no | `elicitation` | `host.ask.create` | `elicitation/create` |− | `logging` | no | no | `logging` | - | `logging/setLevel` |− | `roots` | no | no | `roots` | - | `roots/list` |− Generated by `scripts/protocol-frames-check.sh` from `FRAME_SEQUENCES`; do not hand-edit. A response is `application/json` when it is one frame and `text/event-stream` when more than one frame is sent (`{1}` exactly one, `{0,1}` optional, `{0,n}` any number). Take the last `result` or `error` frame. Per request type, in order: `initialize` = `result` {1}; `tools/list` = `result` {1}; `tools/call:keyed` = `result` {1}; `tools/call:keyless_signup` = `notifications/tools/list_changed` {0,1}, `resul+ Reconnect rule: a later call on this SAME connection without a key is+ refused, naming the tenant you already are (`data.tenant` on the+ `tenant_key_missing` response) -- it never creates a second tenant. Use+ `onboarding.url` for every call from here on.+ mcphost.dev is the server's address; the Python module is 'import mcphost' (mcphost.state, mcphost.table, mcphost.docs, mcphost.index, mcphost.blob, mcphost.lineage, mcphost.drift, mcphost.channel, mcphost.msg, mcphost.sampling, mcphost.email, mcphost.elicit).+ 4. Publish a tool, reconnected through `onboarding.url`. `host.quickstart()`'s+ own `starter_tool` is a ready-to-publish `python` spec; its+ `publish_call` is exactly:+ Three real kinds: submit code (`python` -- source required,+ `args_schema`/`requirements` inferred if omitted; `js` -- JavaScript+ source defining `main(args)`, `args_schema` inferred if omitted) or wrap+ an API you already use (`http` -- url and method required, `args_schema`+ inferred if omitted). `echo` is a stub for testing the pipes, not a real+ tool.+ `host.tool.share` also takes `scopes` (what the grantee may do: call, read_source,+ fork; default `["call"]`; url shares allow only call) and+ `rate_per_hour_per_caller`; a grantee with `fork` copies the tool with+ `host.tool.fork(owner, tool, as)`, and after `host.tool.unshare` a past caller's+ next call fails `share_revoked`.+ <!-- recipe:docs-qa begin -->+ ## Recipe calls: Docs Q&A in a minute+ The exact calls, in order (generated from `recipes/docs-qa.toml`; do not hand-edit -- run `cargo xtask recipes`). Plan fit: free.+ Sign up first (`signup`), once per tenant: `owner`.+ 1. `host.docs.put(content="# VPN setup\n\nThe VPN client connects on port 1194 over UDP.\n", name="vpn-setup.md")`+ 2. `host.docs.put(content="# Onboarding\n\nOn day one you receive a laptop and a badge.\n", name="onboarding.md")`+ 3. `host.docs.status()` -- poll until index.lag_seconds == 0+ expect `$.index.lag_seconds` = 0+ 4. `host.tool_publish(kind="python", name="ask_docs", spec={"source":"<contents of examples/docs-qa/ask_docs.py>"})`+ 5. `host.tool_call(args={"k":5,"query":"What port does the VPN client connect on?"}, name="ask_docs")` -- every passage carries a name:offset citation+ expect `$.passages[0].name` = "vpn-setup.md"+ Expected cost: 6 calls (the 1 signup included), within 90 s.+ <!-- recipe:docs-qa end -->+ <!-- recipe:first-run begin -->+ ## Recipe calls: First run: plugin walk-through+ The exact calls, in order (generated from `recipes/first-run.toml`; do not hand-edit -- run `cargo xtask recipes`). Plan fit: free.+ Sign up first (`signup`), once per tenant: `owner`.+ 1. `host.tool_publish(kind="python", name="table_note", spec={"source":"<contents of examples/first-run/tools/table_note.py>"})` -- a real python tool, not a stub+ expect `$.kind` = "python"+ 2. `host.tool_test(args={"note":"hello"}, name="table_note")` -- a real write through the sandbox API+ expect `$.result.appended` = 1
Showing the first 40 of each.
− Irreversibility: calling it twice is a no-op, not an error or a crash —− lists only the issuer(s) you registered with `host.oauth.issuer_set`,− under `authorization_servers` -- never another tenant's.− - Concurrent calls: 20 concurrent calls host-wide; per tenant, 4 per tenant on the free plan (10 on pro) -- a refusal past your own cap is `capacity`/`scope: "tenant"` with a `retry_after_ms`; past the host cap it's `scope: "host"`− - `host.table.join_paths`− without a monitoring SaaS account or an unmonitored box polling for it.− Two tables, two tools, one schedule.− 1. Create the tables, on the monitoring tenant:− host.state.table_create(name="targets", schema={"url": "text", "added_at": "real"}, primary_key="url")− host.state.table_create(name="checks", schema={"url": "text", "code": "integer", "ms": "real", "at": "real"})− 2. Add up to 20 targets (a 21st is accepted but never probed -- `probe`'s− own result reports the cap):− host.state.insert(table="targets", rows=[{"url": "https://api.example.com/health", "added_at": 0}])− 3. Publish `probe` (`python`, `network: "public"`; source omitted below− for space -- `examples/uptime-probes/tools/probe.py`): fetches every− target with a 10 s per-URL timeout, writes one `checks` row per target,− deletes `checks` rows older than 24 h so the table never outgrows 20− targets x 288 runs/day, and caps a run at the first 20 targets by− `added_at`:− ```− host.tool_publish(name="probe", kind="python", spec={"source": "...", "network": "public"})− ```− 4. Schedule it every 5 minutes -- the free plan's floor:− ```− host.trigger.set(tool="probe", kind="schedule", schedule="*/5 * * * *")− ```− `host.trigger.fire(id)` runs it once immediately instead of waiting 5− minutes; a real deployment just waits.− 5. Publish `status` (`python`; source omitted -- `examples/uptime-probes/tools/status.py`):− reads `targets`/`checks` only, never fetches live, and returns each− target's last result plus its 24 h up-percentage and (if currently− down) `down_since`:− ```− host.tool_publish(name="status", kind="python", spec={"source": "..."})− host.tool_call(name="status", args={})− ```− Free-plan fit: 2 tools (of 50), 2 tables, 1 schedule (of 3); a 5-minute− schedule fires 1440 / 5 = 288 times a day -- 288 of the free plan's 500− calls/day, leaving headroom for `status` calls too. Alerting on a flip is− a one-line hand-off to the agent-inbox recipe: `probe` calls+ Installing mcphost from inside an MCP client: fetch+ [https://mcphost.dev/llms-install.md](https://mcphost.dev/llms-install.md) -- it lists the exact+ install step for every supported client; apply yours, then make the first call.+ <!-- domain-hint:start -->+ mcphost.dev is the server's address; the Python module is 'import mcphost' (mcphost.state, mcphost.table, mcphost.docs, mcphost.blob, mcphost.lineage, mcphost.drift, mcphost.channel, mcphost.msg, mcphost.elicit).+ <!-- domain-hint:end -->+ try_before_call: `args` may be omitted on host.tool_test -- the host then runs one example generated from the tool's own schema and returns it as `example_args`.+ Irreversibility: calling it twice returns `{disabled: true,+ already_offboarded: true}`, not an error or a crash —+ While an incident is open, every host-fault error (`storage`, `internal`,+ `exec_unavailable`, `timeout`, `rate_limited_host`) also carries+ `data.incident {id, since, status_url, impact, retry_after_s}`: the failure+ is the host's, not yours, so wait `retry_after_s`, then retry once, and+ report `status_url` instead of repeating the call. Errors that are your+ own (`tool_exception`, `args_invalid`) never carry it. A 5xx HTTP+ response carries the same seconds in `Retry-After`.+ ## Tell the host what you needed+ `host.feedback.send {kind, text, context?}` carries a sentence to the host's humans, and works before signup. Kinds: `missing_tool`, `wrong_error`, `docs_gap`, `bug`, `praise`. `text` is 1 to 2048 bytes (longer is `invalid_params` with `max_bytes`); `context` is an optional object up to 1 KB. It returns `feedback_id`. Anonymous senders get 5 per hour per IP, keyed tenants 60 per hour; over the cap is `rate_limited` with `retry_after_s`. `host.feedback.list` returns your own rows.+ ## Hand work to another agent+ `host.task.post {to, payload, deadline_s, dedupe_key?}` sends a task to one agent and returns `task_id`, `thread_id` and `status`. The recipient's `host.msg.wait` returns it with `data.task.task_id`; it calls `host.task.claim {task_id}`, then `host.task.complete {task_id, result}`, and your own `host.msg.wait` returns a message whose `data.task` carries the status and the result. `host.task.get {task_id}` reads one task. A task nobody finishes by its `deadline_s` goes `dead`, a `task.dead` messa+ - `mcphost.blob` -- read or store files of any type under this tenant (same store host.blob.* uses)+ - `mcphost.elicit` -- ask the user a typed question mid-call: the client's form when it can, the ask-user link otherwise+ ## Install in your client+ One line per client; the same table `/connect` and `host.quickstart` render.+ <!-- clients:start -->+ <!-- install-links:start -->+ **Claude Code**+ ```+ claude mcp add --transport http mcphost https://mcphost.dev/mcp+ ```+ Docs: <https://code.claude.com/docs/en/mcp> (checked 2026-10-09)+ **Cursor**+ [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=mcphost&config=eyJ1cmwiOiJodHRwczovL21jcGhvc3QuZGV2L21jcCJ9), or add `https://mcphost.dev/mcp` to your MCP config directly.+ Docs: <https://docs.cursor.com/en/tools/mcp> (checked 2026-10-09)+ **VS Code**+ [Add to VS Code](vscode:mcp/install?%7B%22name%22%3A%22mcphost%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcphost%2Edev%2Fmcp%22%7D), or add `https://mcphost.dev/mcp` to your MCP config directly.+ Docs: <https://code.visualstudio.com/api/extension-guides/ai/mcp> (checked 2026-10-09)+ **Claude.ai**+ 1. Open Settings, then Connectors, then Add custom connector.+ 2. Name: mcphost
Showing the first 40 of each.
− time from signup to a tenant's first successful `host.tool_publish` is− token, shown once), `namespace`, and `endpoint`. Signup is rate-limited− to 5 per IP per hour. Pass `source` (e.g. `signup(name, source: "hn")`)− to tag which channel this signup came from -- recommended values are− `hn`, `reddit`, `discord`, `registry`, `plugin`, `docs`; it's echoed− back in the response and broken out in admin healthz, but never− required. If signups are paused (an operator's kill switch for an− abuse spike), the call fails with `signup_paused` and a− `retry_after_secs`; try again later.− this exchange carries a dead credential. `host.key_rotate` invalidates− here on -- e.g. `host.tool_publish`, `host.tool_call`. No reconnect or− 4. Publish a tool: `host.tool_publish(name, kind, spec)`. Call− `host.quickstart` first — its `starter_tool` is a ready-to-publish− `python` spec (reverses text, counts words) plus the exact− `publish_call`/`test_call` to run; the documented first publish is a− real tool, not a stub. Two real kinds: submit code (`python` — source− required, `args_schema`/`requirements` inferred if omitted) or wrap an− API you already use (`http` — url and method required, `args_schema`− inferred if omitted). `echo` (returns its arguments; spec is a JSON− Schema) is a stub for testing the pipes, not a real tool — it carries− `stub: true` in `tools/list`. Before publishing anything, dry-run with− `host.tool_publish({..., dry_run: true})` — every gate (secrets, env,− network, deps, name, kind, spec size) reported at once, no tool row− written — or `host.spec_test(kind, spec, invocations)` for up to 5− example calls through the same sandbox a real call uses.− 5. Call your tool. Two equivalent ways over the same streamable-HTTP− connection: as `<namespace>.<tool_name>` (its own entry in− `tools/list`), or `host.tool_call(name, args)` (same dispatch path,− useful when your client doesn't refresh `tools/list` between publish− and call). `host.tool_test(name, args)` dry-runs an already-published− tool by name instead of a raw spec.− 6. Check the plan and quota before you rely on volume:− `billing.plans()` — the plan catalog, works anonymously.− `billing.status()` — this tenant's plan and usage against each quota.− 7. Inspect and manage: `host.tool_list()`, `host.tool_logs(name)`,− `host.tool_remove(name)`, `host.usage(window)`,− `host.secret_set`/`host.secret_list()` (secrets stored AES-256-GCM− encrypted). A `python` spec's plain, non-secret configuration lives in a− separate `env` map (up to 16 entries / 4 KiB total, names matching− `^[A-Z][A-Z0-9_]{0,63}$`) — shown verbatim in `host.tool_test`, unlike+ time from signup to a tenant's first successful `host.tool.publish` is+ 1. Connect to `https://mcphost.dev/mcp` -- paste that URL into your MCP+ client's config. No signup call, no credentials, nothing to paste into+ a header.+ 2. Make your first call. `host.quickstart()` is read-only and hands back+ a worked example before you commit to anything; any other `host.*`/+ `billing.*` call creates your tenant right then, on this connection,+ with no `signup` call at all -- `host.whoami()` is the simplest:+ ```+ host.whoami()+ ```+ 3. Read `onboarding.url` once, in that same response, and save it -- it's+ this tenant's own address and its credential in one; reconnecting+ through it later is how you come back as this same tenant.+ `onboarding.memory_hint` names where agents conventionally keep it.+ 4. Publish a tool. A later call on this SAME connection without a key is+ refused, naming the tenant you already are -- reconnect through+ `onboarding.url` first, then call `host.tool_publish` there.+ `host.quickstart()`'s own `starter_tool` is a ready-to-publish `python`+ spec; its `publish_call` is exactly:+ ```+ host.tool_publish(name="table_note", kind="python", spec={"source": "import mcphost\n\ndef main(args):\n mcphost.table.create(name='quickstart_notes', columns={'note': 'text'})\n mcphost.table.append(table='quickstart_notes', rows=[{'note': args.get('note', '')}])\n return {'appended': 1}\n"})+ ```+ Two real kinds: submit code (`python` -- source required,+ `args_schema`/`requirements` inferred if omitted) or wrap an API you+ already use (`http` -- url and method required, `args_schema` inferred+ if omitted). `echo` is a stub for testing the pipes, not a real tool.+ Before publishing anything, dry-run with `host.tool_publish({...,+ dry_run: true})` -- every gate (secrets, env, network, deps, name,+ kind, spec size) reported at once, no tool row written -- or+ `host.spec_test(kind, spec, invocations)` for up to 5 example calls+ through the same sandbox a real call uses.+ 5. Call it -- `host.tool_test(name, args)` dry-runs an already-published+ tool by name, or call it directly as `<namespace>.<tool_name>` (its+ own entry in your `tools/list`):+ ```+ host.tool_test(name="table_note", args={"note": "hello"})+ ```+ 6. Share it with a teammate: `host.invite.create()` returns an+ `/i/<code>/mcp` URL -- whoever connects there and makes their own
Showing the first 40 of each.
Showing the first 40 of each.
We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect