zerolith.ioChecking…

← All articles

Tutorial · MCP · AI agent

Letting an agent deploy: Zerolith's MCP server in Claude Code

9 min read

An assistant writes the handler, then you do the copying, pasting and rerunning. MCP removes that round trip: one command to connect Claude Code, an OAuth flow with no API key, and an agent that closes the deploy–call–read-logs–fix loop itself. Including what it never sees, and what it costs.

An assistant writes a perfectly good Python handler. It will also tell you which curl command deploys it. Between the two, there is you: you paste the code into a file, you paste the command, you collect the URL, you hand it back, it reads the error, it fixes the code, and round you go again. The model thinks and you do the typing.

The Model Context Protocol removes that round trip. It's the standard by which an assistant discovers tools and calls them itself. Zerolith exposes one: forty-one tools, every one of them bounded to your account, so deploying becomes a sentence in the conversation instead of a sequence of copied commands.

Here's how to wire it to Claude Code, what it actually feels like once it's there, and the handful of things worth knowing before you let an agent press the buttons.

One command to connect

The server is remote: it runs on our side, over HTTP. Nothing to install, no local process, no binary to keep up to date.

Claude Code
claude mcp add --transport http zerolith https://zerolith.io/mcp

Then open Claude Code and type /mcp: the server shows up, you pick authenticate, your browser opens on the Zerolith authorization page. Log in, approve, and the tools are there.

What appears nowhere in that procedure: an API key. The flow is a full OAuth 2.1, and it's worth saying why, because this is exactly what most "paste your key here" integrations are missing:

  • The agent registers itself (RFC 7591). There is no application credential for you to create, copy, or file away somewhere.
  • PKCE with S256, mandatory. An intercepted authorization code is useless without the verifier, which never leaves your machine.
  • The token is bound to its resource (RFC 8707). A token minted for Zerolith is worth nothing elsewhere, and a token minted for another service is worth nothing here.
  • The access token lives an hour, the refresh token fourteen days, and each renewal invalidates the previous one. A stolen token that gets replayed announces itself.

No long-lived secret sits in a config file. On a workstation that isn't a detail: a config file eventually gets backed up, synced, or shared by accident.

The first thing to ask the agent

Before asking it to deploy anything, ask it what it can see. Two read tools answer, and they frame everything that follows.

whoami returns the account and the balance. get_catalog returns the available languages, the size presets and the prices — here's the real answer from the production server, abridged:

get_catalog
{"languages": [{"name": "python",    "code_filename": "main.py", "default_handler": "main.handler"},
               {"name": "python313", "code_filename": "main.py", "default_handler": "main.handler"},
               {"name": "python314", "code_filename": "main.py", "default_handler": "main.handler"},
               {"name": "nodejs20",  "code_filename": "main.js", "default_handler": "main.handler"},
               {"name": "nodejs22",  "code_filename": "main.js", "default_handler": "main.handler"},
               {"name": "nodejs24",  "code_filename": "main.js", "default_handler": "main.handler"}],
 "sizes": [{"name": "small",  "memory": "128Mi", "cpu": "100m"},
           {"name": "medium", "memory": "256Mi", "cpu": "250m"}, "…"],
 "defaults": {"language": "python", "size": "small", "min_scale": 0, "max_scale": 2,
              "stable_window": "60s", "timeout_seconds": 120},
 "pricing": {"price_per_gb_second": 0.000004, "price_per_vcpu_second": 0.0000125,
             "price_per_invocation": 0.0000004}}

This isn't an opening courtesy, it's the guard against an agent's most common failure: inferring from memory what the platform accepts. A model has read thousands of serverless.yml files and will happily propose a python3.11 runtime, an xlarge preset, or an option that doesn't exist here.

The catalog is the source of truth, and it moves. Look at the ids: python is 3.12 and stays there, because a Knative revision is immutable and repointing python would move third-party code onto an interpreter it never chose — 3.13 deleted a dozen standard-library modules, which is a real break, not a theoretical one. Newer lines are therefore ids of their own, python313 and python314, that you opt into. And a language whose image isn't deployed in production isn't advertised at all, rather than offered and then refused at deploy time: the list above is what runs today, not what the code knows how to run.

The loop that actually changes something

Deploying isn't the point. The point is that the agent can close the loop on its own: deploy, call, read what happened, fix, redeploy.

A plain request is enough:

your request
> Deploy a Python function "rates" that returns today's EUR/USD rate from
  the ECB API as JSON. Make it public, call it, and show me the response.

What the agent chains behind that, tool by tool:

  1. get_catalog — which language, which preset, which defaults.
  2. deploy_function — the code, the name, public: true. Back comes the id and the URL.
  3. invoke_function — a real call, which wakes a pod and returns the HTTP status and body.
  4. get_function_logs — if that status was a 500, the Python traceback is right there.
  5. update_function — the fix, which produces a new revision.
  6. invoke_function — again, to check.

Steps 3 to 6 are the whole value. An assistant without MCP stops at step 2 and hands you a block of code hoping it works; here it observes the failure, reads the traceback and fixes it without you opening a terminal. On a silly error — a mistyped JSON field, a date that won't parse, a timeout set too low — the loop closes in about a minute.

The rest of the surface works the same way. Environment variables, presigned URLs, custom domains, private databases: nineteen of the forty-one tools are about the private database, enough for an agent to create one, lay down a schema through a migrator function, attach it to your API and trigger an export — the full walkthrough, done by hand, is in the previous article.

What the agent never sees

This is the question to ask before wiring anything to an account: how far does the delegation go. Here it covers actions, not secrets.

  • Your API keys stay invisible. They're stored hashed and shown once, at creation, in the UI. No MCP tool returns them, and the agent doesn't need one: it authenticates with its own token.
  • A secret environment variable can't be read back. The agent can create it, replace it, delete it — not read its value. Neither can you, for that matter: the value only exists in the cluster object, and the backend has no read permission on it.
  • Your database token doesn't come out either. The agent can create the database, attach it to a function and run a migration without ever receiving the credential that opens it. The function finds it in its environment.
  • A resource that isn't yours answers "not found", never "forbidden". The agent doesn't even learn that it exists.

There are two scopes and no more: mcp:read to look, mcp:write to act. Each tool also declares whether it modifies state and whether it's destructive, which is what lets Claude Code ask you to confirm a delete_function while waving a list_functions through. The server runs on exactly the same code as the REST API and the web UI, too: there is no privileged path reserved for agents, so an MCP tool can never do what your account couldn't do itself.

What it costs

MCP isn't billed. Listing your functions, reading your metrics, creating a variable, triggering an export: those are control-plane calls, they go through the same API the web UI uses, and they cost nothing.

What is billed is execution — identically, whether it comes from an agent, a curl, or a browser. At public prices a small pod costs €0.00000175/s (0.125 GiB at €0.000004/GiB-second, plus 0.1 vCPU at €0.0000125/vCPU-second) and each invocation €0.0000004.

So a typical debugging session — one deploy, twenty-odd calls spread over a quarter of an hour, the pod awake for a few minutes in total — lands in fractions of a cent. The factor that matters isn't how many calls the agent makes, it's how long a pod stays up: after the last call the autoscaler observes its stable window (60 s by default) and then allows a grace period. Twenty calls inside the same minute cost less than one call every five minutes.

And if the credit runs out, an agent stuck in a loop doesn't dig a hole: the call is refused at the edge, before the pod even starts. That's the same guarantee as everywhere else on the platform, and it doesn't get softer because an agent is at the keyboard. The details are on the pricing page.

Three things learned using it

Make it read before it writes. update_function replaces the code, it doesn't merge it. An agent that didn't call get_function first rewrites the handler from memory, and wipes out the fix you made by hand in the UI two days ago along the way. The tool's own description says so — which still leaves it to you not to phrase the request as an invitation to hurry.

The platform keeps no history of your code. It keeps the current code, readable through get_function, and its digest. Not the previous version. A conversation is not a repository: what the agent deploys has to end up in your git, or the only copy of your handler is the one that's running.

An unverified account can't write anything. The agent gets a clean, polite refusal, which it will relay to you as a mysterious error; it's just the signup email waiting to be confirmed. Same for an invocation refused with a 402: that's not an outage, that's the balance.

Cutting access off

Revocation lives on the MCP page of your dashboard, and it invalidates the access token already issued, immediately — not just its renewal. The distinction sounds theoretical and isn't: without it, an agent would keep control until its token expired — up to an hour — while the UI displayed "disconnected". Resetting your password also cuts every live MCP grant, on top of your web sessions.

What it doesn't replace

Three limits, honestly:

  • It isn't admin access. The agent has exactly your perimeter, no more, no less. If you were hoping it would fix something you can't fix yourself, no.
  • It isn't a code review. A handler deployed by an agent is code in production. It deserves the same eye as yours, especially if it's public and touches whatever arrives in the request.
  • Forty-one tools is a lot of context. In a long conversation you want a client that only loads a tool's detail when it's about to use it. Claude Code does.

The rest fits in one sentence: the tedious part of deploying — copy, paste, rerun, reread the logs — never needed a human. It needed an interface a machine could call.

The full server reference, with the tool table and their scopes, is on the MCP page; the step-by-step tutorial, in four steps, on the Claude Code page. And if you'd rather do all of it by hand, the documentation describes the same thing on the API side.

Try it on your own account

Signing up comes with credit, enough to deploy, schedule and measure everything above, without getting a card out.

Get started »