Open-source MCP server · Mass spectrometry

Ask your AI assistant about your mass spectrometry data.

MSMCP puts your own data in front of an AI client — Claude, Zed, or any other MCP host. Ask it to open an mzML, MGF or imzML file, check a precursor mass against a formula, compute isotope and adduct patterns, score an MS/MS spectrum against your MSP library, or summarise QC across a run.

The model reasons. The tools compute.

MCP — the Model Context Protocol — is the open standard that lets an assistant call tools and read files it is given. MSMCP is the mass spectrometry end of it.

  • 14 tools
  • mzML · MGF · imzML
  • stdio, local only
  • no credentials
  • MIT

Run it now

git clone https://github.com/janusson/MSMCP.git
cd MSMCP
uv sync
uv run msmcp

How the peaks stay out of the prompt

  1. 1experiment.mzML — 220 peaks, parsed in the server process. mzML, MGF and imzML are read locally; vendor formats are refused with conversion guidance.
  2. 2load_spectrum(…) returns metadata and a reference.
  3. 3
    reference =
    "ptr:spectrum:264a5ae9…"

    An opaque, random, expiring handle — never an address, an id or a path.

  4. 4Every later tool dereferences it server-side: validate_precursor · compute_cosine · generate_qc_summary · search_library

The model carries references, job IDs and verdicts — never spectra.

Example

One question, four tool calls.

The transcript below is a real run of MSMCP against a real acquisition and a real spectral library. The library scan is the slow step, and it never blocks the conversation — it is dispatched as a job and polled.

session · MSMCP 1.0.0 over stdio · reference and job IDs shortened
You

“I have a peak eluting at 3.80 min, precursor m/z 315.2321, MS2. Is it Δ⁹-THC?”

load_spectrum(file_path="experiment.mzML", spectrum_index=966)

kind=spectrum  format=mzML  backend=msmcp.mzml  ms_level=2
precursor_mz=315.2321  n_peaks=220  mz_range=[65.0382, 318.2416]
reference="ptr:spectrum:264a5ae9…"← the 220 peaks stay on the server

validate_precursor(theoretical_mass=315.2320, experimental_mass=315.2321)

VALIDATION PASSED — mass error 0.32 ppm, within the 5.0 ppm gate for the generic instrument class.

search_library(spectrum_reference="ptr:spectrum:264a5ae9…", database_file="libraries/MASSBANK.msp", scoring_method="classical")

Job ID: 2aafd7b8… — scanner reads the library from disk through the security boundary and searches it for real. The tool call returns immediately.

check_search_status(job_id="2aafd7b8…") → running …
check_search_status(job_id="2aafd7b8…") → completed

63,879 library spectra scored against 220 query peaks · classical greedy peak matching, ±0.02 Da · 94.9 s · 6 hits at q ≤ 0.05

Spectral library search candidates
Candidate (MassBank accession)Cosineq
1.Δ⁹-tetrahydrocannabinolEQ362902 · C21H30O20.83090.0125
2.Δ⁹-tetrahydrocannabinolAU158302 · C21H30O20.78420.0125
3.Δ⁹-tetrahydrocannabinolAU158301 · C21H30O20.69580.0333
4.progesteroneAU107706 · C21H30O20.69220.0333
5.progesteroneAU107702 · C21H30O20.68620.0333
6.Δ⁹-tetrahydrocannabinolEQ362901 · C21H30O20.68270.0333

The report MSMCP returns leads with its own caveat: these are candidate library matches ranked by the stated scorer and significance model, not validated compound identifications. Note the competition — the runner-up candidates are progesterone, a constitutional isomer at the same precursor m/z. That is what a deterministic score buys an analytical chemist: the whole field, not one confident answer. The q-values beside those candidates are the tool's own significance estimate and are not yet calibrated — MSMCP's scientific validation record marks that capability not validated — so read them as a ranking aid, not as an error rate.

Provenance of this transcript: MSMCP 1.0.0: search_library with scoring_method="classical", run against MassBank's public MSP library (63,879 spectra) read from disk, querying spectrum #966 of a Bruker maXis LC–MS/MS acquisition (RT 3.795 min, precursor m/z 315.2321, 220 peaks, m/z range 65.0382–318.2416). The ppm verdict is a fresh validate_precursor call against MassBank's reference mass. Beyond shortened IDs, relative library paths and a stand-in filename for the acquisition, the numbers are the tools' own output, unedited — and the acquisition's real filename is not published here.

Why MSMCP

Grounded, deterministic, and honest about its own boundaries.

LLMs should reason about mass spectrometry, not perform the arithmetic. Every scientific value on this page is a tool's output rather than a model's recollection, and every derived result carries a record of how it was made. That is the standard AI-assisted mass spectrometry should be held to, and MSMCP is infrastructure for it.

Grounded analysis

The model calls the tool; the tool does the chemistry. Exact masses, adduct shifts, isotope envelopes, ppm gates and cosine scores are computed by pure, unit-tested Python — the same functions every time, answering the same way. The model never invents a mass and never does the arithmetic.

The isotope envelope on this page is annotate_isotopes output for glucose; the 0.32 ppm verdict above is validate_precursor output. Neither was written by a language model.

Deterministic and auditable

Reproducible execution is separated from the model's variable interpretation. Fast operations return immediately; long ones — a library scan is minutes — are dispatched to a job executor and polled, so a client timeout can never truncate a result.

Every derived object carries an immutable provenance record: the operation, its parameters, the source file with reader backend and SHA-256 digest, the parent references, and the software and model versions. When an answer is wrong, you can tell whether the tool failed or the interpretation did.

Data control

MSMCP runs as a local child process on stdio: it makes no network calls of its own, stores no credentials, and ships no library data. Every file read is confined to one allowed root, under file-size and spectrum-count caps, and a path that escapes that root — symlinks included — is refused rather than read.

Its boundary is stated, not implied: the host spawns the server with your credentials, so anyone who can drive your LLM client can drive the server. Run it on a machine you own, and never expose it as a network service.

Tool output · annotate_isotopes

annotate_isotopes("C6H12O6")

M · 180.0634M+1 · 181.0668M+2 · 182.0681
Relative abundance, ×100 zoomM = 1.0000
0.06860.0147

M+1 and M+2 are shown on a separate scale; on the full-scale panel above they are 6.86 % and 1.47 % of the base peak. Values are the tool's output, reproduced without rounding.

Also verified in the tree

  • 14 tools across inspection, chemistry, similarity, quality control and library search — every one asserted by the test suite, and the wire schemas checked separately so no parameter reaches a model undocumented.
  • Readers for mzML (including zlib-compressed arrays, parsed by MSMCP itself), strict MGF, and imzML imaging data through MassFlow. Vendor formats are refused with conversion guidance instead of a stack trace.
  • MSP/NIST libraries are read from disk behind the same security boundary as an acquisition, so search_library searches the libraries you already have — no upload, no hosted service.
  • Empirical evaluation ships with the code: a notebook drives the real server end to end and fails the gate if a registered tool is never exercised or a workflow's context cost grows by half.
  • Spectral foundation models plug in behind one adapter (DreaMS runs real inference; LSM-MS2 awaits public weights). Core MSMCP never requires model weights — and if a model is unavailable it says so rather than returning a number that looks learned.

Get started

Install from source: clone, sync, run.

MSMCP is not published to PyPI yet, so there is no package to install and no binary to download: the path today is a clone, synced with uv. You need uv and Python 3.13, which .python-version pins for you. No API key, no account, no server to sign up for.

Clone, sync, run

git clone https://github.com/janusson/MSMCP.git
cd MSMCP
uv sync
uv run msmcp

Optional extras: --extra chem adds RDKit so SMILES can be converted to a formula; embedding-based scoring needs the DreaMS model installed separately from source. Neither is required for the core server.

Point a client at it

{
  "context_servers": {
    "msmcp": {
      "command": "/absolute/path/to/uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/MSMCP",
        "msmcp"
      ],
      "env": {
        "MSMCP_ALLOWED_ROOT": "/absolute/path/to/ms-data"
      }
    }
  }
}

Shown for Zed's context_servers; any host that can spawn a stdio MCP server configures the same command and arguments. Replace all three paths: uv itself (command -v uv), your clone, and the directory holding your acquisitions and libraries. MSMCP_ALLOWED_ROOT is the file boundary — without it the server falls back to whichever directory the client happened to start it in.

Tools

Fourteen tools, one question each.

Every call below is the signature the server actually advertises, with the argument names and defaults a client receives from tools/list. Tools that take a spectrum you have already loaded take the reference handle, not the peaks.

Inspection and references

load_mzml_summary

What is in this file?

load_mzml_summary(file_path="/data/run.mzML", max_spectra=5)

Per spectrum: MS level, retention time, total ion current, peak count, base peak. The footer states the file's total spectrum count.

list_spectra

Which spectra are MS2, and which index do I want?

list_spectra(file_path="/data/run.mzML", ms_level=2)

An index table — spectrum index, MS level, retention time, precursor m/z, peak count — so the next call names a real spectrum.

load_spectrum

Hold one spectrum server-side.

load_spectrum(file_path="/data/run.mzML", spectrum_index=966)

Metadata plus a ptr:spectrum:… handle. The peak array stays in the server's memory instead of entering the conversation.

summarise_reference

What is behind this handle?

summarise_reference(reference="ptr:spectrum:264a5ae9…")

The same compact spectrum block, read from memory rather than the file, followed by the reference's provenance.

release_reference

Free that memory now rather than waiting for expiry.

release_reference(reference="ptr:spectrum:264a5ae9…")

Confirmation. Releasing an unknown or already-released reference is reported, not raised, so a retry is safe.

Exact-mass chemistry

validate_precursor

Does this precursor mass fit the formula I think it is?

validate_precursor(theoretical_mass=315.2320, experimental_mass=315.2321)

Mass error and a PASSED or REJECTED verdict against the named instrument class — 5.0 ppm for the generic class — with the tolerance stated.

predict_adduct_offset

What does [M+H]+ add, exactly?

predict_adduct_offset(adduct_string="[M+H]+")

Polarity, charge state, exact mass shift in daltons and the offset formula. One of fourteen canonical adducts; anything else is refused.

annotate_isotopes

What should the isotope envelope look like for this formula?

annotate_isotopes(identifier="C6H12O6", isotopologues=3)

Monoisotopic mass and the M, M+1, M+2 masses with relative abundances normalised to M = 1.0000, from the exact multinomial expansion.

Spectral similarity

compute_cosine

How similar are these two MS/MS spectra?

compute_cosine(query_reference="ptr:spectrum:…", reference_peaks=[[mz, intensity], …])

A cosine score under the class's m/z window with the matched-peak count, computed by greedy one-to-one peak matching.

Quality control

generate_qc_summary

Was this run worth searching?

generate_qc_summary(file_path="/data/run.mzML")

Spectrum and total-ion-current counts, peak density, an estimated signal-to-noise ratio and diagnostic-ion prevalence. Reads the whole file, so it is the slow inspection call.

search_library

Match this MS2 against the library I already have.

search_library(database_file="/data/libraries/MassBank.msp", spectrum_reference="ptr:spectrum:…", scoring_method="classical")

A job ID straight away. The MSP library is read from disk through the same boundary as an acquisition, and the scan runs in the background.

check_search_status

Is the scan finished?

check_search_status(job_id="2aafd7b8…")

Running, completed or failed — then the ranked report, naming the scorer, tolerances, library source and provenance.

cancel_search

Stop the scan.

cancel_search(job_id="2aafd7b8…")

Cancellation. A job that already finished is reported as finished, and a cancelled scan's partial work is discarded.

Diagnostics

ping

Is the server alive, and can it see its dependencies?

ping()

Operational status and the MassFlow version it can import. Call it first when another tool fails unexpectedly.

Only cancel_search and release_reference change server state, and both advertise that on the wire so a host can gate them. Parameter bounds, defaults and validation live in the server's own tool schemas — this list is the map, the README is the reference.

Documentation & links

Everything authoritative lives with the code.

There is no separate documentation site, because two copies of a contract drift. Each link below goes to a maintained file in the repository.

Contact. There is no mailing list and no contact form. Bug reports, questions and feature discussion belong in GitHub Issues, where the answer stays searchable for the next person.