An MCP (Model Context Protocol) server that exposes Dockstore to AI assistants and other MCP clients.
Dockstore is a registry of bioinformatics tools and workflows described in CWL, WDL, Nextflow, and Galaxy. It serves them through the GA4GH Tool Registry Service (TRS) API and through Dockstore's own API. This server is a standalone process that sits in front of those APIs and speaks MCP, so it is deployed alongside the Dockstore webservice rather than inside it.
It is built on FastMCP 4 and ships as a container image.
Status: early. All of the tools work: the GA4GH TRS tools (
get_trs_infothroughget_tool_descriptor_by_path) and the four Dockstore tools (search_entries,get_entry,get_version, andget_file).
- Python 3.11 or newer (the container image uses 3.13)
- Docker, if you want to build or run the image
make install # create .venv and install the package plus dev dependencies
make test # run the test suite
make run # start the server on stdioOr without make:
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/dockstore-mcp --helpThe server supports the two standard MCP transports.
stdio — the client launches the server as a subprocess. This is how desktop MCP clients normally work:
dockstore-mcp --transport stdioHTTP — the server runs as a long-lived service, which is how it is deployed:
dockstore-mcp --transport http --host 0.0.0.0 --port 8000Over HTTP it serves two paths:
| Path | Purpose |
|---|---|
/mcp |
The MCP endpoint (stateless streamable HTTP) |
/health |
Liveness probe, returns {"status": "ok", ...} |
docker build -t dockstore/dockstore-mcp:local .
docker run --rm -p 8000:8000 \
-e DOCKSTORE_MCP_DOCKSTORE_URL=https://qa.dockstore.org \
dockstore/dockstore-mcp:localThe image defaults to the HTTP transport on port 8000 and runs as a non-root user. A
docker-compose.yml is included for local runs against a Dockstore webservice on your
machine:
DOCKSTORE_URL=http://host.docker.internal:8080 docker compose up --buildTo use a local checkout from a client that launches servers itself, point it at the
entry point. For example, in Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"dockstore": {
"command": "/path/to/mcp/.venv/bin/dockstore-mcp",
"env": { "DOCKSTORE_MCP_DOCKSTORE_URL": "https://dockstore.org" }
}
}
}To connect to a running HTTP deployment instead, point the client at
https://<host>/mcp. In Claude Code that is:
claude mcp add --transport http dockstore https://<host>/mcpEvery option can be set with a DOCKSTORE_MCP_-prefixed environment variable, in a
.env file (see .env.example), or with a command line flag. Flags win
over the environment.
| Variable | Flag | Default | Meaning |
|---|---|---|---|
DOCKSTORE_MCP_TRANSPORT |
--transport |
stdio |
stdio or http |
DOCKSTORE_MCP_HOST |
--host |
127.0.0.1 |
Interface to bind, HTTP only |
DOCKSTORE_MCP_PORT |
--port |
8000 |
Port to bind, HTTP only |
DOCKSTORE_MCP_PATH |
--path |
/mcp |
Path the MCP endpoint is served from |
DOCKSTORE_MCP_LOG_LEVEL |
--log-level |
INFO |
Logging verbosity |
DOCKSTORE_MCP_DOCKSTORE_URL |
--dockstore-url |
https://dockstore.org |
Dockstore instance whose APIs are exposed |
DOCKSTORE_MCP_GIT_REF |
package version | Version in the dockstore-mcp/<ref> User-Agent |
The container image overrides the first four so that it listens on 0.0.0.0:8000 out of
the box, and sets DOCKSTORE_MCP_GIT_REF from its GIT_REF build argument, which the
release workflow and make docker-build fill in with the git tag or ref being built. It
also sets a few FASTMCP_* variables so that a deployed server logs plainly and does not
check PyPI for updates on startup; see the FastMCP settings for
the full list.
| Tool | Implemented | Description |
|---|---|---|
get_trs_info |
✅ | Reports instance and version; unless local_only, also TRS info and tool classes. |
list_tools |
✅ | Lists one page of TRS tools, optionally filtered by name, class, language, etc. |
get_tool |
✅ | Retrieves one TRS tool by id, with a page of its versions in full or summarized. |
get_tool_version |
✅ | Retrieves one version of a TRS tool: authors, images, languages, optionally files. |
get_tool_descriptor_by_path |
✅ | Fetches a version's primary descriptor, or any file get_tool_version lists, by path. |
search_entries |
✅ | Searches entries by keyword and facet, the equivalent of the site's Search page. |
get_entry |
✅ | Retrieves one entry, with its versions and description limited by default. |
get_version |
✅ | Retrieves one version of an entry, with its file paths limited by default. |
get_file |
✅ | Retrieves one file belonging to a version, with its content limited by default. |
The TRS tools, from get_trs_info to get_tool_descriptor_by_path, call Dockstore's GA4GH
TRS V2 API directly. They form a chain: list_tools yields tool ids, a
tool yields version names, and get_tool_version with files yields the paths that
get_tool_descriptor_by_path takes. Pass summary to list_tools to get
each tool's id, languages, and version names without its full README and version details.
The other four are a chain too: search_entries yields entry identifiers, an entry yields
its versions, and a version yields file paths. get_entry returns at most
version_limit versions (20 by default), in the order Dockstore ranks them (the default
version first), and at most description_limit characters of the description (5,000 by
default); setting either limit to null returns them in full. get_version returns at
most file_limit file paths (100 by default), the primary descriptor first; null returns
them all. get_file returns at most content_limit characters of the file (50,000 by
default); null returns the whole file.
search_entries sends an Elasticsearch query to Dockstore's TRS extension,
POST /api/ga4gh/v2/extended/tools/entry/_search, which searches the same index as the
site's Search page. Keywords are ranked with the Search page's weights. The entry
type, descriptor language, author, and EDAM facets (subject area, operation, and
input and output data and formats) are filters. Keywords, author, and EDAM facets are
written in Lucene query syntax and sent as query_string queries. Results can be sorted by relevance
(with keywords, the square of the Elasticsearch score times the natural log of 1.05 plus the entry's
indexed relevance),
name, stars, last update, or execution count (the Search page's "Most Used"), and a call returns up to 200 of them (20 by default)
along with the total number that matched. Each result carries the entry's categories and
EDAM facets as the index files them, so they cost no extra request. Services are not indexed, so they cannot be
searched for.
Entries and versions are identified by their GA4GH TRS identifiers, which every tool
reports as id. An entry's is its path behind a prefix naming its kind
(#workflow/github.com/org/repo/name, #notebook/…, #service/…), or its bare path for a
tool or an apptool (quay.io/org/repo). A version's is its entry's TRS identifier and its
name joined by a colon (#workflow/github.com/org/repo/name:v1.0).
get_entry takes an entry's TRS identifier and reads the entry from the webservice by
its path. The prefix says which kind of entry the workflow endpoint should look for; an
identifier with no prefix is looked for among tools first and apptools second, since the
two share that form. It then fetches the categories the entry is filed under, from which
it derives the entry's EDAM facets.
get_version takes a version's TRS identifier and asks the webservice to map it to
Dockstore's own entry and version ids (GET /api/entries/mapTrsVersionId), which finds
only published entries and versions that are not hidden. It then reads the version by
those ids — from /api/containers/published/{id}/tags/{tagId} for a tool, falling back
to /api/workflows/published/{id}/workflowVersions/{versionId} for everything else.
Alongside that, it asks TRS (/api/ga4gh/trs/v2/tools/{id}/versions/{version_id}) which
descriptor types the version has, and lists the files for each type from
.../{type}/files. File paths are the relative paths TRS reports, merged across types,
and the language is that of the first type whose listing includes a primary descriptor.
get_file takes a version's TRS identifier and a path from get_version. It lists the
version's files from TRS the same way, finds the first descriptor type whose listing has
the path, which says what kind of file it is, and then reads the file from
.../{type}/descriptor/{relative_path}, with the path percent-encoded as one segment.
That endpoint serves any of a version's files, not only descriptors.
src/dockstore_mcp/
├── __main__.py command line entry point (`dockstore-mcp`)
├── api.py HTTP client for the Dockstore webservice
├── casing.py camelCase JSON -> snake_case models, for API-backed tools
├── config.py settings, read from the environment
├── models.py entry, version, and file types shared by the tools
├── server.py server construction, /health route
└── tools/
├── __init__.py registers every tool group
├── entries.py get_entry, get_version, get_file
├── search.py search_entries
└── trs.py get_trs_info and the other GA4GH TRS tools
tests/ pytest suite, using FastMCP's in-memory client
Dockerfile two-stage build of the deployable image
Add a module under src/dockstore_mcp/tools/ that exposes
register(mcp: FastMCP, settings: Settings, api: DockstoreApi) -> None, and call it
from register_all in tools/__init__.py. Group related tools in one module. The
api argument is the shared HTTP client; reach Dockstore through it rather than
opening a connection of your own, so that calls reuse the connection pool and the
server can close it on shutdown.
Keep tool docstrings written for the model that will read them: say what the tool
returns and when to reach for it. Note that FastMCP can also generate tools directly
from an OpenAPI specification (FastMCP.from_openapi), which may be the right way to
cover large parts of the Dockstore API.
This server is a client of the
webservice. The tools call its REST and TRS
endpoints and read their JSON, and search_entries sends queries to the Elasticsearch
index that the webservice builds. search_entries also reproduces the Search page of
UI2: its field boosts and path matching
follow UI2's QueryBuilderService, and its relevance scoring is meant to match UI2's, so
results come back in the order the site shows them.
When either repository changes something these tools depend on, update this one to match:
- Webservice payloads or endpoints: update the affected tool and its model in
models.py, and refresh the trimmed payloads intests/fake_dockstore.py. The daily smoke tests (make smoke) run against dockstore.org and catch drift that the fake can't. - Index fields or categories: update the field lists at the top of
tools/search.py. Thesearchmodule's docstring explains how to add a new kind of category. - UI2's search boosts or scoring: carry the change over to
_QUERY_FIELDS,_PATH_BOOST, or_RELEVANCE_SCRIPTintools/search.py.
make check # lint, type check, and test
make format # apply ruff formatting and safe fixesTests use FastMCP's in-memory client, so they exercise real tool dispatch without
starting a server or opening a socket. Tools that call Dockstore are pointed at
tests/fake_dockstore.py, which answers from trimmed copies of real payloads and
records what it was asked, so a test can show that a field nobody wanted cost nothing.
Dockstore uses git-secrets to help make sure that keys and private data stay out of the source tree. For information on installing it on your platform check https://github.com/awslabs/git-secrets#id6.
If you're on mac with homebrew use brew install git-secrets.
With git-secrets on your path, make install (or make git-hooks) registers the AWS
patterns and points core.hookspath at git-hooks/, so the scan runs on
every commit. CI runs git secrets --scan over the whole repository as well. False
positives can be listed in .gitallowed.
Apache License 2.0. See LICENSE.