Managed remote MCP server for your AI agents
Zerolith runs a managed, remote MCP (Model Context Protocol) server. Connect Claude Code, Cursor, VS Code or any compatible client: your agent writes, deploys, invokes and administers your serverless functions hosted in France. You have no MCP server of your own to host, and no API key to hand it.
What it is
The Model Context Protocol is the standard by which an assistant discovers and calls external tools. A remote MCP server is reachable over HTTP: nothing to install, nothing running on your machine, and the same connection works from anywhere you authenticate.
Zerolith's MCP server runs on exactly the same code as the REST API and the web app. There is no privileged path reserved for agents, so an MCP tool can never do something your account itself cannot.
https://zerolith.io/mcp
OAuth discovery document (RFC 9728): https://zerolith.io/.well-known/oauth-protected-resource
Connecting a client
The transport is HTTP. Point your client at the URL above; OAuth 2.1 authorization kicks in on the first connection, in your browser.
Claude Code
One command. Your browser opens for authorization, then the tools show up in the session.
claude mcp add --transport http zerolith https://zerolith.io/mcp
Cursor, VS Code and other clients
File-configured clients accept this block; the file name and location vary from one client to the next.
{
"mcpServers": {
"zerolith": {
"type": "http",
"url": "https://zerolith.io/mcp"
}
}
}Authentication and revocation
Access goes through OAuth 2.1: no API key to paste into a client config, so no long-lived secret sitting in a file.
- Dynamic client registration (RFC 7591): your agent registers itself. There is no application credential for you to create, copy or store anywhere.
- PKCE with S256, mandatory: an intercepted authorization code is useless without the verifier, which never leaves your agent.
- Audience-bound tokens (RFC 8707): a token issued for zerolith is worth nothing elsewhere, and a token issued for another service is worth nothing here. Even stolen, it does not open the wrong door.
- Refresh-token rotation: each renewal invalidates the previous one, so a stolen token that is replayed announces itself.
- Two scopes, no more: mcp:read to look, mcp:write to act. A requested scope we do not recognise is refused, never silently widened.
Revocation: changing your password immediately invalidates live MCP grants (refresh tokens and unused authorization codes), on top of your web sessions.
What an agent can do
Connecting an agent delegates actions, not your secrets. Here is how far it can go, and what it will never see.
- It acts only inside your account. Every tool is bounded to your own resources: asking for the id of a function you don't own answers "not found" and never "forbidden". We don't even confirm it exists.
- Your API keys stay invisible. They are stored hashed and shown once, at creation, in the dashboard. No MCP tool returns them, and the agent doesn't need them: it authenticates with its own token.
- So do your secrets. A secret environment variable is written to the cluster and never read back: the agent can create, replace or delete it, but not read its value. Neither can you, for that matter.
- Your database token and your storage keys don't travel either. The agent can create the database, attach it to a function, run your migrations or trigger an export without ever receiving the credential that opens it.
- State-changing actions declare themselves. Every tool states whether it writes, which lets your MCP client ask you to confirm before letting the agent act.
You cut access whenever you decide, from the MCP page of your dashboard. Revocation immediately invalidates the access token already issued, not just its renewal: otherwise an agent would keep control until the token expired while the dashboard said "disconnected". Resetting your password also cuts every live MCP session.
An agent can also manage your database end to end, without ever seeing its credentials →
The tools it exposes
41 tools, all scoped to your account. The descriptions below are the ones the server advertises to your client, word for word: the contract the agent reads.
| Tool | Scope | Description |
|---|---|---|
| whoami | mcp:read | Return the authenticated account and credit balance. |
| get_catalog | mcp:read | List languages, size presets, deploy defaults, and pricing. |
| list_functions | mcp:read | List all of the caller's deployed functions. |
| get_function | mcp:read | Get one function by id (config, URL, attached env vars, and current source code — read this before update_function to edit code in place). |
| deploy_function | mcp:write | Deploy a new function. Requires a verified account. Pass `database` (a name) to auto-create-or-reuse a private database and attach it — the function code then just reads DATABASE_URL / DATABASE_AUTH_TOKEN (requires databases enabled). Pass kind='migrate' for a schema migrator: it runs once on demand and has no URL at all. |
| update_function | mcp:write | Update a function (code and/or config). Requires a verified account. |
| delete_function | mcp:write | Delete a function. Requires a verified account. |
| invoke_function | mcp:write | Invoke a deployed function once (credit-gated); returns HTTP status. |
| get_function_metrics | mcp:read | Live status metrics for one function (rps, latency, instances, cpu/mem). |
| get_function_logs | mcp:read | Recent stdout/stderr of one function, newest first. This is the function's OWN output; the platform's request log is not included. Optionally filter by a plain substring (not a pattern). Paginate backwards with end_cursor_ns. |
| get_database_logs | mcp:read | Recent log lines of one database's sqld engine, newest first. Platform storage identifiers are redacted. Optionally filter by a plain substring (not a pattern). |
| get_usage_summary | mcp:read | Billing summary: balance plus requests, GB-seconds and cost billed, with the compute/storage split (database storage is billed on the tier held). Covers the CURRENT CALENDAR MONTH by default — pass period='all' for the account lifetime. The range aggregated is returned as since/until; the balance is always current. |
| list_env_vars | mcp:read | List the caller's account-level env vars (config values readable; secret values are write-only and never returned). |
| get_env_var | mcp:read | Get one account env var by id (config value readable; secret value never returned — use its value to edit a config var in place). |
| create_env_var | mcp:write | Create an account env var. Requires a verified account. |
| update_env_var | mcp:write | Replace an account env var's value. Requires a verified account. |
| delete_env_var | mcp:write | Delete an account env var (409 if still referenced by a function). Requires a verified account. |
| list_custom_domains | mcp:read | List the caller's custom domains: the hostname, its status (pending/verified/active), and the exact DNS records to publish. Use this to tell the user what is still missing — `pending` means the ownership TXT record has not been seen yet, and `last_error` says why. |
| add_custom_domain | mcp:write | Claim a hostname for one of the caller's functions. Nothing is routed yet: the response carries a TXT record the user must publish to prove they control the domain, plus the CNAME to point at us. Once both exist the platform provisions TLS and the route automatically, usually within a minute. |
| delete_custom_domain | mcp:write | Release a custom domain. Removes its route and certificate; the hostname stops serving immediately and becomes claimable again. |
| create_presigned_url | mcp:write | Mint a presigned URL that invokes ONE function until ONE deadline, with no API key needed by whoever calls it — the credential is in the URL. Use it to hand a webhook sender, a browser or a third party temporary access to a private function instead of sharing an account API key. The URL is returned ONCE and never again; the deadline defaults to 7 days and can be set with `expires_at`. Revoke it early with revoke_presigned_url. |
| list_presigned_urls | mcp:read | List a function's presigned URLs: label, deadline, whether each is still live, when it was last used, and when it was revoked. The URL and its token are never returned — they exist only in the create response. Expired and revoked entries are listed too, because 'why did this URL stop working' is asked after the fact. |
| revoke_presigned_url | mcp:write | Revoke a presigned URL before its deadline. It stops authorizing invocations within seconds (the edge caches decisions briefly). Irreversible — mint a new one if it was a mistake. |
| list_databases | mcp:read | List the caller's private databases (tier, always_on, status). The auth token is never returned — it is injected into attached functions. |
| get_database | mcp:read | Get one private database by id (tier, size, always_on, attached functions, and the injected env-var names). The token value is never returned. |
| create_database | mcp:write | Create a private database. Requires a verified account (+ databases enabled). Returns the injected env-var names (DATABASE_URL, DATABASE_AUTH_TOKEN); the token value is write-only and never returned. |
| delete_database | mcp:write | Delete a private database (409 if still attached to a function). Requires a verified account. |
| attach_database | mcp:write | Attach a database to a function (injects DATABASE_URL / DATABASE_AUTH_TOKEN and redeploys it). One database per function. Requires a verified account. |
| detach_database | mcp:write | Detach a database from a function (drops the injection and redeploys it). Requires a verified account. |
| get_database_metrics | mcp:read | Live status for one database: CPU, memory, running instances and the bytes its data volume holds, against the provisioned storage tier. When instances is 0 the database is asleep: CPU/memory are 0, but volume_used_bytes is still its last measured size (a sleeping database takes no writes) — report it as such, not as live. |
| set_database_size | mcp:write | Change a database's CPU/memory preset (same size names as functions — see get_catalog). Requires a verified account. RESTARTS the database: an awake one is briefly unavailable and in-flight connections drop; the data is untouched. Sending the preset it already has changes nothing and restarts nothing. |
| set_database_tier | mcp:write | Raise a database's provisioned storage tier (see get_catalog for the ladder). UPGRADE ONLY — a lower tier is refused, because the cap bounds a database that is already larger and shrinking needs compaction, not a smaller number. Use this when writes start failing with 'database or disk is full'. Requires a verified account. RESTARTS the database, and the new cap only applies once that restart completes — a write retried immediately may still fail; wait a few seconds. Storage is billed on the tier held, so the charge rises from the next billing window. |
| rotate_database_token | mcp:write | Withdraw this database's current access token and issue a new one. Use after a suspected leak: the token is injected into attached functions' environment by design and carries no expiry, so rotating is the only way to withdraw it. RESTARTS the database and redeploys every attached function; anything still holding the old token (a copied value, a local script) stops working immediately. The data is untouched. Requires a verified account. |
| set_database_always_on | mcp:write | Switch a database between scale-to-zero and always-on. Always-on keeps one instance up so no query pays a cold restore from S3, and bills its compute continuously; scale-to-zero costs nothing while idle. Storage is billed either way. Requires a verified account. RESTARTS the database, so an awake one is briefly unavailable. |
| set_database_stable_window | mcp:write | Set how long a database stays up after its last query before scaling to zero (6s–1h). Waking it costs a cold start plus a restore from S3, so a longer window absorbs spaced-out bursts for a fraction of what always_on costs. Only takes effect while always_on is false. Requires a verified account. RESTARTS the database, so an awake one is briefly unavailable. |
| export_database | mcp:write | Export a database to a portable SQL dump you can download and restore anywhere (gunzip | sqlite3). Poll get_database_export for the download link. Use this before deleting a database, and whenever the account is low on credit: a database whose account stays at a zero balance has its DATA deleted once the retention window (see get_catalog databases.retention_days) elapses. Requires a verified account. |
| get_database_export | mcp:read | Status of a database's latest export, with a time-limited download URL once it is ready (status 'ready'). 'pending' means the dump is still running — poll again; 'failed' carries the reason. The URL expires (see get_catalog databases.export_url_ttl_seconds); calling this again mints a fresh one. |
| list_database_exports | mcp:read | The export history of a database, newest first: when each dump was taken, how big it was, and whether it is still downloadable. Only ONE dump is kept per database — older entries read 'superseded', meaning the record remains but the file was replaced by the newer export. |
| set_database_migrator | mcp:write | Designate one of your functions as a database's schema migrator (attaches it so it receives DATABASE_URL/DATABASE_AUTH_TOKEN). Requires a verified account. The function should be idempotent — it runs on demand via run_migration. |
| unset_database_migrator | mcp:write | Remove a database's migrator designation. The function stays attached (it may still use the database) — use detach_database to drop the injection too. Requires a verified account. |
| run_migration | mcp:write | Run a database's designated migrator function once, on demand. Always invoked through the internal signed-token path, never the function's public route. Credit-gated. Returns the invocation outcome (ok/status). |
19 of these cover private databases, an optional feature. Where a deployment has it off, they are neither advertised by tools/list nor callable.
What the agent cannot do
The MCP server is not admin access. It is bound by the same rules as the rest of the platform:
- Account scope only: a resource you don't own answers 404 and never 403, so the agent can't even learn it exists.
- Write operations require a verified account, and are refused once credit runs out.
- Secret values are never read back: no tool can return them, to the agent or to you.
What it costs
Driving the platform through an agent costs nothing in itself. Administration calls (create a function, list your databases, read your metrics, change a variable) go through the platform API and are not billed.
What is billed is execution. An invocation triggered by an agent is counted exactly like any other, and is subject to the same credit gate: an empty balance refuses it at the edge, before your code even starts. An agent therefore cannot drain your balance behind your back.
Frequently asked questions
Is the MCP server hosted by you or by me?
By us. It's a remote MCP server, reachable over HTTP at the URL published on this page. There's nothing to install or maintain, and it stays reachable from any machine where you authenticate.
Do I have to give my agent an API key?
No. Access uses OAuth 2.1 with PKCE: the client gets an audience-bound token you can revoke at any time, and no long-lived API key is stored in the agent's configuration.
Which MCP clients are supported?
Any client speaking MCP over HTTP transport with OAuth 2.1 authorization: Claude Code, Cursor, VS Code and other conformant implementations. The server has no per-client special casing.
Can I self-host the MCP server?
You don't need to, and that is exactly what the managed service saves you. The protocol stays standard and the same actions are available over the REST API, so nothing locks you in.
Is the MCP server billed separately?
No. MCP itself isn't billed: you pay for your functions' execution (GB-seconds) and their invocations, exactly as if you had deployed them by hand.
Give your agent a FaaS
Create an account, connect your MCP client, and let the agent deploy its first function. Free credit at signup, no card required.
Get started »