Skip to content

Latest commit

 

History

History
140 lines (110 loc) · 7.87 KB

CLAUDE.md

File metadata and controls

140 lines (110 loc) · 7.87 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

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.

Commands

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 8000

Tests 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.

Pull requests

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.

CI

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.

JIRA

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).").

Versioning

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).

Architecture

  • src/dockstore_mcp/server.py — create_server(settings, api) builds the FastMCP instance and the shared DockstoreApi client (closed on shutdown), adds the /health route, and calls register_all. There's also a module-level mcp instance for fastmcp run dockstore_mcp.server:mcp.
  • src/dockstore_mcp/config.py — Settings (pydantic-settings), env-prefixed DOCKSTORE_MCP_*, also readable from .env. CLI flags (parsed in __main__.py) override the environment. Derives trs_url and api_url from dockstore_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, File and their *Summary forms, plus the TRS models). Every field on Entry/Version/File is 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 a register function. The Dockstore tool modules take register(mcp: FastMCP, settings: Settings, api: DockstoreApi) -> None; trs.py makes its own requests and takes only (mcp, settings). tools/__init__.py's register_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's versions give the ids get_version takes, whose files give the paths get_file takes. 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-derived Settings, then runs the server over stdio or HTTP.

Adding a tool

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.

Transports

  • 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 (see Dockerfile, docker-compose.yml).

Conventions

  • Line length 120, target Python 3.11, ruff rule set E, F, W, I, N, UP, B, C4, SIM, RUF.
  • mypy runs in strict mode over src and tests.
  • 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-secrets scans for AWS credential patterns on commit (hooks in git-hooks/, registered by make install / make git-hooks) and again in CI over the whole repo. False positives go in .gitallowed.