This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
An MCP (Model Context Protocol) server, built on FastMCP 4, that exposes Dockstore (a registry of bioinformatics tools/workflows in CWL, WDL, Nextflow, Galaxy) to AI assistants. It is a standalone process deployed alongside the Dockstore webservice, talking to Dockstore's GA4GH Tool Registry Service (TRS) API and its own proprietary API.
Status: early. Every tool works: the GA4GH TRS tools in trs.py and the four Dockstore tools
(search_entries, get_entry, get_version, get_file) in search.py and entries.py.
make install # create .venv and install package + dev deps (also registers git-secrets hooks)
make test # run pytest
make lint # ruff check + ruff format --check
make format # ruff check --fix + ruff format
make typecheck # mypy (strict mode)
make check # everything CI runs: lint, typecheck, test
make smoke # opt-in, read-only tests against the live dockstore.org; not run by make test, make check, or PR CI
make run # run the server over stdio
make run-http # run the server over HTTP on port 8000Tests in tests/live are marked live and deselected by default; the daily smoke.yml workflow runs them.
Run a single test with pytest directly (no Makefile target for this):
.venv/bin/pytest tests/test_tools.py::test_search_takes_every_facet
.venv/bin/pytest -k "trs_info"Tests use FastMCP's in-memory Client/FastMCP pairing (see tests/conftest.py), so
they exercise real tool dispatch without a socket or subprocess. The Dockstore tools' DockstoreApi
is backed by tests/fake_dockstore.py's FakeDockstore, which serves trimmed copies of real
dockstore.org payloads.
When creating a PR, always create it in draft mode. A human developer must be the one to mark it ready for review/move it out of draft state — Claude Code should not do this itself.
Always check with the user before pushing changes to GitHub, even to a branch/PR already being worked on in the conversation — a push can kick off a long CI build or interrupt one that's already running.
When a GitHub MCP server or gh is available, diff the current work against develop (or whatever branch the
PR targets) and try to minimize stylistic or otherwise-minor changes that inflate the diff and make it harder
to review, unless those changes fix something a Codacy finding or other code-quality check actually flagged.
Keep the freeform "Description" and "Review Instructions" sections brief — one paragraph each, or two for a genuinely complicated fix, not multi-paragraph writeups. (The "Security and Privacy" checklist section is separate and must still be copied verbatim per the section below.)
.github/PULL_REQUEST_TEMPLATE.md's "Security and Privacy" checklist (and its other checkboxes) must be copied
into PR descriptions verbatim — never reword, reformat, condense, or append explanatory text to a checklist item.
Only toggle [ ] to [x] for an item, and only after actually confirming that action was taken/verified for this
PR; leave it unchecked otherwise.
GitHub Actions (.github/workflows/ci.yml) runs on every PR, and on every push to develop or a tag via
deploy_tagged.yml, which calls it and publishes the quay.io image only once it passes: a lint/typecheck/test
matrix across Python 3.11–3.14, a git secrets --scan over the whole repository, and a Docker image build with a
smoke test against /health. make check runs the equivalent lint/typecheck/test steps locally before pushing.
When adding comments to JIRA tickets, clearly indicate that the comment was written by Claude (e.g. lead with a line like "This comment was generated by Claude (Claude Code).").
The package version lives in the version field of pyproject.toml's [project] table (currently 0.1.0) and
generally follows semantic versioning. dockstore_mcp.__version__ (src/dockstore_mcp/__init__.py) reads it
back from the installed package's metadata at runtime (importlib.metadata.version), falling back to
"0.0.0+unknown" if the package was never installed (e.g. running directly from a source checkout).
src/dockstore_mcp/server.py—create_server(settings, api)builds theFastMCPinstance and the sharedDockstoreApiclient (closed on shutdown), adds the/healthroute, and callsregister_all. There's also a module-levelmcpinstance forfastmcp run dockstore_mcp.server:mcp.src/dockstore_mcp/config.py—Settings(pydantic-settings), env-prefixedDOCKSTORE_MCP_*, also readable from.env. CLI flags (parsed in__main__.py) override the environment. Derivestrs_urlandapi_urlfromdockstore_url.src/dockstore_mcp/api.py—DockstoreApi, the one HTTP client per server that the Dockstore tools reach the webservice through, so calls reuse its connection pool. It speaks JSON only; turning payloads into models is each tool's job.src/dockstore_mcp/casing.py—normalize_keys/camel_to_snake, for turning Dockstore's camelCase JSON into the snake_case models.src/dockstore_mcp/models.py— Pydantic models/enums shared by the tools (Entry,Version,Fileand their*Summaryforms, plus the TRS models). Every field onEntry/Version/Fileis optional, since Dockstore doesn't fill in every one for every kind of entry.src/dockstore_mcp/tools/— one module per cohesive tool group, each exposing aregisterfunction. The Dockstore tool modules takeregister(mcp: FastMCP, settings: Settings, api: DockstoreApi) -> None;trs.pymakes its own requests and takes only(mcp, settings).tools/__init__.py'sregister_all(mcp, settings, api)calls each in turn; new tool modules must be added there.trs.py— the GA4GH TRS tools, implemented.get_trs_info(local_only=True)doubles as a smoke test that never contacts Dockstore.search.py—search_entries, the Dockstore Search page equivalent.entries.py—get_entry→get_version→get_file, a lookup chain: an entry'sversionsgive the idsget_versiontakes, whosefilesgive the pathsget_filetakes. Each limits what it returns by default (version_limit,description_limit,file_limit,content_limit); null returns everything.
src/dockstore_mcp/__main__.py— CLI entry point (dockstore-mcp); layers argparse flags over env-derivedSettings, then runs the server over stdio or HTTP.
Add a module under src/dockstore_mcp/tools/ exposing register(mcp, settings, api),
and call it from register_all in tools/__init__.py. Group related tools in one
module, and reach Dockstore through the shared api client rather than opening a
connection of your own. Write tool docstrings for the model that will read them (what it returns,
when to reach for it), not for a human API reference. FastMCP can also generate
tools directly from an OpenAPI spec (FastMCP.from_openapi), which may be the right
way to cover large parts of the Dockstore API instead of hand-writing tools.
- stdio (default): client launches the server as a subprocess.
- http: long-lived service, serves
/mcp(streamable HTTP) and/health(liveness probe). This is how it's deployed (seeDockerfile,docker-compose.yml).
- Line length 120, target Python 3.11, ruff rule set
E, F, W, I, N, UP, B, C4, SIM, RUF. - mypy runs in
strictmode oversrcandtests. - Every source file carries the Apache-2.0 header block (see any existing file for the exact text) — keep it when adding new modules.
git-secretsscans for AWS credential patterns on commit (hooks ingit-hooks/, registered bymake install/make git-hooks) and again in CI over the whole repo. False positives go in.gitallowed.