MCP server for AI coding agents#

Install for this page: pip install "scope-profiler[mcp]". To use the optional plot_profile tool too, install pip 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?”:

  1. The agent calls run_profile on a benchmark script before making any change, and keeps the returned file_path (or passes output_path itself to control where it lands) as the baseline.

  2. It edits the code.

  3. It calls run_profile again on the same script for the candidate run.

  4. It calls compare_profiles with the two file paths and reads overall.faster and overall.speedup directly, plus regressions/ improvements to see which regions moved.

  5. It reports the result (and, optionally, calls plot_profile to 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()))