mcphost
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcphostSign me up for a tenant and create an echo tool"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcphost
Ship an MCP tool, not a deployment project.
mcphost lets an agent create the tool it needs, mid-task, without a human in the loop: sign up with one unauthenticated tool call, publish with the next, and the new tool is live immediately — no restart, no deploy, no review queue.
Measured (panel run 0.26.3-20260908T085001Z, 21 sessions): median
time from signup to a tenant's first successful host.tool_publish is
30.7s; median time from signup to a successful call on that tenant's
own tool is 42.4s.
Quickstart for agents
Connect to the endpoint and call
tools/listwith no credentials. The only tool offered issignup.Call
signup(name). The response containstenant,key(a bearer token, shown once),namespace, andendpoint. Signup is rate-limited to 5 per IP per hour. Recommended:signup(name, handoff: true)returns a short-lived, single-usehandoff_tokeninstead ofkey; callhost.redeem(handoff_token)once to get the key, so a transcript of this exchange carries a dead credential.host.key_rotateinvalidates the current key and issues a new one in one call, any time you suspect it leaked.Reconnect with
Authorization: Bearer <key>. Thehost.*control plane is now available.Publish a tool:
host.tool_publish(name, kind, spec). Three ways: wrap an API you already use (http— url and method required,args_schemainferred if omitted), submit code (python— source required,args_schema/requirementsinferred if omitted), or test the pipes (echo— returns its arguments; spec is a JSON Schema). Dry-run first withhost.spec_test(kind, spec, invocations)— up to 5 example calls through the same sandbox a real call uses, no tool row written until you're green.Call your tool. Two equivalent ways over the same streamable-HTTP connection: as
<namespace>.<tool_name>(its own entry intools/list), orhost.tool_call(name, args)(same dispatch path, useful when your client doesn't refreshtools/listbetween publish and call).host.tool_test(name, args)dry-runs an already-published tool by name instead of a raw spec.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.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). Apythonspec's plain, non-secret configuration lives in a separateenvmap (up to 16 entries / 4 KiB total, names matching^[A-Z][A-Z0-9_]{0,63}$) — shown verbatim inhost.tool_test, unlikesecrets, which stay redacted there.
mcphost serve is a streamable-HTTP MCP server, stateless per the 2026-07-28
specification, on which an agent signs up with one unauthenticated tool call,
receives a tenant key, and then owns a namespace of tools it publishes, lists,
inspects and removes through further tool calls. There is no web page. The
operator administers tenants and reads metering through admin.* tools on
the same endpoint. Tool execution kinds (REST wrappers, code) are separate
PRDs; this one ships the endpoint, tenancy, the control plane, the Kind
trait, and a built-in echo kind so the harness can measure the bootstrap
path end to end.
The machine-readable summary lives at
/llms.txton the production endpoint — generated from the same source as the quickstart above (docs/agent-quickstart.md,scripts/gen-agent-docs.sh).
Built from PRD-mcphost-endpoint.md (vision: visions/mcp-host.md).
Related MCP server: Adaptive Agent Harness
Recent
v0.56.0 — agent consent:
contact_policy: contactsnow has a middle setting between open and closed — a stranger may send onehost.agent.contact_requestand nothing else until the recipient callshost.agent.contact_accept;host.agent.mute/unmutekeep a sender's messages arriving without waking the agent, andhost.msg.send(urgent=true)bypasses mute (never block, never aclosedpolicy) under the per-planurgent_per_daycap.v0.11.0 —
host.tool_publishreports every simultaneously-invalid field at once (data.errors, each with its ownfield/expected/example) instead of one rejection per attempt; each kind's example spec/blurb and the new "Kinds" section below both render fromdocs/kinds/*.md, checked to match bytests/publishfirsttry_ac06_docs_shared_source.rs.v0.4.0 —
args_schema(and, forpython,requirements) is now optional on thepythonandhttpkinds: when absent, the host derives it deterministically and offline from the source/templates the tenant already wrote (src/kinds/infer.rs). An explicitargs_schemais used unchanged.v0.1.2 —
synthorg consume --preflightnow has a real integration test (AC12); theKindconformance suite moved totests/ac17_kind_conformance.rs;host.registry_publish+GET /.well-known/mcp/<namespace>/server.jsonare implemented behind the--registry-urlflag (AC19, see "Registry publish (P1)" below).
Install
cargo install --path .Or build locally:
cargo build --release
./target/release/mcphost serveEnvironment contract
Variable | Meaning | Default |
| Directory holding |
|
|
|
|
| URL returned by |
|
| Bearer key that unlocks | unset (admin tools unreachable) |
| Passphrase, SHA-256-derived into an AES-256 key for tenant secrets | dev default (set a real one in production) |
|
|
|
| Enables | unset (registry-publish disabled) |
| Overrides the per-source-IP |
|
mcphost migrate applies pending SQL migrations and exits. mcphost version
prints the version and exits. mcphost serve --registry-url <url> is the
CLI-flag form of MCPHOST_REGISTRY_URL above.
Registry publish (P1)
Off by default. Once --registry-url / $MCPHOST_REGISTRY_URL names a
registry API base (e.g. https://registry.modelcontextprotocol.io):
The operator verifies a tenant's domain namespace by whatever method they trust (the PRD leaves the verification METHOD itself — DNS vs HTTP record — as an open question owned by Joe; this crate does not implement one) and records the outcome with
admin.tenant_verify_namespace:admin.tenant_verify_namespace(tenant="t_xxxxxxxx", domain_namespace="io.github.example.myserver").That tenant can then call
host.registry_publish()(no arguments): it POSTs aserver.jsondocument (name/description/version/remotes: [{type: "streamable-http", url}]) to<registry-url>/v0/publish, and the same document becomes servable, unauthenticated, atGET /.well-known/mcp/<namespace>/server.json.host.registry_publishrefuses with a distinct, machine-readable error indata.error_code:registry_disabled(flag off),namespace_unverified(step 1 not done for this tenant), orregistry_rejected(the registry API answered non-2xx).
Kinds
Every registered kind's minimal example spec, below, and host.tool_publish's
on-wire description (visible from tools/list before signup) are both
rendered from the same docs/kinds/*.md files (PRD-mcphost-publish-first-try
requirement 6) -- tests/publishfirsttry_ac06_docs_shared_source.rs
regenerates this section from those files and fails CI if it's drifted from
what's checked in below. Call host.quickstart(kind) for the same example
with your own namespace already filled in.
echo
spec.schema is any JSON Schema; a call echoes back the arguments it was given, validated against it.
Example spec:
{
"schema": {
"properties": {
"msg": {
"type": "string"
}
},
"required": [
"msg"
],
"type": "object"
}
}Example call arguments:
{
"msg": "hi"
}http
url must be an absolute https URL; method and url are the only required fields -- args_schema is inferred from the url/header/body templates when omitted.
Example spec:
{
"method": "GET",
"url": "https://api.example.com/items/{{id}}"
}Example call arguments:
{
"id": "123"
}python
only source is required -- args_schema and requirements are both inferred from it (tool-infer, v0.4.0); source must define main(args).
Example spec:
{
"source": "def main(args):\n return {\"doubled\": args[\"n\"] * 2}\n"
}Example call arguments:
{
"n": 3
}wasm
component is a base64-encoded WebAssembly component (component-model, not a core module) exporting call: func(args: string) -> result<string, string>; args_schema is optional (defaults to accepting any object).
Example spec:
{
"component": "AGFzbQEAAAAA"
}Example call arguments:
{
"msg": "hi"
}Python spec-language notes
PRD-mcphost-python-kind-runtime (AC6): the AST-check that gates
host.tool_publish accepts assignment expressions (:=, PEP 572) in
general -- CPython has parsed them since 3.8, and mcphost's publish-time
check and the tool's own runtime both compile source with the same
CPython grammar, so there is no mcphost-added restriction to relax. The
one thing that is rejected is a restriction Python's own grammar
enforces: an assignment expression's target must be a plain name.
(obj.attr := 1) and (d[key] := 1) are both invalid Python syntax
(cannot use assignment expressions with attribute / ...with subscript) and would fail identically whether or not mcphost validated
them first -- the tool's own main(args) would refuse to even parse.
Because this is executor-level, not validator-level, there is nothing for
mcphost to loosen; the fix here is that the publish-time rejection now
names the construct and the accepted alternative in one sentence (assign
to a plain name first, then set the attribute/subscript in a separate
statement) instead of leaving CPython's bare grammar message to speak for
itself.
Call limits
PRD-mcphost-call-limits-honest: every limit here is the one the code
enforces -- tests/limits_ac06_quickstart_docs_match_constants.rs checks
this section and www/llms.txt's "Limits and pricing" section against the
same constants host.quickstart's limits object reads.
Call timeout: 30 s by default, or your own
timeout_sup to 60 s max -- a python spec that declarestimeout_sgets exactly that deadline (bounded by the 60 s host maximum), not a shorter one applied silently underneath it.call_timeoutnames the deadline that actually applied.Output size: tool output at most 1 MiB. Over the cap returns
tool_output_too_largenaminglimit_bytesand theactual_bytesproduced, never a baretool_output_invalidparse failure.Request body: at most 1 MiB (HTTP 413 over that -- see
tests/ac16_request_body_too_large.rs; the "2 MiB" in that AC's own description is the oversized test payload used to prove the 1 MiB cap, not the cap itself).Concurrency: 20 concurrent calls host-wide; per tenant, 4 per tenant on the free plan (10 on pro). A refusal past your own tenant's cap is
capacitywithscope: "tenant"and aretry_after_ms; past the host-wide cap it'sscope: "host".Sandbox process cap: a python tool's sandbox allows at most 64 live processes; a fork past that fails with the structured
tool_process_limit, not a silent hang or an opaque OS error.
Metered overage (billing emit-meter)
PRD-mcphost-metered-overage: pro tenants' successful calls past the plan's
50,000 included calls/month bill themselves through Stripe's
mcphost_tool_calls meter and its graduated metered price. Set these
env vars from ~/.config/mcphost/stripe-objects.json (unset means the
same v0.14.0 behavior -- no metering, no meter_lag):
MCPHOST_STRIPE_METERED_PRICE_ID-- the metered price idbilling.checkoutattaches alongside the base price.MCPHOST_STRIPE_METER_EVENT_NAME-- defaults tomcphost_tool_calls.
Then run mcphost billing emit-meter on a timer (every five minutes is the
shipped default): it reads pro tenants' unemitted ok calls, POSTs one
Stripe meter event per tenant (chunked at 100 events/request), ledgers each
batch, and advances its own high-water mark only once every event in the
run has been accepted -- safe to rerun after a crash or a failed POST (see
src/metering.rs's doc comment for the replay/idempotency contract).
Install the shipped systemd user units (~/.config/systemd/user/,
matching this host's other mcphost-* units):
cp deploy/mcphost-emit-meter.service deploy/mcphost-emit-meter.timer \
~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mcphost-emit-meter.timermcphost-emit-meter.service reads ~/.config/mcphost/emit-meter.env (via
EnvironmentFile=-, so a missing file is not an error) for
MCPHOST_DATA_DIR / MCPHOST_STRIPE_SECRET_KEY / the two vars above.
Both unit files pass systemd-analyze verify --user (AC9;
tests/metering_ac09_deploy_units_verify.rs).
/healthz's meter_lag field (present only when MCPHOST_STRIPE_METERED_PRICE_ID
is set) is the count of pro-tenant ok calls still above the high-water
mark -- watch it for emission health at a glance.
Synthetic tenants (admin.*_synthetic)
PRD-mcphost-synthetic-flag: a tenant a test harness creates carries a
free-form synthetic label (e.g. synthorg:<run_id>) from signup onward,
set by the harness sending x-mcphost-synthetic: <label> on its signup
call -- no behavior change, metadata only. /healthz's tenants_real /
tenants_synthetic split, and admin.tenants' synthetic filter
(true/false/all, default all), read this column so
synthorg candidates --measure can exclude panel traffic from "real
tenant" evidence.
Backfilling the existing census (every tenant predates this column, so
all load with synthetic: null until tagged): use admin.tenants_set_synthetic,
previewed with dry_run: true before the dry_run: false that applies it.
The recipe this host's own census used:
admin.tenants_set_synthetic(name_like: 'joe-%', label: 'operator', dry_run: true)
admin.tenants_set_synthetic(name_like: 'joe-%', label: 'operator', dry_run: false)
admin.tenants_set_synthetic(name_like: '%', label: 'synthorg:backfill-20260906', dry_run: true)
admin.tenants_set_synthetic(name_like: '%', label: 'synthorg:backfill-20260906', dry_run: false)Run the joe-* pass first -- the second call's broader % pattern would
otherwise overwrite those rows' label too, since a tenant re-tagged by a
later call simply gets the later label (there is no "already labeled, skip"
guard by design: retagging is how a label ever gets corrected). A single
tenant can be corrected at any time with admin.tenant_set_synthetic(tenant, label) (label: null clears it).
Acceptance
Every P0 acceptance criterion is paired with a real cargo test (integration
tests under tests/ spin up the server on an ephemeral port against a temp
$MCPHOST_DATA_DIR), except AC11 which is hardware-dependent and is
recorded as a smoke result below.
Sandbox suite: user namespace requirement
The python kind's sandbox suites (tests/sandboxready_*, python_ac*,
infer_ac*, warmpool_ac*, ac17_kind_conformance) spawn real bwrap/
unshare isolation and need unprivileged user namespaces
(unshare --user --map-root-user -- true must succeed) to run for real. If
your box denies that (Ubuntu's default AppArmor policy on some kernels, some
container runtimes), running cargo test fails loudly by design outside
CI, naming the fix: sysctl kernel.unprivileged_userns_clone=1 on older
kernels, or sysctl kernel.apparmor_restrict_unprivileged_userns=0 on
Ubuntu 24.04+. See sandbox::require_user_namespaces_or_ci_skip's doc
comment for the full contract, and .github/workflows/ci.yml for how the
hosted CI runner grants the same capability (PRD-mcphost-ci-sandbox-coverage)
instead of silently skipping.
CI runs these suites as their own sandbox job, in parallel with the gate
job that carries static analysis and everything else — once the suites stopped
skipping, a single cargo test --workspace step measured 313–336 s against a
300 s budget . Which SUITE BINARIES go
where is derived, not hand-listed: scripts/ci-test-partition.sh core|sandbox
classifies every tests/*.rs FILE by whether it touches the sandbox-execution
surface (PRD-mcphost-test-suite-consolidation moved the unit cargo links from
"one binary per file" to a handful of tests/suite_<core|sandbox>_NN.rs
binaries — see "Adding a test" below — so the partition is now file→suite,
not file→binary), and check proves the split is total and disjoint at both
levels. Both jobs then fail on any capability-skip in their log, so a file
filed into the wrong half turns CI red rather than passing vacuously.
Adding a test
tests/*.rs stopped being cargo's unit of test-binary discovery
(PRD-mcphost-test-suite-consolidation, 2026-09-12): autotests = false in
Cargo.toml, plus a handful of generated tests/suite_<core|sandbox>_NN.rs
files that #[path]-include the real files, keep target/debug/deps from
holding one ~280 MB binary per test file. Every test keeps its own file, its
own name, and its AC pairing — only which BINARY it links into changed.
To add a test: drop tests/<name>.rs in as always (same naming convention:
<prefix>_ac<N>_<description>.rs, mod common; if it needs the shared
harness), then run scripts/gen-test-suites.sh to fold it into a suite (or
just let CI tell you — scripts/gen-test-suites.sh --check, wired into
ci-test-partition.sh check, fails naming the exact file if you forget). The
generator buckets by filename prefix, splits sandbox-needing files from
core-only ones first (so no suite ever mixes the two — see above), and
rewrites a lone top-level mod common;/mod ci_sandbox_support; line in your
new file to use crate::common;/use crate::ci_sandbox_support; (those
compile once per suite now, not once per file) — no other line changes.
Never hand-edit a tests/suite_*.rs file; it is fully regenerated.
Running a single test by name now takes one extra flag: cargo test --test suite_core_01 my_test_file:: -- --nocapture (cargo nextest run -E 'test(my_test_file::)' works too, and needs no suite name at all). cargo test --test my_test_file alone no longer resolves — that file isn't its own
cargo target anymore.
AC | Requirement | Test |
1 (P0) | Unauthenticated |
|
2 (P0) |
|
|
3 (P0) | Tenant |
|
4 (P0) | Publish, then list, then call round-trips |
|
5 (P0) | Cross-tenant isolation: B can't see or call A's tool |
|
6 (P0) | Remove a tool: omitted from list, |
|
7 (P0) |
|
|
8 (P0) |
|
|
9 (P0) | 6th signup/hour/IP is |
|
10 (P0) | Unregistered kind / invalid name / oversized spec each fail distinctly, nothing written |
|
11 (P0, non-functional) | 200 concurrent |
|
12 (P0) |
|
|
13 (P0) | Mismatched |
|
14 (P0) | Unwritable database: |
|
15 (P0) | A call that never completes times out at the deadline, future dropped |
|
16 (P0) | A request body over the 1 MiB cap (proven with a 2MiB body) is rejected with HTTP 413 |
|
17 (P0) |
|
|
18 (P1) |
|
|
19 (P1) |
|
|
Related fleet work
mcp-core— the reusable stdio JSON-RPC 2.0 MCP-server core (Tooltrait +serve_stdio) other wintermute MCP servers build on. Not reused here:mcphostis a streamable-HTTP server (rmcp), not a stdio server, and its tool surface is dynamic (per-tenant, DB-backed) rather than the staticTooltraitmcp-corewraps. Cited per the PRD's technical considerations as related, not shared, code.
License
Dual-licensed under MIT OR Apache-2.0 — see LICENSE-MIT and
LICENSE-APACHE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-design pattern guidance via 5 hosted read-only tools; Streamable HTTP, no auth, one Release.
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
The cloud for agents. Tools for AI agents to register, build, and deploy other agents. Zero human required.
- AxiomOAuthcom.axiomide
The marketplace where agents don't just use tools — they build, publish, and compose new ones.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables building agent-ready APIs that expose tools as both HTTP and MCP endpoints from a single server definition, with automatic OpenAPI, discovery docs, and interactive API reference.5Apache 2.0
- AlicenseNot gradedqualityBmaintenanceMCP server giving agents a persistent IPython workbench and a brokered RLM engine for durable, stateful computation. Offers 30 tools for bounded model calls, artifacts, and receipts with host-owned authority.1MIT
- FlicenseNot gradedqualityAmaintenanceEnables MCP-compatible AI clients to invoke CLI-driven agent tools over Streamable HTTP, including shell execution, file operations, patching, image viewing, web search, and nested agent tasks, with permission modes and real-time progress streaming.-
- FlicenseNot gradedqualityCmaintenanceEnables agents to securely discover and invoke a centrally governed catalog of tools from distributed internal and external providers, with policy enforcement, quotas, inspection, and audit controls.-