Qiskit MCP Server Development Guide
Instructions
When helping with MCP server development in this repository:
- •Read existing code first - Check
server.pyand core modules before adding new functionality - •Follow established patterns - Match the style of existing tools and tests
- •Include license headers - All new files need the Apache 2.0 header
- •Keep tools async - MCP tools use
async def, delegate to core module functions - •Mock external services - Never call real IBM Quantum APIs in tests
- •Update GitHub CI/CD - New servers require workflow updates (see below)
License Header (Required for all new files)
python
# This code is part of Qiskit. # # (C) Copyright IBM 2025. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals.
Adding a Tool
Tools go in server.py and delegate to async functions in the core module:
python
# Example: from qiskit_<name>_mcp_server.<core> import my_function
from qiskit_ibm_runtime_mcp_server.ibm_runtime import my_function
@mcp.tool()
async def my_tool_name(param: str, optional_param: int = 10) -> dict[str, Any]:
"""Clear description of what this tool does.
Args:
param: Description of the parameter
optional_param: Description with default behavior
Returns:
Description of the return structure
"""
return await my_function(param, optional_param)
Key patterns:
- •Tool function name ends with
_toolsuffix - •Docstring describes the tool for AI assistants
- •Return type is
dict[str, Any] - •Delegate to core module, don't implement logic here
Error Handling Pattern
All tools return consistent response format:
python
# Success
return {"status": "success", "data": result, ...}
# Error
return {"status": "error", "message": "Description of what went wrong"}
Adding a Resource
python
@mcp.resource("protocol://path", mime_type="application/json")
async def get_resource_name() -> dict[str, Any]:
"""Description of the resource."""
return await get_data_from_core_module()
Writing Tests
Tests use pytest with mocked services. See tests/conftest.py for fixtures.
python
import pytest
from unittest.mock import Mock, patch
@pytest.mark.asyncio
async def test_my_tool(mock_service):
"""Test description."""
with patch(
"qiskit_<name>_mcp_server.<core>.ExternalService",
return_value=mock_service,
):
result = await my_function()
assert result["status"] == "success"
Common fixture patterns in conftest.py (names vary by server):
- •
mock_*_service- Mocked external service (e.g.,mock_runtime_service,mock_http_client) - •
mock_env_vars- Sets environment variables likeQISKIT_IBM_TOKEN - •
reset_*- Resets global state between tests (oftenautouse=True)
Code Quality
Run from the server directory:
bash
uv run ruff format src/ tests/ # Format uv run ruff check src/ tests/ # Lint uv run mypy src/ # Type check uv run pytest # Test
Adding a New Server
When creating a new server qiskit-<name>-mcp-server:
- •Create server directory with standard structure (see below)
- •Add to workspace in root
pyproject.toml:toml[tool.uv.workspace] members = [..., "qiskit-<name>-mcp-server"]
- •Create examples/ with LangChain agent demos:
- •
README.md- Setup instructions and usage - •
langchain_agent.ipynb- Interactive Jupyter tutorial - •
langchain_agent.py- CLI agent supporting multiple LLM providers
- •
- •Update GitHub CI -
.github/workflows/test.yml:- •Add to
lintjob (install, ruff, mypy, bandit steps) - •Add new
test-<name>job
- •Add to
- •Update GitHub CD -
.github/workflows/publish-pypi.yml:- •Add to
workflow_dispatchoptions - •Add
publish-<name>job - •Add to
publish-meta-packageneeds array
- •Add to
- •Update MCP Registry CD -
.github/workflows/publish-mcp-registry.yml:- •Add to
workflow_dispatchoptions - •Add
publish-<name>-mcp-registryjob
- •Add to
- •Create server.json for MCP Registry (see template in AGENTS.md)
- •Update documentation - README.md, AGENTS.md
Server Structure
code
qiskit-<name>-mcp-server/ ├── src/qiskit_<name>_mcp_server/ │ ├── __init__.py # Package init, version │ ├── server.py # FastMCP server, @mcp.tool() and @mcp.resource() │ └── <core>.py # Async business logic ├── tests/ │ ├── conftest.py # Fixtures for mocking │ └── test_*.py # Test files ├── examples/ │ ├── README.md # Example documentation │ ├── langchain_agent.ipynb # Jupyter notebook tutorial │ └── langchain_agent.py # CLI agent example ├── pyproject.toml ├── server.json # MCP Registry metadata ├── pytest.ini # (optional) pytest configuration ├── .env.example # (optional) Environment variable template ├── LICENSE # Apache 2.0 license (copy from root) ├── README.md └── run_tests.sh
Quick Reference
| Task | File | Pattern |
|---|---|---|
| Add tool | server.py | @mcp.tool() decorator |
| Add resource | server.py | @mcp.resource("uri") decorator |
| Core logic | <core>.py | Async functions, return dicts |
| Test fixtures | tests/conftest.py | @pytest.fixture |
| Unit tests | tests/test_*.py | @pytest.mark.asyncio |
| New server CI | .github/workflows/test.yml | Add lint steps + test job |
| New server CD (PyPI) | .github/workflows/publish-pypi.yml | Add publish job |
| New server CD (MCP) | .github/workflows/publish-mcp-registry.yml | Add publish job |
| MCP Registry metadata | server.json | JSON with name, version, packages |
For comprehensive documentation, read AGENTS.md in the repository root.