Skip to content

Commit 8a434a1

Browse files
authored
Merge pull request #205 from Qiskit/feat/193-gym-prompts-resources
gym-server: Add MCP prompts and resource templates
2 parents 9d7a723 + ba3d662 commit 8a434a1

2 files changed

Lines changed: 129 additions & 5 deletions

File tree

qiskit-gym-mcp-server/src/qiskit_gym_mcp_server/server_resources.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@
3030
HARDWARE_PRESETS,
3131
get_coupling_map_presets,
3232
)
33-
from qiskit_gym_mcp_server.models import list_loaded_models
33+
from qiskit_gym_mcp_server.gym_core import get_environment_info
34+
from qiskit_gym_mcp_server.models import get_model_info, list_loaded_models
3435
from qiskit_gym_mcp_server.training import (
3536
get_available_algorithms,
3637
get_available_policies,
38+
get_training_status,
3739
list_training_sessions,
3840
)
3941

@@ -217,3 +219,72 @@ async def workflows_resource() -> dict[str, Any]:
217219
"Increase num_searches (up to 10000) for better synthesis results",
218220
],
219221
}
222+
223+
224+
##################################################
225+
## MCP Prompts
226+
## - https://modelcontextprotocol.io/docs/concepts/prompts
227+
##################################################
228+
229+
230+
@mcp.prompt()
231+
def train_synthesis_model(env_type: str, num_qubits: str) -> str:
232+
"""Train a reinforcement learning model for quantum circuit synthesis."""
233+
return (
234+
f"Train an RL model for {env_type} synthesis on {num_qubits} qubits: "
235+
f"1) Call create_{env_type}_env_tool with appropriate parameters for "
236+
f"{num_qubits} qubits to create a training environment, "
237+
"2) Call start_training_tool with the returned env_id and algorithm='ppo', "
238+
"3) Call get_training_status_tool with the session_id to monitor progress, "
239+
"4) When training completes, call save_model_tool to persist the trained model."
240+
)
241+
242+
243+
@mcp.prompt()
244+
def synthesize_circuit(circuit_type: str) -> str:
245+
"""Synthesize a quantum circuit using a trained RL model."""
246+
return (
247+
f"Synthesize a {circuit_type} circuit using a trained model: "
248+
"1) Call list_saved_models_tool to see available models, "
249+
f"2) Call load_model_tool with a suitable model_name for {circuit_type} synthesis, "
250+
f"3) Generate a test input using generate_random_{circuit_type}_tool, "
251+
f"4) Call synthesize_{circuit_type}_tool with the model_id and the generated input, "
252+
"5) Call convert_qpy_to_qasm3_tool to view the resulting circuit."
253+
)
254+
255+
256+
@mcp.prompt()
257+
def explore_hardware_topology(backend_preset: str) -> str:
258+
"""Explore hardware topology and extract subtopologies for training."""
259+
return (
260+
f"Explore the '{backend_preset}' hardware topology: "
261+
"1) Read the qiskit-gym://presets/coupling-maps resource to see available presets, "
262+
f"2) Call extract_subtopologies_tool with preset='{backend_preset}' "
263+
"to find connected subtopologies, "
264+
"3) Call list_subtopology_shapes_tool to summarize the shapes found, "
265+
"4) Create environments for each unique subtopology using create_permutation_env_tool."
266+
)
267+
268+
269+
##################################################
270+
## MCP Resource Templates
271+
## - https://modelcontextprotocol.io/docs/concepts/resources#resource-templates
272+
##################################################
273+
274+
275+
@mcp.resource("qiskit-gym://environments/{env_id}", mime_type="application/json")
276+
async def environment_info_resource(env_id: str) -> dict[str, Any]:
277+
"""Get detailed information about a specific gym environment."""
278+
return await get_environment_info(env_id)
279+
280+
281+
@mcp.resource("qiskit-gym://models/{model_name}", mime_type="application/json")
282+
async def model_info_resource(model_name: str) -> dict[str, Any]:
283+
"""Get information about a specific trained model."""
284+
return await get_model_info(model_name=model_name)
285+
286+
287+
@mcp.resource("qiskit-gym://training/{session_id}", mime_type="application/json")
288+
async def training_status_resource(session_id: str) -> dict[str, Any]:
289+
"""Get the status and metrics of a specific training session."""
290+
return await get_training_status(session_id)

qiskit-gym-mcp-server/tests/test_server.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This code is part of Qiskit.
22
#
3-
# (C) Copyright IBM 2025.
3+
# (C) Copyright IBM 2026.
44
#
55
# This code is licensed under the Apache License, Version 2.0. You may
66
# obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -12,14 +12,14 @@
1212

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

15-
from qiskit_gym_mcp_server.app import mcp
15+
from qiskit_gym_mcp_server.server import mcp
1616

1717

1818
class TestServerRegistration:
19-
"""Test that the MCP server is configured correctly."""
19+
"""Test that tools, resources, prompts, and templates are registered."""
2020

2121
def test_server_name(self):
22-
"""Test the MCP server has the correct name."""
22+
"""Test the server name is correct."""
2323
assert mcp.name == "Qiskit Gym"
2424

2525
def test_server_instructions(self):
@@ -32,3 +32,56 @@ def test_server_instructions(self):
3232
assert "start_training_tool" in mcp.instructions
3333
assert "synthesize_permutation_tool" in mcp.instructions
3434
assert "qiskit-gym://" in mcp.instructions
35+
36+
def test_resources_registered(self):
37+
"""Test that all expected static resources are registered."""
38+
resource_uris = set(mcp._resource_manager._resources.keys())
39+
expected_resources = {
40+
"qiskit-gym://presets/coupling-maps",
41+
"qiskit-gym://algorithms",
42+
"qiskit-gym://policies",
43+
"qiskit-gym://environments",
44+
"qiskit-gym://training/sessions",
45+
"qiskit-gym://models",
46+
"qiskit-gym://server/config",
47+
"qiskit-gym://workflows",
48+
}
49+
assert expected_resources.issubset(resource_uris), (
50+
f"Missing resources: {expected_resources - resource_uris}"
51+
)
52+
53+
def test_resource_count(self):
54+
"""Test the expected number of static resources."""
55+
assert len(mcp._resource_manager._resources) == 8
56+
57+
def test_prompts_registered(self):
58+
"""Test that all expected prompts are registered."""
59+
prompt_names = set(mcp._prompt_manager._prompts.keys())
60+
expected_prompts = {
61+
"train_synthesis_model",
62+
"synthesize_circuit",
63+
"explore_hardware_topology",
64+
}
65+
assert expected_prompts.issubset(prompt_names), (
66+
f"Missing prompts: {expected_prompts - prompt_names}"
67+
)
68+
69+
def test_prompt_count(self):
70+
"""Test the expected number of prompts."""
71+
assert len(mcp._prompt_manager._prompts) == 3
72+
73+
def test_resource_templates_registered(self):
74+
"""Test that all expected resource templates are registered."""
75+
template_uris = set(mcp._resource_manager._templates.keys())
76+
expected_templates = {
77+
"qiskit-gym://environments/{env_id}",
78+
"qiskit-gym://models/{model_name}",
79+
"qiskit-gym://training/{session_id}",
80+
}
81+
assert expected_templates.issubset(template_uris), (
82+
f"Missing resource templates: {expected_templates - template_uris}"
83+
)
84+
85+
def test_resource_template_count(self):
86+
"""Test the expected number of resource templates."""
87+
assert len(mcp._resource_manager._templates) == 3

0 commit comments

Comments
 (0)