# mcphost

Host your MCP tool over streamable HTTP in one command.

## signup

Explicit signup, for a client that cannot keep a session across calls: create a tenant and receive a bearer key and namespace. Unauthenticated. Most clients never need this -- any other host.*/billing.* call from a fresh connection creates a tenant implicitly and returns its own /u/<secret>/mcp URL in the response's onboarding field. Recommended here: pass handoff: true to receive a short-lived, single-use handoff_token instead of the raw key -- redeem it once with host.redeem to get the key, so a transcript of this call and the redeem call, if it leaks, carries a dead credential. The raw-key path (handoff omitted) stays fully supported.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `handoff` | boolean | no | Recommended: true to receive a handoff_token (redeem via host.redeem) instead of the raw key. Default false (raw key, unchanged). |
| `name` | string | yes | display name |
| `source` | string | no | Optional attribution tag for this signup (1-64 chars matching the pattern), e.g. "plugin", "www", "cli", "hn"; omit for none. Stored as the tenant's signup_source and counted in the signup-source funnel. Attribution only: whether a signup is real or synthetic is decided by the harness header and fleet IP, never by this value. "synthorg" is reserved for the discovery-chain harness. |

## host.quickstart

Return the shortest ordered sequence of calls to a working tool of `kind`, with your namespace and a filled-in example already substituted in, plus the current limits and a try_before_call table naming the one dry-run tool for each case. Read-only -- this call alone never creates a tenant. Call this before host.tool_publish if you're not sure what a spec should look like. Unauthenticated callers get a worked example with no tenant data; any other host.*/billing.* call creates a tenant on first use, no signup required.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `kind` | string | no | Which registered kind to return a worked example for, e.g. echo, http, python -- or a job-word alias (webhook, event, cron, ...) for its recipe. Omit to see every kind, alias and recipe name. |
| `retry_after_s` | integer | no | Seconds a rate-limited signup said to wait; an error's next_call carries it back here. Informational, never required. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.help.search

Search this host's own documentation and tool descriptions. Returns up to k hits (default 3, response under 2 KB), each with a snippet, the nearest runnable example and an anchor. Read-only, anonymous, no tenant data -- call it with the error text or import line you are stuck on before reading any docs file.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `k` | integer | no | How many hits to return (default 3). |
| `kind` | string | no | Only search the page of this registered kind (docs/kinds/<kind>.md). |
| `q` | string | yes | What you are stuck on: a tool name, an error code or message, an import line. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.help.get

Return the documentation section an anchor points at: its text (at most 4 KB) and its first fenced example. Pass an anchor exactly as an error, hint or help entry printed it, e.g. a rate_limited error's `anchor`. Read-only, anonymous, no tenant data. An unknown anchor is anchor_not_found with the nearest anchors in did_you_mean.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `anchor` | string | yes | A `<path>#<slug>` anchor, e.g. README.md#call-limits, as printed by an error's `anchor` field or a `host.help.search` hit. `<path>#top` and a bare `<path>` (e.g. docs/kinds/chain.md) both return that document's first section. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.errors

List every error code this host can return, each with its class (agent_fault | host_refusal | upstream_fault | plan_limit | host_fault), family, remedy, docs anchor, retryable flag and help_url -- the same object GET /errors.json serves. Pass code to get one row; a misspelt code answers tool_not_found with did_you_mean. Read-only, anonymous, no tenant data.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `code` | string | no | An error code, e.g. tool_exception. Omit to list every code. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.redeem

Exchange a signup(handoff: true) handoff_token for the tenant key it was issued for. Single-use: a second redemption fails with handoff_token_redeemed; past its expiry it fails with handoff_token_expired. Unauthenticated -- the token itself is the proof.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `handoff_token` | string | yes | The handoff_token signup(handoff: true) returned. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## billing.plans

The plan catalog (price and quotas per plan) and whether Stripe billing is configured on this host. Anonymous callers get the same answer as tenants.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.whoami

Return the calling tenant's identity, including key_age_s and key_rotated_at for auditing credential hygiene.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.tool_publish

Publish a tool of a registered kind (e.g. python, http or echo; the kind argument's enum lists them all) under this tenant's namespace. Call host.quickstart(kind) first for a filled-in example spec and the full publish-to-call sequence. Python's sandbox API: import mcphost (mcphost.table, mcphost.state, mcphost.docs). Name must match ^[a-z][a-z0-9_]{1,40}$; a rejection names the failing field and a corrected example. Try host.tool_test before a real call.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean | no | return the diff this call would make and commit nothing |
| `force` | boolean | no | Publish a chain whose mapping can never resolve (verdict will_fail) anyway; the response's gates entry then carries forced: true. Default false. |
| `kind` | string | yes | Which registered kind to publish under: one of this field's `enum` (host.quickstart with no kind lists every kind, alias and recipe). A job-word alias in the enum also resolves here -- webhook-style words for an inbound webhook, cron-style words for a timed run -- both resolve to kind http and the descriptor comes back with resolved_from naming the alias you asked for. Error contract per kind: chain (families: compose, tool_state; source_line: no); echo (families: none; source_line: no); http (families: upstream; source_line: no); jq (families: tool_code, tool_limit; source_line: no); js (families: tool_code, tool_limit, upstream, compose, tool_state; source_line: yes); python (families: tool_code, tool_limit, upstream, compose, tool_state; source_line: yes); wasm (families: tool_code, tool_limit; source_line: no). |
| `name` | string | yes | Local name for the new tool; must match ^[a-z][a-z0-9_]{1,40}$. |
| `scopes` | array | no | OAuth scopes a token must carry (directly, or via mcp) to reach this tool: catalogued names from host.oauth.scopes, or the built-ins read/write. At most 8. Omit (or [] ) to require only mcp -- unaffected by any catalog. |
| `spec` | object | yes | The kind-specific spec object; see host.quickstart(kind) for a filled-in example. For python's network field: "none" (every plan), "public" (requires the pro plan), "egress" (requires the pro plan) -- a tool's own code reaches this tenant's data with `import mcphost` (mcphost.table, mcphost.state, mcphost.docs, mcphost.lineage) even when network is "none". |
| `tags` | array | no | Catalog tags: at most 16, each 1-32 chars of a-z, 0-9 and '-', stored lower-case and deduped. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |
| `visibility` | string | no | Who may find the tool (default private). unlisted: callable by full name and readable by host.catalog.get, never listed. public: listed in the catalog, and refused (untested_tool_cannot_be_public) unless the current version passes its dry run first. Use host.tool_share for group or url. |

## host.tool_call

Invoke a tool this tenant has already published, by its local name -- the same real, metered call as calling it directly by its namespaced name (<namespace>.<name>), for a session that has no way to see its own namespaced tool name yet. Unlike host.tool_test, this counts toward host.usage and appears in host.tool_logs. Pass async: true for a tool that needs more than the call deadline: returns {run_id, status: "queued"} immediately instead of running inline -- see host.runs.get/wait. Pass version to pin the call to one of host.tool_history's versions instead of whichever is current.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `args` | object | no | Arguments to pass, validated against the tool's own args_schema. |
| `async` | boolean | no | Run as a job instead of inline: returns {run_id, status} within ~50ms under the plan's job_max_s deadline; default false. |
| `budget` | object | no | Lowers this call's budget below the plan default: any subset of max_child_calls, max_est_tokens, max_tool_latency_ms, max_wall_ms. A value above the plan's own ceiling is a validation error naming it; see host.runs.get's progress.budget. |
| `dry_run` | boolean | no | Ask instead of call: runs every admission check the real call runs, dispatches nothing, writes no call row, and returns {fits, units, remaining, refusal}; same as host.estimate. Default false. |
| `name` | string | yes | Local name of the tool to invoke. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |
| `version` | integer | no | Pin the call to this version instead of whichever is current; see host.tool_history. An unknown version is an argument error. |

## host.tool_test

Dry-run a published tool by name: verdict (pass/will_fail/unverifiable), writes rolled back (dry_run); see host.quickstart.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `args` | object | no | Arguments to pass, same shape as a real call. Omit to run one example generated from the tool's args_schema; the response then carries example_args (what ran) and untyped (keys the schema gave no type for). |
| `fixtures` | object | no | Chain tools only: {"<step number or tool name>": <recorded result>}. A fixtured step executes nothing (no upstream call); later mappings resolve against it. host.runs.get(run_id, as_fixtures=true) exports a real run in this shape. |
| `name` | string | yes | Local name of the already-published tool to dry-run. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.state.set

Write one key in this tenant's key-value state namespace; value may be any JSON value. Overrun of the plan's state_bytes_max quota fails with state_quota_exceeded and writes nothing.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean | no | return the diff this call would make and commit nothing |
| `end_user` | ['string', 'null'] | no | "self" for the caller's own verified end-user identity, an explicit subject (only when this call carries no end-user identity of its own), or omit/null for the tenant-wide value. |
| `key` | string | yes | Key to write in this tenant's key-value state namespace. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |
| `value` | ['string', 'number', 'integer', 'boolean', 'array', 'object', 'null'] | yes | Any JSON value to store under key. |

## host.state.get

Read one key from this tenant's key-value state namespace. Returns found: false (not an error) if the key was never set.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `end_user` | ['string', 'null'] | no | "self" for the caller's own verified end-user identity, an explicit subject (only when this call carries no end-user identity of its own), or omit/null for the tenant-wide value. |
| `key` | string | yes | Key to read from this tenant's key-value state namespace. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.state.list

List keys (with their current values) in this tenant's key-value state namespace, optionally filtered by prefix.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `end_user` | ['string', 'null'] | no | "self" for the caller's own verified end-user identity, an explicit subject (only when this call carries no end-user identity of its own), or omit/null for the tenant-wide value. |
| `limit` | integer | no | Max keys to return; default 100. |
| `prefix` | string | no | Only list keys starting with this prefix; default: all keys. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.tool_share

Share one of this tenant's published tools with everyone (visibility: "public") or with a named group this tenant owns (visibility: "group", group: <name>). The tool keeps running in this tenant's own sandbox with this tenant's own secrets; a caller reaches it as <this tenant's namespace>.<name>.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `description` | string | no | Catalog-facing blurb; shown by host.catalog.search/get. |
| `expose_spec` | boolean | no | Default false. When true, any tenant this tool is shared with may call host.tool_spec_shared to read its (redacted) spec. |
| `group` | string | no | Required when visibility is "group"; must already exist (host.group.create). |
| `name` | string | yes | Local name of the tool to share. |
| `rate_per_hour_per_caller` | integer | no | Optional cap (1..10000) on calls per hour from any one grantee. |
| `scopes` | array | no | What the grantee may do: call, read_source, fork. Default ["call"] (["call","read_source"] with expose_spec: true). url shares allow only call. |
| `tags` | array | no | Catalog tags merged into the tool's existing ones (same rules as host.tool_publish). |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |
| `visibility` | string | yes | "public" (needs a passing dry run of the current version), "unlisted", "group" or "url". |

## host.feedback.send

Tell the host's humans what you needed: kind, up to 2048 bytes of text, optional context. Works before signup. Returns feedback_id.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `context` | object | no | Optional, at most 1 KB: tool, error_code, free. |
| `kind` | string | yes | What you are telling the host. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |
| `text` | string | yes | What you needed or saw; 1 to 2048 bytes after trim. |

## host.feedback.list

List your own feedback (newest first, status received). Anonymous callers get [].

| Parameter | Type | Required | Description |
|---|---|---|---|
| `limit` | integer | no | Max rows, up to 100; default 20. |
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## host.resume

Resume where you left off in one call (at most 4096 bytes): your tools with each one's last call status, unfinished runs, your newest notes, what changed on the host since your last call, and one suggested next_call. truncated names any section that was cut -- call that section's own tool (host.tool.list, host.runs.list, host.note.list, host.changelog) for the rest. Read-only. An unauthenticated caller gets the onboarding block and creates no tenant.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `tenant_key` | string | no | The key `signup` returned. Optional on the connection that ran signup (or host.redeem): that connection is already bound to the tenant it created, so later calls on it need no tenant_key. On a connection with no Authorization: Bearer header and no prior signup, omitting it implicitly signs that connection up as a brand-new tenant and runs this call as it -- see host.whoami's source field. When both a header and this argument are present, the header wins. |

## Plans

- **free**: Early access: full API, 50 tools per tenant, 500 calls/day, community support. No card required.
- **pro**: Pro: 50 tools, 50,000 calls/month included then usage-billed per call. $19/month.
