Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
HARDWARE_PRESETS,
get_coupling_map_presets,
)
from qiskit_gym_mcp_server.models import list_loaded_models
from qiskit_gym_mcp_server.gym_core import get_environment_info
from qiskit_gym_mcp_server.models import get_model_info, list_loaded_models
from qiskit_gym_mcp_server.training import (
get_available_algorithms,
get_available_policies,
get_training_status,
list_training_sessions,
)

Expand Down Expand Up @@ -217,3 +219,72 @@ async def workflows_resource() -> dict[str, Any]:
"Increase num_searches (up to 10000) for better synthesis results",
],
}


##################################################
## MCP Prompts
## - https://modelcontextprotocol.io/docs/concepts/prompts
##################################################


@mcp.prompt()
def train_synthesis_model(env_type: str, num_qubits: str) -> str:
"""Train a reinforcement learning model for quantum circuit synthesis."""
return (
f"Train an RL model for {env_type} synthesis on {num_qubits} qubits: "
f"1) Call create_{env_type}_env_tool with appropriate parameters for "
f"{num_qubits} qubits to create a training environment, "
"2) Call start_training_tool with the returned env_id and algorithm='ppo', "
"3) Call get_training_status_tool with the session_id to monitor progress, "
"4) When training completes, call save_model_tool to persist the trained model."
)


@mcp.prompt()
def synthesize_circuit(circuit_type: str) -> str:
"""Synthesize a quantum circuit using a trained RL model."""
return (
f"Synthesize a {circuit_type} circuit using a trained model: "
"1) Call list_saved_models_tool to see available models, "
f"2) Call load_model_tool with a suitable model_name for {circuit_type} synthesis, "
f"3) Generate a test input using generate_random_{circuit_type}_tool, "
f"4) Call synthesize_{circuit_type}_tool with the model_id and the generated input, "
"5) Call convert_qpy_to_qasm3_tool to view the resulting circuit."
)


@mcp.prompt()
def explore_hardware_topology(backend_preset: str) -> str:
"""Explore hardware topology and extract subtopologies for training."""
return (
f"Explore the '{backend_preset}' hardware topology: "
"1) Read the qiskit-gym://presets/coupling-maps resource to see available presets, "
f"2) Call extract_subtopologies_tool with preset='{backend_preset}' "
"to find connected subtopologies, "
"3) Call list_subtopology_shapes_tool to summarize the shapes found, "
"4) Create environments for each unique subtopology using create_permutation_env_tool."
)


##################################################
## MCP Resource Templates
## - https://modelcontextprotocol.io/docs/concepts/resources#resource-templates
##################################################


@mcp.resource("qiskit-gym://environments/{env_id}", mime_type="application/json")
async def environment_info_resource(env_id: str) -> dict[str, Any]:
"""Get detailed information about a specific gym environment."""
return await get_environment_info(env_id)


@mcp.resource("qiskit-gym://models/{model_name}", mime_type="application/json")
async def model_info_resource(model_name: str) -> dict[str, Any]:
"""Get information about a specific trained model."""
return await get_model_info(model_name=model_name)


@mcp.resource("qiskit-gym://training/{session_id}", mime_type="application/json")
async def training_status_resource(session_id: str) -> dict[str, Any]:
"""Get the status and metrics of a specific training session."""
return await get_training_status(session_id)
76 changes: 76 additions & 0 deletions qiskit-gym-mcp-server/tests/test_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2026.
#
# 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.

"""Tests for MCP server registration and configuration."""

from qiskit_gym_mcp_server.server import mcp


class TestServerRegistration:
"""Test that tools, resources, prompts, and templates are registered."""

def test_server_name(self):
"""Test the server name is correct."""
assert mcp.name == "Qiskit Gym"

def test_resources_registered(self):
"""Test that all expected static resources are registered."""
resource_uris = set(mcp._resource_manager._resources.keys())
expected_resources = {
"qiskit-gym://presets/coupling-maps",
"qiskit-gym://algorithms",
"qiskit-gym://policies",
"qiskit-gym://environments",
"qiskit-gym://training/sessions",
"qiskit-gym://models",
"qiskit-gym://server/config",
"qiskit-gym://workflows",
}
assert expected_resources.issubset(resource_uris), (
f"Missing resources: {expected_resources - resource_uris}"
)

def test_resource_count(self):
"""Test the expected number of static resources."""
assert len(mcp._resource_manager._resources) == 8

def test_prompts_registered(self):
"""Test that all expected prompts are registered."""
prompt_names = set(mcp._prompt_manager._prompts.keys())
expected_prompts = {
"train_synthesis_model",
"synthesize_circuit",
"explore_hardware_topology",
}
assert expected_prompts.issubset(prompt_names), (
f"Missing prompts: {expected_prompts - prompt_names}"
)

def test_prompt_count(self):
"""Test the expected number of prompts."""
assert len(mcp._prompt_manager._prompts) == 3

def test_resource_templates_registered(self):
"""Test that all expected resource templates are registered."""
template_uris = set(mcp._resource_manager._templates.keys())
expected_templates = {
"qiskit-gym://environments/{env_id}",
"qiskit-gym://models/{model_name}",
"qiskit-gym://training/{session_id}",
}
assert expected_templates.issubset(template_uris), (
f"Missing resource templates: {expected_templates - template_uris}"
)

def test_resource_template_count(self):
"""Test the expected number of resource templates."""
assert len(mcp._resource_manager._templates) == 3
Loading