MCP server: soup mcp serve (v0.71.28)
v0.71.28 ships a Model Context Protocol server, so you can drive Soup from any MCP client — Claude Code, Cursor, Cline, Continue — without leaving the chat. Your agent inspects a dataset, searches recipes, reads runs and gives a ship verdict as tool calls.
No other fine-tuning CLI ships an MCP server.
Start it
pip install "soup-cli[mcp]" # official mcp SDK (mcp>=1.2.0,<2), lazy-imported
soup mcp serve # stdio; read-only tools
soup mcp serve --allow-mutating # also run the 2 planning tools
soup mcp serve --allow-execute # planning + real execution (v0.73.3)Transport is stdio only: no network listener, no HTTP, no SSE. That is a deliberate security property, not a limitation to work around.
Execution (v0.73.3)
--allow-execute implies --allow-mutating, and its history is worth two sentences, because copy written about it ages badly. v0.73.2 added the flag as a reserved gate that opened nothing. v0.73.3 opened it.
soup mcp serve --allow-execute # planning + real execution, behind a tokenThe server now carries 18 tools, and a planned training or export can actually run. How that is gated is the whole design, and it is covered in the execution model below.
If you are auditing this against the binary, do not trust
--helphere. At the v0.73.3 tag the help string for--allow-execute, the--allow-mutatinghelp, and the server's own startup banner were all left behind by the feature commit and still say execution is disabled. The changelog, the command reference and the running code agree that it executes. This is the reverse of the usual rule, where the shipped binary settles a documentation disagreement.
Nothing executes unless you pass the flag. --allow-mutating on its own can never trigger a subprocess, and that is asserted in the code rather than assumed.
Connect a client
Add Soup to your client's MCP config (.mcp.json for Claude Code, claude_desktop_config.json for Claude Desktop):
{
"mcpServers": {
"soup": { "command": "soup", "args": ["mcp", "serve"] }
}
}To expose the planning tools, add the flag:
{
"mcpServers": {
"soup": { "command": "soup", "args": ["mcp", "serve", "--allow-mutating"] }
}
}The tools
The server exposes 18 tools total: 14 read-only + 2 planning + 2 executing. It exposes MCP Tools only — no Resources, no Prompts. Every tool maps to a Soup command and returns JSON.
14 read-only tools (always available)
| Tool | Backing command | Returns |
|---|---|---|
advise | soup advise | PROMPT_ENG / RAG / SFT / DPO / GRPO verdict for a dataset |
data_inspect | soup data inspect | Row count, columns, length distribution, duplicates |
data_validate | soup data validate | Format-compliance report (issues + valid-row count) |
data_score | soup data score | Quality scorecard: PII, toxicity, language mix, educational value |
data_doctor | soup data doctor | Chat-template compatibility vs a tokenizer (needs [train]) |
recipes_search | soup recipes search | Search the catalog by keyword / task / size (no YAML body) |
recipes_show | soup recipes show | A full recipe including the ready-to-use soup.yaml |
runs_list | soup runs | Recent experiment runs from the local tracker |
runs_show | soup runs show | One run's full record (accepts an id prefix) |
registry_list | soup registry list | Registry entries, filterable by name/tag/base/task |
registry_show | soup registry show | One entry by id / prefix / name:tag / registry:// ref |
profile | soup profile | Memory / speed / GPU-fit estimate from a soup.yaml (no model load) |
diagnose_evidence | soup diagnose --evidence | Failure-mode report card from a pre-computed evidence JSON |
ship_evidence | soup ship --evidence | SHIP / DON'T-SHIP verdict from a pre-computed evidence JSON |
2 planning tools (behind --allow-mutating)
| Tool | Backing command | Behaviour |
|---|---|---|
train_start | soup train | Validates a soup.yaml, returns {config_valid, task, base, would_run, note}. Never executes by itself |
export | soup export | Validates the format, returns {format, would_run, note}. Never executes by itself |
Both are always listed but refuse to run unless you started the server with --allow-mutating, and even then they only render the exact command that would run. Calling them while disabled returns a clean isError telling you to restart with the flag.
Under --allow-execute they do one thing more: each returns a confirmation_token for the plan it just built. That token is the only way to reach the two tools below.
2 executing tools (behind --allow-execute, v0.73.3)
| Tool | Runs | Accepts |
|---|---|---|
train_execute | The training the matching train_start planned | confirmation_token and nothing else |
export_execute | The export the matching export planned | confirmation_token and nothing else |
Both are always listed, carry a destructive annotation, and refuse unless the server was started with --allow-execute. They return {run_id, status, pid, log_path}.
The execution model
The interesting property is what the client is not allowed to send. There is no command parameter, no argv, no shell string, and no environment: the argv was built server-side at plan time, and the only thing a caller can do is name a plan it already made.
- The token is server-generated, random, bound to both the plan and the execution kind, single-use, and expires after five minutes. A token from a
train_startcannot run an export. - The config is snapshotted at plan time and the run executes from that copy, so the file cannot be edited underneath a plan that was already approved.
- Protected inputs are re-validated by content, not by timestamp. The digest walks a directory tree by sorted relative path and per-file hash, refusing symlinks and bounded in both file count and bytes. The earlier mtime-and-size check did not change when a file *inside* a protected directory was rewritten, which meant a model could be swapped between planning and execution and still pass.
- The process is spawned with
shell=Falseandstdinclosed, its working directory pinned to where the server started, and its output redirected to.soup/mcp-runs/<run_id>.log. - The token is consumed, and capacity taken, before the process is spawned. A failed spawn therefore requires a fresh plan rather than allowing a replay.
- One execution at a time, gated on a persisted run whose recorded process is still alive. That survives a restart: a live child from a previous server blocks a new run, while a stale record whose process is gone frees the slot instead of wedging it shut.
- The run goes through the normal experiment tracker, so
soup runssees what MCP started.
What it does not do: the snapshot freezes the configuration, not external filesystem assets, and a launch is fire-and-forget, so disconnecting your MCP client does not stop a subprocess that is already running.
Security model
Every point below is implemented and tested:
- stdio only — no network listener at all. stdout is reserved for the JSON-RPC channel; all human-facing text goes to stderr, and handler stdout is redirected so a stray
print()can't corrupt the stream. - Path containment — every path argument re-enters cwd-containment + symlink rejection (
O_NOFOLLOW+ fstat TOCTOU defence on reads). - Output sanitization — C0/ESC/DEL bytes are recursively stripped from every returned string (tab/newline/CR kept), so a malicious dataset string can't smuggle ANSI/OSC terminal escapes into your client.
- Path-free errors — a failing handler becomes a clean
isErrorresult; no filesystem path or stack trace leaks, and the server survives. - Bounds — string args ≤ 4096 chars, JSON args ≤ 16 MiB, dataset loads ≤ 1 GiB, int args range-checked (rejected, not silently clamped).
- Execution is default-off and separately gated —
--allow-mutatingalone can never spawn a process. With--allow-executeon, authorization is a server-issued one-time token: any client confirmation prompt you see is presentation, and the security lives entirely in the server-side token state.
Install note
The [mcp] extra pulls one dependency, mcp>=1.2.0,<2 (the official MCP Python SDK), and is lazy-imported — only the server module touches it, so the core CLI and the tool registry stay PyTorch-free and SDK-free. If you run soup mcp serve without the extra, you get a friendly one-line install hint and exit 1. The extra is also folded into [all] and [dev].
SSE/HTTP transport is a filed follow-up; the released server is stdio only.
See also
- v0.73.3: four flags that did nothing — the release that opened the execution gate.
- Fine-tune Doctor —
data_doctoris one of the read-only tools here. - soup ship —
ship_evidencegives the SHIP / DON'T-SHIP verdict as a tool call. - advise / diagnose — the decide-and-report tools your agent can call directly.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.