plimsoll trainer · 12 / the execution server

The MCP server

An AI application such as Claude Code, Codex or Claude Desktop lets its model call outside programs through MCP, the Model Context Protocol: an open standard for describing the actions a program offers and carrying each request and answer. Each such action is a tool: one named operation with a schema for its arguments and one for its result, which the model calls by name. The plimsoll MCP server offers five. With them the model runs Python or JavaScript in a sandbox and gets back, beside the output, the isolation tier the code ran behind (how strong the wall around it was) and a run record (the daemon's statement of what was sent, what came back and where it ran, which the client checked before accepting the answer). With a key configured, every exchange also leaves a receipt: a signed line anyone holding the public key can check later. Every example on this page is a real recorded exchange with the real server, made by a program anyone can rerun; the recording program chose these calls, rather than an independently acting model.

What MCP is, in one picture

Think of a hospital switchboard. A doctor (the model) does not phone the pharmacy, the lab and the porters directly; the switchboard (the host, the application the model lives in) keeps a directory of departments, each with the forms it accepts. The doctor fills in a form, the switchboard passes it on, the department does the work and sends a report back. MCP is the standard for that directory and those forms. plimsoll's department is the MCP server, a small program the host starts on your own machine and talks to over stdio, the program's standard input and output: two byte streams, no network between them. The server, in turn, talks to plimsolld, the plimsoll daemon, which starts the sandbox. Five lanes, and every example below travels along them.

The recording's server answered server_info with this. The provider is the backend that ran the code, docker here, and kernel is the tier docker reaches only when it runs under gVisor, a stand-in kernel that answers the container's requests itself as an ordinary program. Section 08 says what that claim rests on.

The recording did not load; mcp-tour.js is missing.

The Professor (a fictional narrator)

I shall attempt the same, with one picture and no equations. The picture has five boxes. Two of them are yours.

A real quote

Geoffrey Hinton: So today I'm going to do something very foolish.

He then proposes explaining a technical idea without equations.

Nobel Prize lecture "Boltzmann Machines", Aula Magna, Stockholm University, 8 December 2024, his opening words (punctuation added to the video's captions). Nobel lecture video at 1:34 (EXTERNAL · video ↗)

The five tools, each with a real call

Each card is the tool exactly as the server described it to the host in the recording: its description, and its four MCP hints (whether it only reads, whether it can destroy something, whether repeating it is harmless, whether it reaches an open world). Under each, one real call from the recording: the arguments the model's part sent and the JSON the server returned. A digest, where you see one, is a SHA-256 hash standing for exact content: changing the content almost certainly changes its digest. The server also publishes a digest of this whole tool list, so a host showing an old list can be caught.

Fresh, or in a sandbox that remembers

run_code has two modes, and the result says which one you got. Without a handle, the call runs in a fresh sandbox and nothing is kept. With a handle from open_sandbox, the call runs in a session: one sandbox the daemon keeps open for many calls, where files written by one call are there for the next and an interpreter per language stays alive, so a variable defined in one call exists in the next, as in a notebook. Each call in it is a cell, code run in that kept interpreter; that is why the traceback in example 4 names <cell 1>. The handle is random, made by the server, and never the daemon's own session ID. Four flags on every result tell the model what survived and what did not; the two columns below read them off the recorded answers of examples 1 and 3.

A sandbox the daemon ends (its lifetime ran out, or it hit a disk or memory limit) is replaced on the handle's next call, and that call says freshSandbox: true and, in sandboxReplaced, why the earlier one ended. Example 7 shows the finer case: the sandbox survives, the interpreter does not.

Eight examples, replayed from the recording

A program in the server's own repository played the model's part: it started the server binary over stdio, exactly as a host does, sent each tool call and recorded every argument, every answer and every wall time against a daemon running docker under gVisor. No model chose these calls; that is stated on each one. Press Play to watch a call travel the five lanes; the clock counts to the recorded time. Click a hop to see the real JSON that crossed it. Where the recording does not hold a hop's bytes (the daemon's own wire messages), the hop says so and names the digest that stands for them.

Why it matters: a model answering from memory can only sound sure. One that runs the test has an exit code and an output to quote.

The Professor (a fictional narrator)

Notice what the model did not say: "I believe it is prime." It ran the test. Belief is for the reader; the sandbox deals in exit codes.

A real quote

Geoffrey Hinton: People confabulate all the time. At least I think they do. I just made that up.

StarTalk, 28 February 2026, at 1:01:41, on why hallucinations should be called confabulations. StarTalk video at 1:01:41 (EXTERNAL · video ↗)

Why it matters: data travels in files, written into the sandbox's working directory before the code runs, so a model never has to paste a table into a program. The data here is made up for the example, and the page says so because the recording does.

Why it matters: three calls, one interpreter. The variable loaded by the first call answers the next two, and every answer carries stateKept: true so the model knows it may rely on that.

Why it matters: a failing program is a result, not an error. The exit code and the traceback come back for the model to read, which is the loop a person would use too.

The Professor (a fictional narrator)

The first median was written by a confident author. The sandbox tried it on four numbers. The second median was written by the same author, a little less confident.

A real quote

Donald E. Knuth: Beware of bugs in the above code; I have only proved it correct, not tried it.

The last sentence of a 1977 memo to Peter van Emde Boas, as Knuth tells it on his own FAQ page. Knuth's FAQ (EXTERNAL · author's page ↗)

Why it matters: the sandbox has one network interface, the loopback, so a request to the internet fails inside it. The recorded code fetched an address rather than a name on purpose: a fetch by name fails too, but only after waiting on a name lookup that has nowhere to go (about five seconds when the recording tried it), which would have hit this recording's three-second run budget with an empty output instead of this clean refusal.

The Professor (a fictional narrator)

The sandbox removes the third leg. A stool with two legs makes a poor getaway vehicle.

A real quote

Simon Willison: Access to your private data; Exposure to untrusted content; The ability to externally communicate

"The lethal trifecta for AI agents: private data, untrusted content, and external communication", simonwillison.net, 16 June 2025; three short excerpts from his list, joined here with semicolons. Simon Willison's post (EXTERNAL · article ↗)

Why it matters: one sandbox, one working directory, an interpreter per language. Python writes a file; JavaScript, in the same sandbox, reads it and names the Node version that did.

Why it matters: a loop that never ends is stopped at the run's time budget (three seconds in this recording, set low on purpose; the server's default is fifty) and reported as timedOut with exit 124. The sandbox answers the next call, but in a new interpreter: stopping the loop ended the one that held the variable, so answer is gone and the result says freshInterpreter: true. The recording also shows the stopped call answered after the budget, at 3,133 ms of wall time.

Why it matters: a week later, someone asks whether that primality check really ran, behind which tier, with what code. get_record returns the record by its digest while the server is running, and the receipt line on disk outlives the server. Section 07 checks that line in your browser.

The safety scene: a fourth sandbox, refused

Not a use case, a rule. The server keeps at most PLIMSOLL_MAX_SANDBOXES open at once, three unless configured otherwise, and that is what the recording ran with. With three open, the model asked for a fourth. The server refused, said nothing ran, and listed the three idle handles. Opening one never deletes existing work. An explicit close_sandbox deletes the named sandbox, so that action carries the destructive hint. Daemon expiry, shutdown or a boundary failure can also end a sandbox, and a timed-out call can lose its interpreter state. A model that wants room has to decide which work it is willing to lose, and say so by name. Step through the recorded calls.

The refusal, exactly as the model received it

The Professor (a fictional narrator)

Here, one thing stops them: a cap of three, and a refusal that deletes nothing and names what it kept.

A real quote

Asked what prevents agents from making copies of themselves, Hinton replies:

Geoffrey Hinton: Nothing.

[laughter]

StarTalk, "Is AI Hiding Its Full Power? With Geoffrey Hinton", 28 February 2026, at 1:10:16. StarTalk video at 1:10:16 (EXTERNAL · video ↗)

Safe to retry? A game answered from ran

When a call fails, the one question that matters is whether the code ran. The server answers it in a field called ran: "no" means the daemon or the server refused before any code was sent, so fixing the cause and sending the call again repeats nothing; "maybe" means the code may have run without an answer that checked, so sending it again could do its work twice, and the server itself never retries it. A program that merely failed is neither: it is a result with an exit code, and the right move is to read it. Six shapes below; three are from the recording, three are the server's own wording from its source, shown with example values because the recording never hit them. Choose, then read the field.

The Professor (a fictional narrator)

If the answer went missing somewhere between two computers, do not ask the question again. The code may have heard you the first time.

A real quote

Leslie Lamport: A distributed system is one in which the failure of a computer you didn't even know existed can render your own computer unusable.

Email to the SRC src-t list, 28 May 1987, published on Lamport's site. the 1987 email (EXTERNAL · author's page ↗)

The receipt you check yourself

With PLIMSOLL_SIGNING_KEY_FILE and PLIMSOLL_RECEIPTS_DIR set, the server signs every checked exchange with a signing key, the private half of a key pair: anyone holding the public half can check the signature, and nobody without the private half can make one. The signing is done by plimsoll's attest harness, a program outside the daemon that signs run records and verifies them later, so a daemon running hostile code never holds the key. Each receipt is one line of a file: the recorded request and answer, the run record stated as an in-toto statement (a standard format for signed claims about software) inside a DSSE envelope (Dead Simple Signing Envelope, a small standard that wraps a payload with its signatures), and a second signed statement binding the line to its place in the file. The recording signed with a demo key made for it; the private half was deleted when the recording ended, and only the public half is on this page.

The check below is done by your browser, with its own Web Crypto implementation of Ed25519, over exactly the bytes the receipt says are signed: the string DSSEv1, the length and text of the payload type, the length and bytes of the payload, each separated by one space (the DSSE pre-authentication encoding). Nothing is fetched; the line is embedded in the recording.

What this check covers

That the demo key signed this statement and that not one byte of it has changed since, and that the statement names example 1's run record, the SHA-256 the result gave. The page also recomputes the line's own SHA-256 and the key's ID and compares both with what the result said.

What it does not cover

It does not recompute the record from the request and answer stored in the line, check the second statement that binds the line to its file, or check the file around it; and for a sandbox call it does not follow the chain from one record to the previous. Those are plimsoll-attest verify-entry for one line and plimsoll-attest verify for a whole file. It never shows that what the code printed is true.

The same line checked by plimsoll-attest verify-entry, as recorded

The Professor (a fictional narrator)

You do not need a posse of Swedes. You need thirty-two bytes of public key and a browser.

A real quote

Geoffrey Hinton: My very first thought was how could I be sure it wasn’t a spoof call.

He recalls trusting the call partly because several Swedish voices were on it. The interviewer jokes about a group of impersonators.

"First reactions" telephone interview with Adam Smith of Nobel Prize Outreach, October 2024, minutes after the call telling him he had won the 2024 Nobel Prize in Physics. Nobel Prize telephone interview (EXTERNAL · official transcript ↗)

The tier is evidence, not attestation

Every result in the recording says kernel. That word is the daemon's configuration (docker with SANDBOX_DOCKER_RUNTIME=runsc, gVisor's runtime) plus the provider's evidence (it inspected the runtime it was given) plus its startup smoke tests (a throwaway container proved from inside that its root is read-only, that loopback is its only network interface, that its writable space is the promised size). It is not attestation, cryptographic proof rooted in hardware of exactly what software is running; plimsoll has none to offer and says so in every summary. The server adds a floor, the weakest tier it will accept, PLIMSOLL_MIN_ISOLATION, sent on every request and compared again against the answer: a daemon below it refuses before any code runs. Move the floor against the recorded daemon and read what would have happened.

The server's floor PLIMSOLL_MIN_ISOLATION default kernel

The record carries two more identities. environment is the exact docker image ID the daemon verified at startup, and softwareIdentity is the digest of the one platform build of that image it launched; with PLIMSOLL_SOFTWARE_IDENTITY the server can require that exact value, or one of an approved set, and the client checks the record against it on every answer. A tag such as latest is never an identity, because a tag can be moved.

The Professor (a fictional narrator)

A smoke test shows the walls held where it pushed. It cannot show there is no door it did not try. That is why the word is evidence.

A real quote

Edsger W. Dijkstra: Program testing can be a very effective way to show the presence of bugs, but is hopelessly inadequate for showing their absence.

"The Humble Programmer", ACM Turing Lecture, 1972 (EWD340). EWD340 transcription (EXTERNAL · source text ↗)

Install it, and know where the token sleeps

Four hosts, one server. Pick yours, type your daemon's address and where you put the binary, and the exact configuration is written below, with a plain statement of where the caller token ends up on your disk, because that differs by host and it is the one thing worth knowing before you paste. What works today is the binary built from source. The packaged forms (a Claude Code plugin, an npm launcher, a Claude Desktop bundle) are in the repository and arrive with the server's first release, 0.1.0; the picker shows what each will write, and marks it so. Before any of them you need a running plimsolld that keeps sessions (SANDBOX_MAX_SESSIONS of 8 for this example, leaving room for the server’s three open handles and other callers) and a caller token with the code:run scope.

cd clients/mcp && go build -o plimsoll-mcp ./cmd/plimsoll-mcp   # Go 1.26

The Professor (a fictional narrator)

Installing is one step of the process. Deciding where the token sleeps is another, and it is the one people skip.

A real quote

Bruce Schneier: Security is a process, not a product.

Crypto-Gram, 15 May 2000, "The Process of Security". Crypto-Gram, May 2000 (EXTERNAL · author's newsletter ↗)

Install formats and credential handling: Codex MCP configuration (EXTERNAL · official docs ↗); Claude Code plugin reference (EXTERNAL · official docs ↗). Checked 14 October 2026. A host with a longer configured server deadline needs a correspondingly longer tool timeout.

Keep reading