MCP server
Launch workflows and agents over STDIO, then watch their output by run ID.
The Marbles MCP server loads one workspace’s authoring modules and owns an
engine for that connection. External agents can inspect the loaded
configuration, dispatch work, read its status, and cancel it. Both workflow
and agent launches return a runId without waiting for completion.
Start from a source checkout
This first iteration runs directly from Bun source. It has no packaged MCP
executable or build step. Install the checkout’s dependencies with
bun install --frozen-lockfile, then configure your MCP client to launch:
{
"mcpServers": {
"marbles": {
"command": "bun",
"args": [
"/absolute/path/to/foundry/apps/marbles/src/mcp/stdio.ts",
"--source",
"/absolute/path/to/project/.foundry/marbles"
]
}
}
}
The example uses the common mcpServers configuration shape. Adapt the
client configuration as needed, keeping the same command and arguments.
--source accepts a single module or a directory. Without it, the server
looks for .foundry/marbles under its working directory, with the CLI’s
legacy marbles.config.ts fallback. See project discovery.
The workspace environment is loaded and validated through Varlock before source imports. Missing source or an invalid environment prevents startup. Importing the modules executes their top-level code and registers their declarations. Keep module scope to declarations. Restart the server to load source edits.
The server also accepts --state, --artifacts, repeatable --harness,
and --dry-run. Their defaults match the CLI flags.
Diagnostics and ordinary source stdout go to stderr; stdout carries MCP
messages. The server does not start the schedule/monitor polling loop.
Tools
| Tool | Arguments | Result |
|---|---|---|
list_config |
None | Source path, workspace root, named definitions with input schemas, agent IDs/model routes, and declared schedules and monitors. |
dispatch_workflow |
name, optional input |
Starts a named workflow or step and returns its runId, kind, name, and status. |
dispatch_agent |
prompt, optional agent and execution options below |
Starts agent work and returns the same run fields plus its session reference. |
get_run |
runId |
Current status, final output, agent text/session when applicable, or an error. |
cancel_run |
runId |
Requests cancellation and returns the current run status. |
Dispatch responses are acknowledgements, even if their status is already
completed. They contain no final output, text, or error; call get_run
for those fields. Dispatch never waits for result finalization.
Tool results contain JSON in an MCP text content block. Unknown targets and
rejected arguments return tool errors. An accepted run can still fail during
execution; inspect it with get_run or watch it to its end.
dispatch_workflow addresses definition names, not schedule or monitor
keys. Use list_config for the available names and input schemas. It does
not tick a schedule, poll a monitor, or start a trigger loop.
Dispatch and watch a workflow
Call dispatch_workflow with the name and input matching your source:
{
"name": "count",
"input": { "extension": ".ts" }
}
The response includes a runId. Both launch tools use the engine’s run ID;
an agent’s session ID identifies its conversation and is separate.
Wait for either kind of run in a single command:
marbles watch <runId>
From the source checkout, without a build:
bun apps/marbles/src/cli.ts watch <runId>
The watcher replays earlier output, follows new output, prints the result
and terminal status, then exits. It needs no source path and can run from
another working directory. If you set a custom --state on the MCP server,
pass the same value to the watcher. See watching runs
for exit codes and interruption behavior.
Agent options
dispatch_agent accepts these fields:
| Field | Meaning |
|---|---|
prompt |
Required task text. |
agent |
Optional declared agent ID from list_config. Omit it for an ad hoc agent. |
instructions |
Instructions for an ad hoc agent, or instructions appended to a declared agent’s prompt. Without either, an ad hoc agent uses a short task-completion prompt. |
model, provider |
Override the agent’s configured model route. Otherwise use its route or the engine default. |
cwd |
Working directory, resolved relative to the loaded workspace root. |
maxTurns |
Positive integer limiting model/tool steps. The agent can finish earlier. Network models and local Claude Code support it; Claude Code accepts at most 100. Local Codex and Pi reject this option because their current adapters cannot enforce it. Omit it to use their own loop defaults. |
timeoutMs |
Positive integer execution timeout in milliseconds, at most 2,147,483,647. Expiry requests cancellation and reports a failed run. |
compaction |
Optional boolean forwarded to session context-compaction settings. Omit it to use the session defaults. |
session |
An earlier { id, model, provider } session reference, to continue that conversation. Wait for its earlier run to finish first. |
For example:
{
"agent": "reviewer",
"prompt": "Review the changes and report any bugs.",
"provider": "claude-code",
"maxTurns": 10,
"timeoutMs": 120000
}
maxTurns limits SDK model/tool steps for network providers and the native
loop for Claude Code. Dry runs use an SDK step limit. It does not ask the
server to send repeated follow-up prompts or force that many replies.
Continue a conversation by making another dispatch with its session.
Concurrent dispatches cannot use the same active session in this host.
Status, cancellation, and lifetime
Pass the returned run ID to get_run or cancel_run:
{ "runId": "the-returned-run-id" }
Statuses follow the engine: queued, running, suspended, completed,
failed, or cancelled. MCP spells the engine’s complete status completed.
Cancellation through the engine or MCP appears in the same run record.
Cancellation is a request; keep reading status until the run settles.
Code inside workflow bodies must honor the context’s signal to stop its
external work. Cancellation cannot undo completed side effects.
Keep the MCP connection open while runs execute. Closing stdin or sending
SIGINT/SIGTERM shuts down the owning engine and cancels outstanding work.
Each connection launches its own engine. There is no instance discovery or
pooling, and get_run/cancel_run address only this host’s launches.
Run lookup is held in memory for the life of the server. Persisted engines
also write an output journal, so
completed output remains watchable after the owner exits. The journal is
not a persistent job queue, and output files have no automatic retention
policy yet. --dry-run and other in-memory engines save no journal and
cannot be watched from another process. Dry runs still execute arbitrary
source and workflow code; they do not isolate its own filesystem or network
calls.
Custom artifact tools, sandbox tools, remote transport, and detached work that outlives the MCP owner are outside this iteration.