MCP server for AI coding agents#
Install for this page:
pip install "scope-profiler[mcp]". To use the optionalplot_profiletool too, installpip install "scope-profiler[mcp,pproc]".
MCP (Model Context Protocol) is a
standard way for an AI coding agent - Claude Code, or any other
MCP-capable client – to call tools exposed by a local server.
scope-profiler-mcp is such a server: it exposes profiling,
benchmarking and comparison as structured tools, so an agent can inspect
a run’s performance and check whether a code change helped, as part of
its own workflow, without you copy-pasting tables into the chat.
It is a thin adapter, not a second implementation: every tool calls
straight into the same Python API Plotting with the CLI, CLI reference and the
HDF5 output & post-processing from Python page describe — read_h5, the region_rows/
diff_rows used by inspect/diff, ProfileManager.run_script, and
the plotting_scripts functions behind plot. Nothing about profiling,
HDF5 parsing, or comparing runs is reimplemented for MCP.
Claude Code
| MCP (stdio)
v
scope-profiler-mcp
| the same Python API the CLI uses
v
scope-profiler
|
v
inspect / benchmark / compare -> structured JSON
This page covers using the MCP server as it stands today: an agent can
read profiling data and judge whether a change helped. It does not
modify your code, run an optimization search, or make commits - an agent
using these tools still has to decide what to try next and make the edit
itself, the same as it would from a git diff and your instructions.
Installing#
pip install "scope-profiler[mcp]"
This is a separate extra from the base install:
pip install scope-profiler alone does not pull in the mcp package,
so normal profiling and the scope-profiler CLI are unaffected either
way. plot_profile (see below) additionally needs the pproc extra,
exactly as scope-profiler plot does:
pip install "scope-profiler[mcp,pproc]"
Starting the server#
scope-profiler-mcp
or equivalently:
python -m scope_profiler.mcp_server
This speaks MCP over stdio and is meant to be launched by an MCP client
(Claude Code, another agent, or mcp dev/an MCP inspector for manual
testing) - not run directly in a terminal you intend to type into.
Configuring Claude Code#
Add it as an MCP server, either via the CLI:
claude mcp add scope-profiler -- scope-profiler-mcp
or by adding it directly to your project’s .mcp.json (or Claude Code’s
global MCP config):
{
"mcpServers": {
"scope-profiler": {
"command": "scope-profiler-mcp"
}
}
}
If scope-profiler-mcp is not on PATH (e.g. installed into a
virtualenv Claude Code does not activate), point command at the
interpreter instead:
{
"mcpServers": {
"scope-profiler": {
"command": "/path/to/venv/bin/python",
"args": ["-m", "scope_profiler.mcp_server"]
}
}
}
Once configured, restart Claude Code (or run /mcp to check connection
status) and the six tools below become available for it to call.
Troubleshooting: CONNECTION_CLOSED / ModuleNotFoundError: No module named 'mcp'#
scope-profiler-mcp resolves to whichever interpreter’s bin/ the
command is found in first on PATH. If that interpreter has
scope-profiler installed without the mcp extra (e.g. a
global/system Python, while the extra was only installed into a project
virtualenv), the process crashes immediately on import and the client
reports a generic closed-connection error rather than the underlying
ModuleNotFoundError. Run scope-profiler-mcp directly in a terminal
to see the real traceback, then either
pip install "scope-profiler[mcp]" into the interpreter actually on
PATH, or point command/args at the virtualenv interpreter as shown
above.
Available tools#
inspect_profile#
Structured version of scope-profiler inspect: runtime, rank count,
region statistics (sorted, capped at top_n - raise it or pass 0 for
no limit), metadata grouped the same way (run info, hardware,
MPI/OpenMP, Slurm), and a LIKWID hardware-counter summary when the run
recorded one. Large, rarely-useful metadata (raw environment variables,
unrecognized fields) is collapsed to a count by default;
full_metadata=True returns everything. The run and Slurm sections are
always returned, so they include the user name, host name, working
directory and job variables of a file recorded with
metadata_detail="full"; the default "minimal" keeps those out of the
file in the first place (see HDF5 output & post-processing from Python).
compare_profiles#
Structured version of scope-profiler diff, plus the whole-run
comparison diff does not compute: given a baseline and a candidate
file, it returns overall.faster (bool), overall.speedup,
overall.relative_change_pct and overall.absolute_diff_seconds,
alongside the per-region deltas and a regressions/improvements split
by threshold_pct. The arithmetic is done here, in Python - an agent
should never need to subtract two numbers from two separate
inspect_profile calls to answer “did this get faster?”.
run_benchmark#
Structured version of the declarative benchmark runner described in
AGENTS.md: runs benchmark.script from a benchmark.toml-style
config the configured number of times, runs the correctness check, and
returns a manifest (timing statistics plus a pass/fail correctness
result) tagged with label. Prefer this over run_profile when a repo
already has a benchmark.toml - it gives repeated-run statistics and a
correctness gate in one call instead of a single profiling run.
compare_benchmarks#
Compares two manifests produced by run_benchmark and returns a
decision: "keep" / "reject" verdict alongside the timing deltas -
the same keep/reject rule the CLI optimization workflow in AGENTS.md
uses, so an agent following its “benchmark, change, benchmark, compare”
loop does not have to reimplement the threshold logic itself.
run_profile#
Runs a script under the profiler (equivalent to scope-profiler run)
and returns an inspect_profile-style summary of the result. The script
runs in its own subprocess, with a timeout, so it cannot hang or crash
the MCP server; a non-zero exit, a timeout, or a missing script all come
back as a clear tool error rather than a partial result. script_args
are passed through as a plain argument list (no shell involved),
matching how ProfileManager.run_script already works.
plot_profile (optional)#
Renders one figure (gantt, flame_chart, flame_graph, durations,
timeseries, speedup) via the same functions scope-profiler plot
uses, and returns the path it wrote. Most agent workflows should prefer
the numbers from inspect_profile/compare_profiles; reach for this
only when a human is going to look at the image afterwards
(e.g. attaching it to a PR). Requires the pproc extra. For speedup,
x_field picks the x-axis: num_ranks (default), num_nodes,
omp_num_threads, total_cores, or any other metadata field every file
recorded; a file missing it is a tool error.
Example: an agent judging a code change#
A typical loop, in words, for “is this function faster after my change?”:
The agent calls
run_profileon a benchmark script before making any change, and keeps the returnedfile_path(or passesoutput_pathitself to control where it lands) as the baseline.It edits the code.
It calls
run_profileagain on the same script for the candidate run.It calls
compare_profileswith the two file paths and readsoverall.fasterandoverall.speedupdirectly, plusregressions/improvementsto see which regions moved.It reports the result (and, optionally, calls
plot_profileto attach a Gantt or duration chart) - and decides, itself, whether to keep the change, try something else, or ask you.
Step 5 is where this stops: the agent reasons about the structured
numbers you get from steps 1-4, the same as it would reason about a
git diff, but scope-profiler does not close the loop by editing code
or committing on its own.
Testing without a live client#
The server’s tools are plain Python functions under
scope_profiler.mcp_server.tools, importable and testable without the
mcp package at all:
from scope_profiler.mcp_server.tools import inspect_profile
print(inspect_profile("profiling_data.h5", top_n=5))
scope_profiler.mcp_server.server.create_server() builds the full MCP
server object (tool registration, JSON schemas) without starting a
transport, useful for scripting against it directly:
import asyncio
from scope_profiler.mcp_server.server import create_server
server = create_server()
print(asyncio.run(server.list_tools()))