The eval endpoint and MCP
The HTTP endpoints in Call the HTTP API expose one action
at a time. The /api/eval endpoint exposes a different surface onto the same shared action
layer: it runs a whole TypeScript script that can call many actions, in a hermetic sandbox
built from every registered provider’s actions. Both are views of the one capability layer
your host assembles — you write your providers once, and the sandbox behind this endpoint is
built from ProviderRegistry.toTools() (see
Register providers and services and
Tools and transports).
Run a script
Section titled “Run a script”POST /api/eval with a script, an initial data object, and an optional callLog. The
script runs in a sandbox whose available functions are all your providers’ actions.
curl -X POST http://localhost:3800/api/eval \ -H 'content-type: application/json' \ -d '{ "script": "const note = await notesCreate({ title: data.title, body: data.body }); data.id = note.id;", "data": { "title": "Groceries", "body": "Milk, eggs, coffee" } }'The script has no return statement — it surfaces results by writing to the persistent data
object (and by console.log). The response reports how the run ended, the resulting data,
the recorded call log, and anything the script logged:
{ "status": "completed", "data": { "title": "Groceries", "body": "Milk, eggs, coffee", "id": "note_8f2a" }, "callLog": [{ "...": "one entry per action call the script made" }], "console": [{ "...": "one entry per console.* call" }]}status is "completed" when the script ran to the end, or "interrupted" when an action
paused the run (see resumption below). On either outcome you get data, callLog, and
console back.
Discover callable actions
Section titled “Discover callable actions”Scripts call actions by their aggregated function name. List what is available with
GET /api/eval/actions:
curl http://localhost:3800/api/eval/actions[ { "functionName": "notesCreate", "name": "Create note", "description": "Creates a note." }, { "functionName": "notesSearch", "name": "Search notes", "description": "Finds notes by text." }]For the precise call signature of one action, request its detail. The response includes
generated TypeScript typings describing the function’s parameters and return type — paste
them into your editor to write scripts against a typed surface:
curl http://localhost:3800/api/eval/actions/notesCreate{ "functionName": "notesCreate", "name": "Create note", "description": "Creates a note.", "typings": "declare function notesCreate(input: { title: string; body: string }): Promise<{ id: string; title: string; createdAt: string }>;"}Resume an interrupted run
Section titled “Resume an interrupted run”An action can pause itself — for example to ask a human for approval. When that happens the
run comes back with status: "interrupted", plus two things you need to continue:
- a
callLogrecording every call up to the pause, and - an
interruptobject:{ position, data, state }.positionis the paused call’s slot in the log,datais what the action is asking you to decide on, andstateis its opaque checkpoint.
{ "status": "interrupted", "data": { "title": "Groceries", "body": "Milk, eggs, coffee" }, "callLog": [/* calls up to the pause; the paused slot is null */], "console": [], "interrupt": { "position": 0, "data": { "question": "Approve?" }, "state": { "token": "…" } }}To resume, call /api/eval again with the same script, the returned callLog, and a
resumption that carries the interrupt’s position and state back plus your answer under
data. The sandbox replays the recorded calls deterministically instead of re-running them,
then delivers your answer to the call that paused so it can complete:
curl -X POST http://localhost:3800/api/eval \ -H 'content-type: application/json' \ -d '{ "script": "<the same script>", "data": { "title": "Groceries", "body": "Milk, eggs, coffee" }, "callLog": [ /* the callLog returned by the interrupted run */ ], "resumption": { "position": 0, "data": { "approved": true }, "state": { "token": "…" } } }'Because the call log is deterministic, resumption is exact: already-completed calls are not
executed again, and only the paused call runs — now with your answer in hand. A run can pause
more than once; each time, answer the new interrupt the same way. This is what lets a long
or approval-gated workflow survive across multiple HTTP requests. The underlying mechanism is
Determinism and resumption.
The vision is that this one shared action layer is reachable through many surfaces, MCP among
them, so that agents can call your providers’ capabilities as tools. As of the current
server, MCP is not yet wired into the host — createServer registers the health,
provider, and eval routes plus the interactive docs, and no MCP endpoint. Treat MCP as
planned rather than available today; until it lands, agents reach the action layer through
the eval endpoint or by consuming providers directly with @grundlag/agent.
A typed client
Section titled “A typed client”For calling a host from application code, you usually don’t hand-write requests at all: a host can generate a strongly-typed TypeScript client built against its exact providers, so you get compile-time types for the actions and entities that host exposes. The same package also reconstructs a host’s actions as tools, so a sandbox or agent can run on the client over HTTP. See Generate a typed client.
Next steps
Section titled “Next steps”- HTTP API reference — the exact eval request and response shapes.
- Register providers and services — how
toTools()builds the layer the sandbox runs on. - Generate a typed client — type-safe calls without hand-written request shapes.