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.
01
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.
ModelWrites the tool call: which tool, what arguments. The tool schema carries no caller token or daemon address.
HostClaude Code, Codex, Claude Desktop. Holds the conversation and the tool list, starts the server, carries each call and answer.
MCP serverplimsoll-mcp, on your machine. Checks the call, sends it to the daemon as plimsoll's own client, checks the answer's run record, returns one JSON value.
plimsolldThe daemon. Checks whether the request may run, starts or reuses a sandbox, runs the code, states the tier and the record.
SandboxHere a locked-down container under gVisor, a program that stands between the code and the host kernel, with no network.
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 ↗)
02
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.
03
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.
04
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.
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.
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.
05
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:
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.
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.
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_ISOLATIONdefault 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.
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.