Source code for instrmcp.servers.jupyter_qcodes.core.qcodes_tools

"""
QCodes instrument tool registrar.

Registers tools for interacting with QCodes instruments.
"""

import json
import logging
from typing import List

from mcp.types import TextContent

from instrmcp.utils.logging_config import get_logger
from instrmcp.utils.mcptool_logger import log_tool_call
import time

logger = get_logger("tools.qcodes")


[docs] class QCodesToolRegistrar: """Registers QCodes instrument tools with the MCP server."""
[docs] def __init__(self, mcp_server, tools): """ Initialize the QCodes tool registrar. Args: mcp_server: FastMCP server instance tools: QCodesReadOnlyTools instance """ self.mcp = mcp_server self.tools = tools
def _to_concise_instrument_info(self, info: dict, name: str) -> dict: """Convert full instrument info to concise format. Concise format: status, name (or list), brief parameter summary (counts per level), omit full parameter dict/values and timing. """ # For wildcard query, return just names and count if name == "*": return { "status": "success", "instruments": info.get("instruments", []), "count": info.get("count", 0), } # For specific instrument, return concise summary with parameter list hierarchy_info = info.get("hierarchy_info", {}) direct_params = hierarchy_info.get("direct_parameters", []) channel_info = hierarchy_info.get("channel_info", {}) return { "status": "success", "name": info.get("name", name), "parameters": hierarchy_info.get("all_parameters", []), "parameter_summary": { "direct_count": len(direct_params), "channel_count": len(channel_info), "total_count": hierarchy_info.get("parameter_count", 0), }, "has_channels": hierarchy_info.get("has_channels", False), } def _to_concise_parameter_values(self, result) -> dict: """Convert full parameter value result to concise format. Concise format: per query {instrument, parameter, value, error?}; only include value. """ # Handle single result if isinstance(result, dict): return self._concise_single_param(result) # Handle batch results return [self._concise_single_param(r) for r in result] def _concise_single_param(self, result: dict) -> dict: """Convert a single parameter result to concise format.""" query = result.get("query", {}) concise = { "instrument": query.get("instrument", ""), "parameter": query.get("parameter", ""), } if "error" in result: concise["error"] = result["error"] else: concise["value"] = result.get("value") return concise
[docs] def register_all(self): """Register all QCodes instrument tools.""" self._register_instrument_info() self._register_get_parameter_info() self._register_get_parameter_values()
def _register_instrument_info(self): """Register the qcodes_instrument_info tool.""" @self.mcp.tool( name="qcodes_instrument_info", annotations={ "readOnlyHint": True, "idempotentHint": True, "openWorldHint": False, }, ) async def instrument_info( name: str, with_values: bool = False, detailed: bool = False ) -> List[TextContent]: # Description loaded from metadata_baseline.yaml start = time.perf_counter() try: info = await self.tools.instrument_info(name, with_values) duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_instrument_info", {"name": name, "with_values": with_values, "detailed": detailed}, duration, "success", ) # Apply concise mode filtering if not detailed: info = self._to_concise_instrument_info(info, name) return [ TextContent( type="text", text=json.dumps(info, indent=2, default=str) ) ] except Exception as e: duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_instrument_info", {"name": name, "with_values": with_values, "detailed": detailed}, duration, "error", str(e), ) logger.error(f"Error in qcodes_instrument_info: {e}") return [ TextContent( type="text", text=json.dumps({"error": str(e)}, indent=2) ) ] def _register_get_parameter_info(self): """Register the qcodes_get_parameter_info tool.""" @self.mcp.tool( name="qcodes_get_parameter_info", annotations={ "readOnlyHint": True, "idempotentHint": True, "openWorldHint": False, }, ) async def get_parameter_info( instrument: str, parameter: str, detailed: bool = False ) -> List[TextContent]: # Description loaded from metadata_baseline.yaml start = time.perf_counter() try: info = await self.tools.get_parameter_info( instrument, parameter, detailed ) duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_get_parameter_info", { "instrument": instrument, "parameter": parameter, "detailed": detailed, }, duration, "success", ) return [ TextContent( type="text", text=json.dumps(info, indent=2, default=str) ) ] except Exception as e: duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_get_parameter_info", { "instrument": instrument, "parameter": parameter, "detailed": detailed, }, duration, "error", str(e), ) logger.error(f"Error in qcodes_get_parameter_info: {e}") return [ TextContent( type="text", text=json.dumps({"error": str(e)}, indent=2) ) ] def _register_get_parameter_values(self): """Register the qcodes_get_parameter_values tool.""" @self.mcp.tool( name="qcodes_get_parameter_values", annotations={ "readOnlyHint": True, "idempotentHint": True, "openWorldHint": False, }, ) async def get_parameter_values( queries: str, detailed: bool = False ) -> List[TextContent]: # Description loaded from metadata_baseline.yaml start = time.perf_counter() try: queries_data = json.loads(queries) results = await self.tools.get_parameter_values(queries_data) duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_get_parameter_values", {"queries": queries, "detailed": detailed}, duration, "success", ) # Apply concise mode filtering if not detailed: results = self._to_concise_parameter_values(results) return [ TextContent( type="text", text=json.dumps(results, indent=2, default=str) ) ] except Exception as e: duration = (time.perf_counter() - start) * 1000 log_tool_call( "qcodes_get_parameter_values", {"queries": queries, "detailed": detailed}, duration, "error", str(e), ) logger.error(f"Error in qcodes_get_parameter_values: {e}") return [ TextContent( type="text", text=json.dumps({"error": str(e)}, indent=2) ) ]