Your Agent Is Now a First-Class Tumult Operator
2026-07-04
Since 1.0, Tumult has had an MCP server. It worked. It also treated the agent on the other end like a tourist: 19 tools, all returning prose, no way to tell a harmless read from a fault injection, and an experiment runner that threw its journal away the moment the call returned.
2.1.0 rebuilds that surface into something spec-faithful. 24 tools. Annotations on every one. Structured output with advertised schemas on 16. Workspace files served as real MCP resources. And — the part that changes what an agent can actually do — the run→analyze→recommend loop now closes entirely over MCP.
Let’s take those one at a time.
The loop is closed
Here’s the embarrassing part of the old behavior. tumult_run_experiment executed your experiment, rendered the journal into the response… and that was it. Nothing written to disk. Nothing ingested. The analytics store — the thing that powers recommend, coverage, and trend — never heard about the run.
before 2.1.0:
agent ──► run_experiment ──► journal text ──► (gone)
recommend / coverage / trend ──► store ──► "you've never tested anything"
(the store never saw the runs)
The tool was a dead-end executor. An agent could run chaos all afternoon and the intelligence tools would keep recommending the experiments it had just finished.
Now tumult_run_experiment does what tumult run has always done: it persists the journal (journal_path, default journal.toon) and auto-ingests it into the analytics store. Skip with no_ingest, redirect with store_path — CLI parity, parameter for parameter.
2.1.0:
agent ──► run_experiment ──┬──► journal.toon written
└──► ingested into analytics store
│
┌─────────────────────────┼─────────────────────┐
▼ ▼ ▼
tumult_recommend tumult_coverage tumult_trend
"test kafka next" "tumult-net: NONE" "score improving"
│
▼
agent picks the gap ──► run_experiment ──► (loop)
Run, learn, decide, run again. That’s chaos engineering. Now it’s chaos engineering an agent can do alone in a room.
The result even tells you how the ingestion went — ingested, duplicate, skipped, or failed: <reason>. Ingestion trouble is a warning in the result, never a failed run.
Annotations: reads are free, chaos is gated
MCP tool annotations exist so a client can make policy decisions before calling anything. Every one of the 24 tools now declares its readOnlyHint / destructiveHint / idempotentHint / openWorldHint.
The split is stark, and that’s the point:
24 tools
│
┌───────────────┼──────────────────┐
▼ ▼ ▼
18 read-only 2 destructive 4 writers
idempotent + open-world (non-destructive)
validate run_experiment create_experiment
read_journal gameday_run gameday_create
analyze report
compliance these inject recommend
trend REAL faults (open-world with
coverage into REAL agent= set)
... systems
client policy: client policy: client policy:
auto-approve ✓ ASK THE HUMAN ⚠ approve writes ✎
A well-behaved MCP client reads these hints and does the obvious thing: let the agent grep through journals, compliance verdicts, and coverage reports without ceremony — and put a human approval gate in front of the two tools that pause your Redis or kill your database connections.
One annotation worth calling out: tumult_recommend is marked openWorldHint: true, but not because Tumult phones home. When you pass agent: claude-code, the tool spawns your local agent CLI, and that may call its model API. The annotation tells the truth about the blast radius.
Structured output: schemas, not prose
The old tools returned text. Nicely formatted text! Which your agent then… parsed. With regexes. In 2026.
before: after:
┌─────────────────────┐ ┌──────────────────────────────┐
│ "Experiment passed! │ │ content: [ text (unchanged) ] │
│ Duration: 314ms. │ │ structuredContent: { │
│ 3 steps executed, │ │ journal: {...}, │
│ journal looks OK." │ │ journal_path: "...", │
└─────────┬───────────┘ │ ingestion: "ingested" │
│ └──────────────┬───────────────┘
▼ │
agent regexes the prose validated against the
and hopes for the best outputSchema advertised
in tools/list ✓
16 of the 24 tools now return structuredContent and advertise a matching outputSchema. Here’s a real one — the shape tumult_run_experiment declares, filled in by the Redis experiment from the README:
{
"journal": {
"experiment_title": "Redis resilience — verify recovery after disruption",
"experiment_id": "d2f8...",
"status": "completed",
"started_at_ns": 1782043200000000000,
"ended_at_ns": 1782043200314000000,
"duration_ms": 314,
"method_results": [
{
"name": "pause-redis",
"activity_type": "action",
"status": "succeeded",
"duration_ms": 102,
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7"
}
],
"rollback_results": []
},
"journal_path": "/workspace/journal.toon",
"ingestion": "ingested"
}
The status field is an enum in the schema (completed / deviated / aborted / failed / interrupted), the activity statuses are enums, the trace IDs are right there for your observability stack. No prose archaeology.
The same strictness applies on the way in: enum-ish parameters (format, rollback_strategy, framework, metric, load_tool) reject unknown values with the list of valid ones. And every inline text payload is capped at 512 KiB with an explicit truncation notice — your context window will thank us.
Resources: the workspace has URIs now
Tool calls aren’t the only way to get at files anymore. The workspace is served under a proper URI scheme:
tumult://
│
├── journal/{file} ──────── journals, read as JSON {summary, journal}
│ (application/json)
├── experiment/{file} ───── experiment definitions, raw TOON
│ (application/toon)
└── gameday/{file} ──────── campaign files, raw TOON
(application/toon)
and tools hand out links into it:
run_experiment ────► resource_link: tumult://journal/journal.toon
gameday_create ────► resource_link: tumult://gameday/drill.gameday.toon
report ────────────► resource_link: tumult://journal/report.xml's path
list_journals ─────► one resource_link per journal (first 50)
So the flow an MCP client sees: call a tool, get the text, get the structured content, and get a link to the artifact the tool just produced. Follow the link with resources/read whenever you want the file — no second tool call, no path guessing. resources/list paginates with opaque cursors (pages of 100), and the three list tools grew limit / offset / total for the same reason: nobody wants 4,000 journal paths in one response.
Filenames only, by the way — path separators and traversal attempts in a resource URI are rejected with the same containment checks the tools use. And if you’ve set TUMULT_MCP_TOKEN, resource requests pass the same bearer gate as tool calls.
The full map
Five of the 24 are new in 2.1.0 (report, compliance, trend, agents, gameday_create), and together the surface now covers the whole workflow:
discover validate run analyze
──────── ──────── ─── ───────
discover validate run_experiment ⚠ read_journal
list_experiments create_experiment list_journals
agents analyze
analyze_store
store_stats
query_traces
report compliance gameday recommend
────── ────────── ─────── ─────────
report compliance gameday_create recommend
trend gameday_run ⚠ coverage
gameday_analyze
agentic: list_scenarios, gameday_list ⚠ = destructive,
smoke, run_experiment gate it
Everything an operator does at the terminal, an agent can now do over MCP — with the same shared code underneath. tumult_compliance runs the same tumult_core::compliance scoring as tumult compliance. tumult_recommend runs the same tumult-intelligence engine as the CLI, agent enhancement and validation gate included. tumult_gameday_run even fixed a real bug on the way in: it used to silently skip the campaign’s declared load; it now drives the same k6 executor as the CLI.
One implementation, two front doors.
Try it
tumult-mcp --transport http --port 3100
Point any MCP client at it, list the tools, and watch it read the annotations before it touches anything. Then let it run an experiment and ask for a recommendation — the answer will already know about the run.
The MCP server negotiates protocol revision 2025-11-25 and ships in 2.1.0. The MCP Guide has the full data model — annotations, schemas, resources, pagination, and auth.