Galaxy MCP Server - Python Implementation
This is the Python implementation of the Galaxy MCP server, providing a Model Context Protocol server for interacting with Galaxy instances.
Features
- Complete Galaxy API integration through BioBlend
- Optional OAuth login flow for HTTP deployments
- Intergalactic Workflow Commission (IWC) integration
- FastMCP 3 server with remote deployment support
- Session-aware tool visibility: every tool is tagged, and a middleware can hide tags a session cannot use (see Filtering the catalog by tag)
- Type-annotated Python codebase
Requirements
- Python 3.10+
- FastMCP 3.1+
Installation
From PyPI (Recommended)
# Install from PyPI
pip install galaxy-mcp
# Or using uv (recommended)
uvx galaxy-mcp
From Source
# Clone the repository
git clone https://github.com/galaxyproject/galaxy-mcp.git
cd galaxy-mcp/mcp-server-galaxy-py
# Install with uv (recommended)
uv sync --all-extras
Configuration
At minimum the server needs to know which Galaxy instance to target:
export GALAXY_URL="https://usegalaxy.org.au/"
How you authenticate depends on your transport:
-
Stdio / long-lived sessions – provide an API key:
export GALAXY_API_KEY="your-api-key"
-
HTTP / OAuth – configure the public URL that users reach and a signing secret for session tokens. The server mints short-lived Galaxy API keys on behalf of each user.
GALAXY_MCP_SESSION_SECRETis required wheneverGALAXY_MCP_PUBLIC_URLis set; the server refuses to start without it. Generate it once withopenssl rand -hex 32and store it — the same value must be used on every restart and every replica, since it is the key that encrypts session tokens. A fresh secret invalidates every token issued under the old one.export GALAXY_MCP_PUBLIC_URL="https://mcp.example.com" export GALAXY_MCP_SESSION_SECRET="<your stored secret>"
Optionally set
GALAXY_MCP_CLIENT_REGISTRYto control where OAuth client registrations are stored.For non-OAuth HTTP clients,
connect(url=..., api_key=...)stores Galaxy credentials per MCP session rather than globally, so sessions don't see each other's credentials. That is not authentication, though -- without OAuth, anything that can reach the listener can call every tool. See Serving over HTTP before exposing the server to anyone else.
You can also steer the transport with GALAXY_MCP_TRANSPORT (stdio, streamable-http, or sse).
All variables can be placed in a .env file for convenience.
Usage
Quick Start with uvx
# Local stdio transport (no network listener)
uvx galaxy-mcp
# Remote/browser clients with HTTP + OAuth
export GALAXY_URL="https://usegalaxy.org.au/"
export GALAXY_MCP_PUBLIC_URL="https://mcp.example.com"
export GALAXY_MCP_SESSION_SECRET="$(openssl rand -hex 32)"
uvx galaxy-mcp --transport streamable-http --host 0.0.0.0 --port 8000
Installed CLI
pip install galaxy-mcp
galaxy-mcp --transport streamable-http --port 8000 # listens on 127.0.0.1
If --transport is omitted the server defaults to stdio and reads/writes MCP messages via stdin/stdout.
Working from a checkout
uv sync
uv run galaxy-mcp --transport streamable-http --port 8000
Serving over HTTP
Galaxy MCP is first of all a local server for the person running it. Over stdio the caller is whoever launched the process; over HTTP the caller is anything that can reach the socket, so the HTTP transports come with a few guards:
- Loopback by default. The listener binds to
127.0.0.1unless you pass--hostor setGALAXY_MCP_HOST. - No accidental open servers. Binding to a non-loopback address without OAuth is refused.
If something else controls access to the listener (a container's published port, an
authenticating reverse proxy), say so with
--allow-unauthenticatedorGALAXY_MCP_ALLOW_UNAUTHENTICATED=1. Remember that anyGALAXY_API_KEYin the environment is then usable by every caller. - Host and Origin checks. Without OAuth, requests must carry a loopback
Hostheader, and browser requests are only answered for pages served from the same machine. This keeps web pages you happen to have open from driving a local server. Add names withGALAXY_MCP_ALLOWED_HOSTSandGALAXY_MCP_ALLOWED_ORIGINS(comma-separated;*disables the check). With OAuth the bearer token is the gate and any origin is answered unlessGALAXY_MCP_ALLOWED_ORIGINSis set. - No server-filesystem access.
upload_fileanddownload_dataset(file_path=...)take paths on the machine running the server, which is only what you want when that machine is yours. They are disabled over HTTP; useupload_file_from_urland in-memory downloads instead. A trusted single-user deployment can turn them back on withGALAXY_MCP_ALLOW_LOCAL_FILES=1. Never set that on a server other people can reach -- it lets every caller read and write whatever the server process can. - Environment credentials stay put.
connect(url=...)only uses the environment'sGALAXY_API_KEYwhen the URL is the configuredGALAXY_URL; any other server needs an explicitapi_key. With OAuth enabled the environment's credentials are never lent to a request.
None of this can see a reverse proxy: a loopback listener behind nginx looks local from here, so a proxied deployment needs OAuth (or authentication at the proxy) just as a directly exposed one does.
In a container the listener has to bind 0.0.0.0 for the published port to work, so running the
image over HTTP without OAuth needs the explicit opt-out, and should only be published to
loopback:
docker run --rm -it -p 127.0.0.1:8000:8000 \
-e GALAXY_URL="https://usegalaxy.org/" \
-e GALAXY_API_KEY="your-api-key" \
-e GALAXY_MCP_TRANSPORT="streamable-http" \
-e GALAXY_MCP_ALLOW_UNAUTHENTICATED=1 \
galaxyproject/galaxy-mcp
See USAGE_EXAMPLES.md for detailed tool usage patterns.
Tool discovery mode (experimental)
Galaxy MCP registers one @mcp.tool per operation. The client fetches that catalog once per session, but it carries every one of those definitions into the model's context on each turn -- expensive for agents that only ever use a handful. --discovery-mode code (also honored via GALAXY_MCP_DISCOVERY_MODE=code) collapses the catalog into three meta-tools:
search-- BM25 search over tool names and descriptionsget_schema-- fetch the full schema for specific toolsrun_galaxy_tool-- execute any tool by name
pip install 'galaxy-mcp[code-mode]' # pulls in pydantic-monty sandbox
galaxy-mcp --discovery-mode code
The code-mode extra installs pydantic-monty, the sandboxed Python interpreter that backs run_galaxy_tool. Without it the server still starts in full mode but raises a clear error if --discovery-mode code is requested.
Default is full, which keeps the existing catalog unchanged. CodeMode is useful when you want to keep the agent's context lean and are willing to trade a few extra turns (search -> schema -> execute) per tool call. It's built on FastMCP's experimental CodeMode transform, so the API may shift before it stabilizes.
The server also ships agent-facing usage guidance via the MCP instructions field (returned during the initial handshake). It explains the typical workflow, the difference between MCP tools and Galaxy tools (e.g. FastQC isn't an MCP tool -- find it via search_tools_by_name), and -- when code mode is active -- how to use run_galaxy_tool and call_tool. Agents that respect the instructions field will read this without you having to prompt them.
Filtering the catalog by tag
Every tool is registered with three tags: a family (connection, user, histories, datasets, jobs, tools, workflows, iwc, pages), an access level (read or write), and a tier (core, extended, or niche). ToolVisibilityMiddleware in middleware.py filters both tools/list and direct calls against those tags, so a hidden tool cannot be invoked by name either.
Two environment variables drive it, each a comma-separated tag list:
export GALAXY_MCP_INCLUDE_TAGS="core,histories" # show only tools carrying one of these
export GALAXY_MCP_EXCLUDE_TAGS="write" # then drop any tool carrying one of these
The middleware also hides the admin and user_tools tags from callers that cannot use them, probing the connected Galaxy for is_admin and for enable_unprivileged_tools. Mind the cache scope: the middleware is constructed once when the server module loads, so those answers are cached for the life of the process, not per session -- the admin answer keyed by URL and API key, the user_tools answer by URL alone, and so shared across every key on that server. No tool currently carries either tag, so this half is inert until tools are labelled for it.
The code-mode meta-tools are added by FastMCP's transform and carry no tags at all, so tag filtering and --discovery-mode code do not combine: with GALAXY_MCP_INCLUDE_TAGS set, all three meta-tools are hidden.
Available MCP Tools
Every tool returns a GalaxyResult: data, success and message always, count on
most list operations, and pagination on the ones that actually page. Names and behavior
are the contract; arguments live in each tool's own description, which is what the MCP
client sees.
Version requirements
A few tools need a Galaxy newer than the oldest one this server speaks to. Each declares its own minimum, says so in its description, and is refused before anything is sent -- so a write can never half-succeed against a server that was never going to accept it:
list_pages needs Galaxy 26.1 or newer; this server reports 26.0. Nothing was sent to Galaxy.
The version comes from /api/version, asked once per server. A server that will not answer
leaves its version unknown, and an unknown version refuses nothing: the tool is attempted and
stands or falls on its own. get_server_info reports what the connected server cannot run in
unsupported_tools, and says in version_known whether an empty list means anything.
A tool is gated as a whole, and some of them are gated for part of what they do: the check runs
before the arguments are looked at, and answering the wrong thing quietly is worse than refusing.
list_pages needs 26.1 because 26.0 has no history filter and no history_id on a page, so it
would answer a request for one history's notebooks with every page the user can see and no way to
tell which is which; create_page because 26.0 cannot attach a page to a history and answers with
a summary that has no editable content in it. get_page is not gated -- 26.0 answers it with the
editable markdown, which is what it is for.
Connection and account
connect: Point the session at a Galaxy instance and validate the credentialsget_server_info: Version, URL, and public configuration of the connected Galaxy, plus the tools it is too old to runget_user: The authenticated user
Histories
get_histories: List histories, optionally filtered by namelist_history_ids: A compact id-and-name list, for when you just need an id; 100 per pageget_history_details: One history's metadata and item counts, without its contentsget_history_contents: The datasets and collections inside a historycreate_history: Create a historyupdate_history: Rename, annotate, tag, delete, or publish a history
Datasets, collections, and jobs
get_dataset_details: Dataset metadata, optionally with a short content previewget_collection_details: A dataset collection and its elementsget_job_details: The job that produced a dataset, with its state and parametersupload_file: Upload a local file into a historyupload_file_from_url: Have Galaxy fetch a file from a URL into a historydownload_dataset: Download a dataset to afile_pathon disk
Galaxy tools
search_tools_by_name: Substring search over tool name, id, and description; 25 per pagesearch_tools_by_keywords: Match keywords against tool names, descriptions, and the file extensions a tool accepts as input; 50 per pageget_tool_details: A tool's metadata, optionally including its full input schemaget_tool_panel: Browse the tool panel one level at a time -- the sections and their sizes, or one section's tools withsection_id. The whole panel is megabytes on a production server, so it is never returned whole; 100 entries per pageget_tool_citations: How to cite a toolget_tool_run_examples: The tool's own XML test definitions, as written -- good for seeing how inputs are shaped, but some expect failure and their input files are test fixturesget_tool_input_template: A ready-to-fillinputsskeleton plus a compact schema; call this beforerun_toolwhen the shape is unclearrun_tool: Run a Galaxy tool in a historyrecommend_biocontainer: Resolve a verifiedquay.io/biocontainersimage for a set of conda packages. Registered only when thecontainer-recommendextra is installed
User-defined tools
User-defined tools are unprivileged, containerized tools a user creates themselves. They
are addressed by UUID rather than by tool id, and run_user_tool resolves that UUID before
submitting the run, so they need their own run tool even though the run itself goes through
the same Galaxy tools API as a catalog tool.
list_user_tools: The current user's user-defined tools; 25 per page, because each entry carries the tool's full representationcreate_user_tool: Create one from a tool representationdelete_user_tool: Deactivate one, so it stops loading into the toolboxrun_user_tool: Run one in a history
Workflows and invocations
list_workflows: Stored workflows, optionally filtered by name or published state; 50 per pageget_workflow_details: One workflow's steps and inputs, at a given versionget_workflow_input_template: A ready-to-fill input template plus a run guide; call this beforeinvoke_workflowinvoke_workflow: Run a workflow, validating the inputs against its steps firstget_invocations: Invocations, by invocation, workflow, or historycancel_workflow_invocation: Cancel a running invocation
IWC catalog
The Intergalactic Workflow Commission publishes a curated, versioned workflow catalog. These tools read that public manifest; only the import touches your Galaxy.
get_iwc_workflows: The IWC manifest, 20 workflows per pagesearch_iwc_workflows: Substring search across the catalog; 20 per pagerecommend_iwc_workflows: Rank catalog workflows against a free-text description of what you want to do; the best 5 by default, up to 25get_iwc_workflow_details: Everything about one workflow before you commit to importing itimport_workflow_from_iwc: Import a catalog workflow into the connected Galaxy
Pages
Pages are Galaxy-flavored markdown documents. One attached to a history is a Notebook; a
standalone one is a Report. Embedded datasets are referenced by encoded id. Pages written as
HTML in the Galaxy UI are the exception: their body is in content, which get_page only
returns with include_rendered, and they stay HTML when updated, so don't send markdown to one.
All of these need Galaxy 26.1 or newer except get_page, which 26.0 answers in full.
list_pages: Pages, optionally filtered by history or search termget_page: A page and its latest revision, editable markdown and optionally renderedcreate_page: Create a pageupdate_page: Update a page; a content change records a new revisionlist_page_revisions: A page's revision historyget_page_revision: One revision's editable content, andcontent_editor_sourcesaying whether that came from the server or from the export-expandedcontentstanding in for itrevert_page_revision: Restore an earlier revision as a new one
Testing
The suite is mock-based: it stands up a GalaxyInstance mock and never talks to a real
server, so it runs anywhere in seconds. Test dependencies come from the dev extra, which
uv sync --all-extras already installs.
# Run everything
uv run pytest
# One file
uv run pytest tests/test_history_operations.py
# Coverage has to be asked for -- see the config note below
uv run pytest --cov=galaxy_mcp --cov-report=term-missing
# Type checking is a separate gate -- neither pytest nor pre-commit runs it
uv run mypy src/galaxy_mcp
Two pytest configs exist and pytest.ini wins, which pytest says out loud on every run
(ignoring pytest config in pyproject.toml). The addopts in pyproject.toml therefore
never apply, and the ones in pytest.ini sit under a [tool:pytest] header that
pytest.ini does not read. A bare uv run pytest gets no -v and no coverage; pass the
flags yourself, as CI does.
How the tests are put together
conftest.pybuilds the sharedmock_galaxy_instancefixture and resets the server's module-level connection and cache state between tests.test_helpers.pyunwraps the registered tools back to plain functions, so most tests call a tool directly and assert on theGalaxyResultit returns.test_job_operations.pymocks at the HTTP layer withresponsesinstead, where the code under test goes around BioBlend.tests/mcp_session.pydrives the tools through an in-memory FastMCP client. Only the live suite uses it today. That path matters because a tool called without an MCP request context has no session id, and the session-scoped connection store then has nowhere to put the client -- which is what made the live suite look broken for months.
The live suite
tests/test_real_integration.py runs against a real Galaxy and is skipped by default.
It needs GALAXY_TEST_API_KEY set to a valid key and GALAXY_TEST_URL (default
http://localhost:8080) answering on /api/version; if either is missing the whole module
is skipped. A green pytest run therefore says nothing about these tests -- check the skip
count before reading it as coverage.
export GALAXY_TEST_URL="http://localhost:8080"
export GALAXY_TEST_API_KEY="a-real-key"
uv run pytest tests/test_real_integration.py
Development
Code Style Guidelines
- Use Python 3.10+ features
- Employ type hints where appropriate
- Follow PEP 8 style guidelines
- Use ruff for code formatting and linting
- All code should pass type checking with mypy
Development Setup
# Install every dependency, including the dev and optional extras
make install
# Set up pre-commit hooks (required for contributing)
uv run pre-commit install
Pre-commit hooks will automatically format your code and run linting checks when you commit. All contributors should install these hooks to maintain consistent code quality.
Development Commands
We use a Makefile for consistent development commands:
# Show all available commands
make help
# Install dependencies
make install # Install all dependencies
# Code quality
make lint # Run the pre-commit hooks (formatting and lint -- no type checking)
# Testing
make test # Type-check with mypy, then run tests with coverage
# Building
make clean # Clean build artifacts
make build # Build distribution packages
# Running
make run # Run the MCP server
make dev # Run the FastMCP dev inspector
Using uv directly
All commands can also be run directly with uv:
# Install dependencies
uv sync --all-extras
# Format and lint code
uv run pre-commit run --all-files
# Type check
uv run mypy src/galaxy_mcp
# Run tests with coverage
uv run pytest --cov=galaxy_mcp --cov-report=html
# Update dependencies
uv lock --upgrade
Cross-version Testing
Test across multiple Python versions using tox:
# Test on all supported Python versions
uv run tox
# Test on specific version
uv run tox -e py312
# Run only linting
uv run tox -e lint
# Run type checking
uv run tox -e type
Pre-commit Hooks
The project uses pre-commit hooks for automatic code quality checks:
# Install pre-commit hooks (one-time setup)
uv run pre-commit install
# Run pre-commit manually on all files
uv run pre-commit run --all-files
# Skip pre-commit for a single commit (not recommended)
git commit --no-verify
Pre-commit runs automatically on git commit and includes:
- Code formatting with ruff
- Linting with ruff
- Trailing whitespace removal
- File cleanup (EOF, YAML/JSON/TOML validation)
- Large file detection
- Merge conflict detection
It does not type-check. CI runs mypy src/galaxy_mcp as its own job, so run that
yourself before pushing.
License
Metadata
Release files for galaxy-mcp 1.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| galaxy_mcp-1.11.0.tar.gz | 269.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| galaxy_mcp-1.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 378.3 kB
Release files / galaxy_mcp-1.11.0.tar.gz
| Download URL | galaxy_mcp-1.11.0.tar.gz |
|---|---|
| Size | 269.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5abe3698bd688d53bd85ed9eb7295ba5af4bc638e74ebad4c2322e9eae096164
|
|
BLAKE2b-256 checksum How to use checksums |
9f89257caefdd2f7c6e5755377d380bf62b01a157c6f6fa56cd1ac429403b675
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|
Release files / galaxy_mcp-1.11.0-py3-none-any.whl
| Download URL | galaxy_mcp-1.11.0-py3-none-any.whl |
|---|---|
| Size | 108.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
65b57b5f7c3129fed062fa168a1820e6716a6ca546b4a70f69e460a29ae1e6a1
|
|
BLAKE2b-256 checksum How to use checksums |
90213ef06f180ac9a69a412e13faa6ff5454fc9c85d508ef80c3fdf46cb7016b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.14
|