MCP
Serve the unseal-space tools to an MCP Host over stdio, from the same standalone binary.
unseal-space mcp serve turns the CLI you already installed into an
MCP server. It publishes 34 tools named after
business intent — create a Project, send a turn, publish a Site, buy a domain —
with typed arguments, stated permissions and standard annotations, so a Host can
show a user what a call will do before it happens.
There is no second thing to install. It is a mode of the same executable.
Configure your Host
The Host launches the process and owns its lifecycle. Most Hosts take a command, arguments and environment:
{
"mcpServers": {
"unseal-space": {
"command": "unseal-space",
"args": ["mcp", "serve"],
"env": {
"UNSEAL_SPACE_API_KEY": "…"
}
}
}
}Run unseal-space mcp serve --help for the authoritative option list. Running it
by hand is only useful to check that it starts: stdin and stdout carry MCP
JSON-RPC and nothing else, so there is nothing to read in a terminal.
Startup validates only what is local — the credential's presence, the API URL, the read allowlist. It never calls the platform, so discovery works offline and an outage shows up as a failed tool call rather than a server that will not start. If startup does fail, stdout stays empty and one redacted JSON line explains why on stderr.
Environment
| Variable | Meaning |
|---|---|
UNSEAL_SPACE_API_KEY | The Agent Credential. Required. |
UNSEAL_SPACE_API_URL | API base URL. Defaults to https://api.unseal.space |
UNSEAL_SPACE_MCP_STATE_DIR | Where resumable Task state lives. Defaults to a per-user state dir. |
UNSEAL_SPACE_MCP_VALUE_<REF> | A value a tool may reference by name. Never passed as an argument. |
BUILDER_API_KEY and BUILDER_API_URL still work as deprecated aliases and
emit one notice on stderr.
Secrets and PII are passed by reference
A tool argument becomes model context, an MCP log line and Host history all at once. So values never travel as arguments:
{
"env": {
"UNSEAL_SPACE_API_KEY": "…",
"UNSEAL_SPACE_MCP_VALUE_DATABASE_URL": "postgres://…",
"UNSEAL_SPACE_MCP_VALUE_REGISTRANT": "{\"name\":\"…\",\"email\":\"…\"}"
}
}The agent then calls environment_set with valueRef: "DATABASE_URL", or
registered_domain_purchase_complete with contactRef: "REGISTRANT". References
resolve only within the UNSEAL_SPACE_MCP_VALUE_ prefix — naming
AWS_SECRET_ACCESS_KEY reads nothing. Values never appear in results or
diagnostics, not even in the message that says a reference was wrong.
Local files are off unless you grant them
Two tools read local files: attachment_upload and environment_import_dotenv.
Neither can read anything until you name a directory:
{ "args": ["mcp", "serve", "--allow-read", "/home/me/uploads"] }--allow-read is repeatable and takes absolute paths. There is no
current-working-directory default. Each requested path is resolved to its real
path and must still sit inside an allowed directory, so .. segments and
symlinks — to a file, or aliasing a whole directory — are refused. MCP Roots may
inform a model but never widen this grant. With no --allow-read, both tools
stay listed and return LOCAL_FILE_ACCESS_DISABLED.
Long operations
Sending a turn and publishing a Site are remote operations that outlive a tool
call, so each is split in two: message_send / message_wait and
deployment_create / deployment_wait. The creating call returns its
identifier immediately, which is what makes a timeout survivable.
A wait blocks for at most 60 seconds and then returns a successful pending
result carrying the identifier — and, for messages, a cursor to resume from. A
local deadline is not a remote failure; the work is still running.
Task state is kept in a per-user SQLite database, isolated per Agent Credential and expired after 24 hours. Rotating a credential makes its old handles inaccessible while the Message or Deployment itself remains resumable by its platform identifier. If that database cannot be opened, the server refuses to start rather than pretending to be durable.
What the tools guarantee
- The catalog never changes shape. All 34 tools are listed in a fixed order whatever your credential allows. Descriptions state the permission each one needs, so a denial can be explained and repaired instead of guessed at.
- The platform is the only authority. Annotations are hints. A Host that skips a confirmation does not bypass platform validation or gain permissions.
- High-impact calls repeat their target. Publishing repeats the Project id,
restoring repeats the commit, unbinding and disconnecting repeat the domain,
and switching a domain's yearly renewal off repeats it too — that ends in a
domain you lose, on a date after which nothing can bring it back, while
switching renewal on is a schedule you can still change.
There is no unbound
confirm: true, and buying a domain goes through a server-issued challenge that fixes the exact domain and maximum price. - You choose idempotency keys. Every retriable mutation requires one; the server never invents a key on your behalf.
- Failures are results, not disconnections. Bad arguments, denied
permissions and platform refusals come back as structured errors with
code,why,fixand typed follow-up actions. Only protocol-level problems become JSON-RPC errors.
Protocol support
The server speaks MCP core revisions 2024-10-07 through 2025-11-25 and the
modern 2026-07-28. The opening exchange selects the era; the catalog is the
same either way.
A modern client that declares io.modelcontextprotocol/tasks in the
per-request capabilities of a message_wait or deployment_wait call gets a
durable Task back instead of a bounded pending: poll it with tasks/get
at the advertised pollIntervalMs, and the terminal poll carries the result
the call would have returned. tasks/cancel records cooperative intent — it
does not claim the Message or Deployment stopped. The extension is advertised
in server/discover.
Everyone else — clients that do not declare it, and every legacy client — keeps
the bounded, resumable pending described above. No tool requires Tasks.