AI Agents (MCP)

graflag mcp serves GraFlag over the Model Context Protocol, so an AI agent can manage the platform with the same operations as the CLI: list methods and datasets, launch and watch runs, evaluate and verify them, and read what they produced. It runs on your machine beside the client and drives the cluster the same way the CLI does, over SSH.

Installing

The server needs the MCP SDK, an optional dependency that needs Python 3.10 or later (the rest of GraFlag runs on 3.8):

pip install "graflag[mcp]"

The extra pins the SDK below 2.0. Version 2 removed the FastMCP API the server is built on, and other MCP servers installed in the same environment may still import it.

Connecting a client

The server speaks MCP on stdio: the client starts graflag mcp and talks to it over its stdin and stdout. It uses the same configuration as the CLI (see Quick Start), or the file given with --config.

Claude Code:

claude mcp add graflag -- graflag mcp
claude mcp add graflag -- graflag mcp --config ~/.config/graflag/lab.env   # another cluster

Add --scope user to have it in every project rather than only the current one.

Claude Desktop, and other clients configured with JSON:

{
  "mcpServers": {
    "graflag": {"command": "graflag", "args": ["mcp"]}
  }
}

The server never prompts. SSH runs in batch mode, so the key it uses must work without a passphrase prompt (an unencrypted key, or one loaded in ssh-agent), and an unreachable manager fails after 15 seconds instead of hanging a tool call. A configuration problem is reported on the first tool call, with how to fix it; listing the tools works without one.

Tools

Tool

What it does

cluster_status

The manager, the Swarm, its nodes and services, the shared directory

read-only

list_methods

Every method: description, upstream or reimplementation, accepted datasets, image

read-only

method_details

One method’s parameters with their defaults, source and README

read-only

list_datasets

Every dataset, with size and file count

read-only

list_experiments

Recent experiments, filtered by method, dataset or status

read-only

get_experiment

One experiment’s status, and any run or evaluation this server started for it

read-only

run_experiment

Start a run and return its name at once

wait_for_experiment

Wait, up to a timeout, until the experiment and its jobs have finished

read-only

get_logs

The last lines of a run’s output

read-only

stop_experiment

Stop a run; its directory is kept

destructive

evaluate_experiment

Start computing metrics and plots, and return at once

get_results

Result type, run time, peak memory, the method’s own summary; never the scores

read-only

get_evaluation

The metrics, and the names of the plots

read-only

get_plot

One plot, as an image

read-only

verify_experiment

The checks of graflag verify

read-only

cleanup_services

Remove the services of finished runs (see graflag cleanup)

destructive

storage_report

What graflag clear would reclaim; removes nothing

read-only

The last column is sent to the client as tool annotations, which is what lets a client ask before a destructive call.

Deliberately not offered: graflag clear --apply and --gc, stop --rm (which deletes the experiment directory), setup, copy, sync, and register_metric, which writes code that the evaluator runs on the cluster as root. Those stay with a person at the CLI.

How a run proceeds

A run takes seconds to hours, longer than a client will wait for one tool call. run_experiment checks that the method and dataset exist, starts the run in the background and returns the experiment name. wait_for_experiment then blocks for up to timeout_seconds (at most 600, default 120) and returns the status either way, with finished saying which; an agent calls it again to keep waiting. When the run ends, its service is removed, as graflag run does.

evaluate_experiment works the same way, and wait_for_experiment also waits for an evaluation this server started. A failure that happens before the run has written anything – a build that fails, say – is reported by get_experiment and wait_for_experiment from the job itself.

A session that runs AnoGraph on the ISCX stream, recorded on the development cluster (timings from the start of the session):

run_experiment      {"method": "anograph", "dataset": "anograph_iscx"}
                    -> exp__anograph__anograph_iscx__20260924_132705, started      2.8 s
wait_for_experiment -> completed, finished                                      13.7 s
evaluate_experiment -> evaluating
wait_for_experiment -> completed, finished                                      30.5 s
get_evaluation      -> auc_roc 0.948, auc_pr 0.523, 2751 samples
verify_experiment   -> 0 failed, 1 warned, 3 passed

The session left nothing running: both services were removed when their jobs ended.

On a development cluster, run one method at a time: its nodes share one host’s memory. The server does not enforce this.

Parameters

run_experiment takes params as an object, e.g. {"EPOCHS": 5}. Names are those of the method’s .env with or without the leading underscore, in any case; they are sent upper-case, as the .env declares them. Values must be strings, numbers or booleans. A name GraFlag sets itself (DATA, EXP, METHOD_NAME, COMMAND, MONITOR_INTERVAL) is refused with an error, where the CLI would drop it.

What an agent can and cannot send

Everything an agent sends is treated as untrusted, like a request to the web dashboard. Method, dataset, experiment, tag and plot names must be plain identifiers (letters, digits, ., _, -); anything else is refused before it reaches a command that runs on the manager.

For maintainers: stdout is the protocol

A stdio MCP server’s stdout carries its replies and its stdin carries the client’s requests, so anything else that writes to one or reads the other breaks the session. graflag mcp guards both: it gives the protocol private copies of the two pipes, points file descriptor 1 at stderr and 0 at /dev/null, and moves sys.stdout to stderr once the transport has started. Separately, every core call it makes waits without streaming (follow=False), and every ssh and rsync GraFlag starts has its stdin closed. tests/test_mcp.py runs a real stdio session against a backend that prints and starts a subprocess writing to stdout and reading stdin, and checks that the session survives. Keep new server code off print(); log instead, which goes to stderr.