PanelAgent exposes its SQLite inventory and deterministic panel computations as ten read-only MCP tools. A compatible MCP host can discover the laboratory's libraries and instruments, inspect available antibodies, generate candidate panels, and diagnose conflicts. The server does not call an LLM, import inventory, annotate antibodies, or save panel history.
This integration uses stdio and the official Python MCP SDK, with the supported
dependency range mcp>=1.29.1,<2. A host that requires a remote HTTP URL cannot
connect directly to this stdio server. The Web workbench
uses a Next.js server-side MCP client to bridge browser requests to this process.
From a PanelAgent checkout, install into a dedicated environment:
python3 -m venv .venv
.venv/bin/python -m pip install -e '.[mcp]'Or install the release wheel with the same extra
(pip install "panelagent-<version>-py3-none-any.whl[mcp]"); the packaged
seed data ships inside the wheel, so no checkout is needed. See
distribution.md for artifacts and verification.
Use an explicit database path. This example creates a separate demonstration
database and imports the small repository fixture as the Mouse library:
.venv/bin/python -m panelagent init --db /tmp/panelagent-mcp-demo.db --config-dir config --csv Mouse=tests/fixtures/panel_inventory.csv
.venv/bin/python -m panelagent db stats --db /tmp/panelagent-mcp-demo.db --jsonFor laboratory use, substitute the intended persistent database path, instrument
configuration, and --csv Library=/absolute/path/inventory.csv. A library name is
an arbitrary inventory label, not a required species code. Initialization without
an inventory import provides seed data but no antibody libraries.
MCP uses path precedence --db > PANELAGENT_DB >
~/.local/share/panelagent/panelagent.db. It opens an existing schema-version-1
database read-only; missing, uninitialized, or unsupported databases produce tool
errors. Starting the server does not create tables, seed data, or import files.
Each call closes its database connection even when it fails.
For hosts using a mcpServers configuration object, adapt
examples/mcp-client.json:
{
"mcpServers": {
"panelagent": {
"command": "/absolute/path/PanelAgent/.venv/bin/python",
"args": [
"-m", "panelagent", "mcp",
"--db", "/absolute/path/lab.db"
]
}
}
}Replace both absolute paths before use and point to the interpreter where the
package and its MCP extra were installed. With this installation, the client can
launch the server from any working directory; it does not need PYTHONPATH or a
shell activation command. The host launches the process and communicates through
stdin/stdout. Configuration file locations vary by host.
For a manually launched process, the equivalent command is:
/absolute/path/PanelAgent/.venv/bin/python -m panelagent mcp --db /absolute/path/lab.dbWaiting silently for protocol input is expected. This is not an HTTP service and
has no page to open in a browser. Do not add shell banners, print statements, or
logging to stdout; diagnostics belong on stderr. No API key is required for the
server. Model credentials, if needed, belong to the MCP host.
All tools are read-only or pure computation. Their MCP annotations describe this behavior; no write tool is exposed. Tool discovery supplies JSON input and output schemas, descriptions, and parameter bounds.
| Tool | Parameters | Result |
|---|---|---|
db_stats |
None | Row counts for each core table. |
list_libraries |
None | Library names and antibody_count, ordered by library. |
list_instruments |
None | Instrument id, vendor, model, and channel_count (including blocked channels), ordered by ID. |
list_markers |
Required library; limit=100, offset=0 |
Target names and antibody_count in that library; excludes missing/placeholder targets. |
list_fluorochromes |
Optional laser, instrument_id |
Known dye information and available mapping/brightness data. |
get_fluorochrome |
Required name; optional instrument_id |
Exact name or configured alias resolution, or null if unknown. |
list_channels |
Optional laser, instrument_id |
Instrument channel rows, laser metadata, and blocked status. |
search_antibodies |
Optional library, target, keyword, flag; limit=100, offset=0 |
Matching inventory rows, including quality fields. |
generate_panel |
Required library, markers; max_solutions=10, include_bad=false; optional instrument_id |
Candidate assignments or a completed no-solution result with diagnosis; includes selected instrument_id. |
diagnose_panel |
Required library, markers; include_bad=false; optional instrument_id |
Missing markers and detected channel conflicts, with selected instrument_id. |
Instrument-dependent tools select the sole instrument automatically. When the
database contains multiple instruments, first call list_instruments and pass an
explicit instrument_id; the server must not merge mappings across instruments.
An unknown instrument ID or missing instrument configuration is an error. Use
laser names from list_channels, such as Blue or Violet, when filtering.
Channels with blocked=true are visible for inspection but excluded from panel
generation and usable laser capacity.
Library names are exact labels obtained from list_libraries. Antibody target
search is case-insensitive exact matching; keyword searches target,
fluorochrome, clone, brand, and catalog number, using SQL LIKE matching (% and
_ act as wildcards). flag accepts good, warn, or
bad; omitting it includes every quality state. Inventory rows with missing
target or fluorochrome are displayed with - placeholders. Missing brightness
or spectrum data remain unknown and must not be invented. list_fluorochromes
starts from stored spectra; it is not an exhaustive list of inventory labels.
Use antibody search and exact dye lookup to inspect labels that lack spectra.
Both paginated tools accept limit from 1 to 500 and nonnegative offset.
Results remain lists, with no total-count envelope. Increase offset by limit
until a page contains fewer than limit rows. Discovery counts include inventory
rows regardless of quality or whether they can be used on the selected
instrument; a count is not proof that a marker has a usable antibody.
Text parameters are trimmed and must contain 1–128 characters. Panel tools accept
1–64 marker strings. Marker names must be nonempty after normalization and must
be unique after the core's case/punctuation normalization
(for example, PD-1 and pd1 are duplicates). Generation accepts
max_solutions from 1 to 100. Both generation and diagnosis exclude bad
antibodies unless include_bad=true; keep this setting consistent between calls.
After MCP initialization, discover tools with tools/list, then perform these
tools/call operations. The ID below is illustrative: use the actual value
returned by list_instruments.
- Call
db_stats,list_libraries, andlist_instrumentswith{}to confirm inventory availability and select a library and instrument. - Call
list_markerswith{"library":"Mouse","limit":100,"offset":0}to inspect marker names. Read additional pages if needed. - Call
search_antibodieswith{"library":"Mouse","target":"CD3"}to inspect matching inventory and quality notes. - Call
generate_panelwith{"library":"Mouse","markers":["CD3","CD4","CD8"],"instrument_id":1,"max_solutions":10}. - Inspect each candidate's
markersassignments,brightness_summary,warnings,laser_distribution, andlaser_capacity. Every assignment identifies anantibody_id, fluorochrome, and channel. - When investigating limitations, call
diagnose_panelwith the same library, markers, instrument, and quality setting. A generation result with no candidates already includes diagnosis.
The host should preserve the user's experimental conditions between calls and explain constraints before changing them. An unavailable marker is a reason to discuss alternatives, not permission to silently drop a required target.
Check MCP CallToolResult.isError before interpreting business fields. SDK
structured results for dictionary-returning tools contain the dictionary itself;
list and nullable returns are wrapped in structuredContent.result. The SDK also
provides text content, which can be empty for a null result or an empty list.
Use the advertised output schema rather than
assuming every response has the same outer shape.
| Situation | MCP result | Interpretation |
|---|---|---|
| Valid candidate found | isError=false, business status="success" |
Read candidates; these are deterministic assignments. |
| Completed generation finds no panel | isError=false, business status="error", candidates=[] |
Read diagnosis; the tool completed successfully. |
| Diagnosis finds missing markers/conflicts | isError=false, business status="conflict" |
Constraints or inventory need attention. |
| Diagnosis finds no obvious conflict | isError=false, business status="ok" |
This limited diagnostic does not prove that a valid assignment exists. |
| Unknown dye or no search matches | isError=false, null or [] |
Valid lookup with no match. |
| Bad parameters, unknown library/instrument, unavailable database | isError=true |
Correct the input or initialization problem before retrying. |
| Generation exhausts 100,000 search nodes | isError=true |
Search was interrupted by its computation budget; infeasibility has not been established. |
Generation enforces supported channel assignments and prevents duplicate channel use. Laser load balancing is a candidate-ordering preference. These results do not calculate a complete spillover/spreading model, optimize marker expression against dye brightness, or replace experimental validation. Diagnosis detects missing options and selected channel bottlenecks; it is not a complete feasibility solver. Neither tool provides an LLM evaluation.
If installation is missing, install the mcp extra in the configured Python
environment. For missing or uninitialized databases, run pa init --db PATH
explicitly and import the intended inventory. For empty discovery results,
inspect db_stats and initialization/import reports. For ambiguous instruments,
select an ID with list_instruments. Use the same database path for CLI updates
and MCP; subsequent tool calls read the updated inventory.
The protocol tests launch a real stdio subprocess against temporary databases; they do not import fixtures into the default laboratory database. From the repository root, using an environment with the test dependencies installed:
PYTHONPATH=. python3 -m pytest tests/core/test_mcp.py -q
PYTHONPATH=. python3 -m pytest tests/ -q
python3 -m ruff check panelagent/ tests/core/