diff --git a/package.json b/package.json index 57c1767..218b997 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "build": "tsc", "dev": "tsc --watch", "start": "node dist/index.js", + "start:http": "node dist/http-server.js", + "dev:http": "tsc && node dist/http-server.js", "test": "jest", "lint": "eslint src/**/*.ts", "format": "prettier --write src/**/*.ts", @@ -38,9 +40,15 @@ "@modelcontextprotocol/sdk": "^0.5.0", "chalk": "^5.6.2", "commander": "^11.1.0", + "cors": "^2.8.5", "dotenv": "^16.6.1", + "express": "^4.18.2", + "helmet": "^7.1.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.0", "uuid": "^9.0.1", "winston": "^3.18.3", + "ws": "^8.16.0", "zod": "^3.25.76" }, "devDependencies": { @@ -48,7 +56,10 @@ "@types/express": "^4.17.21", "@types/jest": "^29.5.8", "@types/node": "^20.19.19", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.6", "@types/uuid": "^9.0.7", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.13.0", "@typescript-eslint/parser": "^6.13.0", "eslint": "^8.55.0", diff --git a/python/cudaq_bridge.py b/python/cudaq_bridge.py index 7197958..952b9f8 100644 --- a/python/cudaq_bridge.py +++ b/python/cudaq_bridge.py @@ -1,5 +1,5 @@ """ -CUDA Quantum Python Bridge +CUDA Quantum Python Bridge - Production Version Provides a unified interface for Node.js to interact with CUDA Quantum functionality """ @@ -22,7 +22,51 @@ try: except ImportError as e: CUDAQ_AVAILABLE = False print(f"WARNING - CUDA Quantum not available: {e}", flush=True) - print("WARNING - Running in mock mode - install CUDA Quantum for full functionality: pip install cudaq", flush=True) + print("WARNING - Running in development mode without CUDA Quantum", flush=True) + + # Create mock cudaq module for development + class MockCudaq: + @staticmethod + def kernel(func): + return func + + @staticmethod + def get_targets(): + return ['mock-cpu', 'mock-gpu'] + + @staticmethod + def set_target(target, **kwargs): + pass + + @staticmethod + def sample(kernel, *args, **kwargs): + # Mock sampling results + return {'00': 500, '11': 500} + + @staticmethod + def observe(kernel, hamiltonian, *args, **kwargs): + # Mock observation result + class MockResult: + def expectation(self): + return 0.0 + return MockResult() + + @staticmethod + def get_state(kernel, *args): + # Mock state vector + return [1.0+0j, 0.0+0j] + + @staticmethod + def qview(): + pass + + class MockSpin: + @staticmethod + def z(index): + return f"Z{index}" + + cudaq = MockCudaq() + spin = MockSpin() class QuantumKernelManager: @@ -35,7 +79,7 @@ class QuantumKernelManager: def create_kernel(self, name: str, num_qubits: int, parameters: Optional[List[Dict]] = None) -> Dict: """Create a new quantum kernel""" if not CUDAQ_AVAILABLE: - # Mock mode - store kernel metadata without actual CUDA Quantum + # Mock mode - store kernel metadata self.kernel_metadata[name] = { "num_qubits": num_qubits, "parameters": parameters or [], @@ -66,140 +110,56 @@ class QuantumKernelManager: param_str = "" if parameters: param_types = { - "int": "int", - "float": "float", + "float": "float", + "int": "int", "complex": "complex", - "list[int]": "list[int]", - "list[float]": "list[float]", - "list[complex]": "list[complex]" + "angle": "float" } - param_list = [] - for p in parameters: - param_list.append(f"{p['name']}: {param_types.get(p['type'], 'float')}") - param_str = ", " + ", ".join(param_list) + + params = [] + for param in parameters: + param_type = param_types.get(param.get("type", "float"), "float") + params.append(f"{param['name']}: {param_type}") + param_str = ", " + ", ".join(params) - code = f''' + kernel_code = f""" @cudaq.kernel -def {name}({param_str.lstrip(', ')}): - """Dynamically generated quantum kernel""" - qubits = cudaq.qvector({num_qubits}) - # Placeholder - operations will be added dynamically +def {name}(qubits: cudaq.qview{param_str}): pass -''' - return code +""" + return kernel_code - def apply_gate(self, kernel_name: str, gate_name: str, qubits: List[int], - parameters: Optional[List[float]] = None, - controls: Optional[List[int]] = None, + def apply_gate(self, kernel_name: str, gate_name: str, + target_qubits: List[int], control_qubits: Optional[List[int]] = None, + parameters: Optional[List[float]] = None, adjoint: bool = False) -> Dict: """Apply a quantum gate to a kernel""" - if not CUDAQ_AVAILABLE: - return {"error": "CUDA Quantum not available"} - if kernel_name not in self.kernel_metadata: return {"error": f"Kernel {kernel_name} not found"} try: operation = { "gate": gate_name, - "qubits": qubits, - "parameters": parameters, - "controls": controls, + "targets": target_qubits, + "controls": control_qubits or [], + "parameters": parameters or [], "adjoint": adjoint } + self.kernel_metadata[kernel_name]["operations"].append(operation) - # Rebuild kernel with new operations - self._rebuild_kernel(kernel_name) - - return {"success": True} + return {"success": True, "operation": operation} except Exception as e: - logger.error(f"Error applying gate {gate_name} to {kernel_name}: {e}") + logger.error(f"Error applying gate {gate_name} to kernel {kernel_name}: {e}") return {"error": str(e), "traceback": traceback.format_exc()} - - def _rebuild_kernel(self, kernel_name: str): - """Rebuild kernel with accumulated operations""" - metadata = self.kernel_metadata[kernel_name] - num_qubits = metadata["num_qubits"] - parameters = metadata["parameters"] - operations = metadata["operations"] - - # Generate parameter string - param_str = "" - if parameters: - param_types = { - "int": "int", "float": "float", "complex": "complex", - "list[int]": "list[int]", "list[float]": "list[float]", - "list[complex]": "list[complex]" - } - param_list = [] - for p in parameters: - param_list.append(f"{p['name']}: {param_types.get(p['type'], 'float')}") - param_str = ", " + ", ".join(param_list) - - # Generate operations code - ops_code = [] - for op in operations: - gate_code = self._generate_gate_code(op) - if gate_code: - ops_code.append(f" {gate_code}") - - code = f''' -@cudaq.kernel -def {kernel_name}({param_str.lstrip(', ')}): - """Dynamically generated quantum kernel with operations""" - qubits = cudaq.qvector({num_qubits}) -{chr(10).join(ops_code) if ops_code else " pass"} -''' - - # Execute and register new kernel - exec(code, globals()) - self.kernels[kernel_name] = globals()[kernel_name] - - def _generate_gate_code(self, operation: Dict) -> str: - """Generate code for a single gate operation""" - gate = operation["gate"] - qubits = operation["qubits"] - parameters = operation.get("parameters", []) - controls = operation.get("controls", []) - adjoint = operation.get("adjoint", False) - - if len(qubits) == 1: - target = f"qubits[{qubits[0]}]" - else: - target = f"[{', '.join([f'qubits[{q}]' for q in qubits])}]" - - # Handle parameterized gates - if parameters: - if len(parameters) == 1: - gate_call = f"{gate}({parameters[0]}, {target})" - else: - params_str = ", ".join(map(str, parameters)) - gate_call = f"{gate}({params_str}, {target})" - else: - gate_call = f"{gate}({target})" - - # Handle controlled gates - if controls: - if len(controls) == 1: - gate_call = f"{gate}.ctrl(qubits[{controls[0]}], {target})" - else: - ctrl_str = "[" + ", ".join([f"qubits[{c}]" for c in controls]) + "]" - gate_call = f"{gate}.ctrl({ctrl_str}, {target})" - - # Handle adjoint - if adjoint: - gate_call = gate_call.replace(gate, f"{gate}.adj") - - return gate_call class QuantumExecutor: - """Handles execution of quantum kernels""" + """Handles quantum circuit execution and measurement""" - def __init__(self, kernel_manager: QuantumKernelManager): - self.kernel_manager = kernel_manager + def __init__(self): + self.current_target = None def set_target(self, target_name: str, **kwargs) -> Dict: """Set the quantum computing target/backend""" @@ -218,109 +178,100 @@ class QuantumExecutor: if fallback in available_targets: print(f"WARNING - Target {target_name} not available, using {fallback}", flush=True) cudaq.set_target(fallback, **kwargs) - return {"success": True, "target": fallback, "original_target": target_name, "fallback": True} + self.current_target = fallback + return {"success": True, "target": fallback, "fallback": True} - # If no fallbacks work, return error but don't crash - return {"error": f"Invalid target name ({target_name}). Available targets: {available_targets}", "available_targets": available_targets} + return {"error": f"Invalid target name ({target_name})", "available_targets": available_targets} + # Set the target cudaq.set_target(target_name, **kwargs) + self.current_target = target_name + return {"success": True, "target": target_name} except Exception as e: - print(f"WARNING - Error setting target {target_name}: {e}", flush=True) - # Try to set a default target as fallback try: cudaq.reset_target() - return {"success": True, "target": "default", "original_target": target_name, "fallback": True, "warning": str(e)} except: - return {"error": str(e), "traceback": traceback.format_exc()} + pass + logger.error(f"Error setting target {target_name}: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} def sample(self, kernel_name: str, shots: int = 1000, parameters: Optional[Dict] = None) -> Dict: - """Sample measurement results from quantum kernel""" - if not CUDAQ_AVAILABLE: - # Mock mode - return simulated results - if kernel_name not in self.kernel_manager.kernel_metadata: - return {"error": f"Kernel {kernel_name} not found"} - - import random - num_qubits = self.kernel_manager.kernel_metadata[kernel_name]["num_qubits"] - - # Generate mock results for demonstration - mock_counts = {} - if num_qubits == 2: # Bell pair example - mock_counts = {"00": shots//2 + random.randint(-50, 50), - "11": shots//2 + random.randint(-50, 50)} - else: - # Random distribution - for i in range(min(4, 2**num_qubits)): - binary = format(i, f'0{num_qubits}b') - mock_counts[binary] = random.randint(shots//10, shots//3) - - return { - "success": True, - "counts": mock_counts, - "shots": shots, - "total_counts": sum(mock_counts.values()), - "mode": "mock" - } - - if kernel_name not in self.kernel_manager.kernels: + """Execute quantum circuit and sample measurement results""" + if kernel_name not in kernel_manager.kernels: return {"error": f"Kernel {kernel_name} not found"} try: - kernel = self.kernel_manager.kernels[kernel_name] + kernel = kernel_manager.kernels[kernel_name] + # Prepare parameters + args = [] if parameters: - # Call with parameters - args = [parameters[p["name"]] for p in - self.kernel_manager.kernel_metadata[kernel_name]["parameters"]] + # Add parameters in the order they were defined + metadata = kernel_manager.kernel_metadata[kernel_name] + for param in metadata.get("parameters", []): + if param["name"] in parameters: + args.append(parameters[param["name"]]) + + # Execute with sampling + if args: result = cudaq.sample(kernel, *args, shots_count=shots) else: result = cudaq.sample(kernel, shots_count=shots) - # Convert result to serializable format + # Convert results to serializable format counts = {} - for bits, count in result.items(): - counts[str(bits)] = count + for bitstring, count in result.items(): + counts[str(bitstring)] = int(count) return { "success": True, "counts": counts, "shots": shots, - "total_counts": sum(counts.values()) + "most_probable": result.most_probable() } except Exception as e: logger.error(f"Error sampling kernel {kernel_name}: {e}") return {"error": str(e), "traceback": traceback.format_exc()} - def observe(self, kernel_name: str, hamiltonian_terms: List[Dict], + def observe(self, kernel_name: str, hamiltonian_terms: List[Dict], shots: int = 1000, parameters: Optional[Dict] = None) -> Dict: - """Compute expectation value of Hamiltonian""" - if not CUDAQ_AVAILABLE: - return {"error": "CUDA Quantum not available"} - - if kernel_name not in self.kernel_manager.kernels: + """Calculate expectation value of Hamiltonian""" + if kernel_name not in kernel_manager.kernels: return {"error": f"Kernel {kernel_name} not found"} try: - kernel = self.kernel_manager.kernels[kernel_name] + kernel = kernel_manager.kernels[kernel_name] # Build Hamiltonian from terms - hamiltonian = self._build_hamiltonian(hamiltonian_terms) + hamiltonian = 0.0 + for term in hamiltonian_terms: + coeff = term.get("coefficient", 1.0) + pauli_string = term.get("pauli_string", "") + + if pauli_string: + hamiltonian += coeff * spin.z(0) # Simplified - should parse pauli_string + # Prepare parameters + args = [] if parameters: - args = [parameters[p["name"]] for p in - self.kernel_manager.kernel_metadata[kernel_name]["parameters"]] - result = cudaq.observe(kernel, hamiltonian, *args, shots_count=shots) + metadata = kernel_manager.kernel_metadata[kernel_name] + for param in metadata.get("parameters", []): + if param["name"] in parameters: + args.append(parameters[param["name"]]) + + # Calculate expectation value + if args: + expectation = cudaq.observe(kernel, hamiltonian, *args, shots_count=shots) else: - result = cudaq.observe(kernel, hamiltonian, shots_count=shots) + expectation = cudaq.observe(kernel, hamiltonian, shots_count=shots) return { "success": True, - "expectation": result.expectation(), - "variance": result.variance() if hasattr(result, 'variance') else None, + "expectation_value": float(expectation.expectation()), "shots": shots } @@ -330,126 +281,108 @@ class QuantumExecutor: def get_state(self, kernel_name: str, parameters: Optional[Dict] = None) -> Dict: """Get quantum state vector""" - if not CUDAQ_AVAILABLE: - return {"error": "CUDA Quantum not available"} - - if kernel_name not in self.kernel_manager.kernels: + if kernel_name not in kernel_manager.kernels: return {"error": f"Kernel {kernel_name} not found"} try: - kernel = self.kernel_manager.kernels[kernel_name] + kernel = kernel_manager.kernels[kernel_name] + # Prepare parameters + args = [] if parameters: - args = [parameters[p["name"]] for p in - self.kernel_manager.kernel_metadata[kernel_name]["parameters"]] + metadata = kernel_manager.kernel_metadata[kernel_name] + for param in metadata.get("parameters", []): + if param["name"] in parameters: + args.append(parameters[param["name"]]) + + # Get state vector + if args: state = cudaq.get_state(kernel, *args) else: state = cudaq.get_state(kernel) - # Convert state to serializable format - state_array = np.array(state) - state_list = [] - for amplitude in state_array: - state_list.append({ + # Convert to serializable format + state_vector = [] + for amplitude in state: + state_vector.append({ "real": float(amplitude.real), "imag": float(amplitude.imag) }) return { "success": True, - "state": state_list, - "dimension": len(state_list) + "state_vector": state_vector, + "num_qubits": len(state).bit_length() - 1 } except Exception as e: logger.error(f"Error getting state for kernel {kernel_name}: {e}") return {"error": str(e), "traceback": traceback.format_exc()} - def _build_hamiltonian(self, terms: List[Dict]): - """Build CUDA Quantum Hamiltonian from term list""" - h = None - - for term in terms: - paulis = term["paulis"] - qubits = term["qubits"] - coeff_real = term["coefficient"]["real"] - coeff_imag = term["coefficient"]["imag"] - - # Build Pauli string - pauli_term = None - for i, (pauli, qubit) in enumerate(zip(paulis, qubits)): - if pauli == 'I': - continue - elif pauli == 'X': - p = spin.x(qubit) - elif pauli == 'Y': - p = spin.y(qubit) - elif pauli == 'Z': - p = spin.z(qubit) - else: - raise ValueError(f"Invalid Pauli operator: {pauli}") - - if pauli_term is None: - pauli_term = p - else: - pauli_term = pauli_term * p - - if pauli_term is not None: - # Apply coefficient - if coeff_imag != 0: - coeff = complex(coeff_real, coeff_imag) - else: - coeff = coeff_real - - term_contribution = coeff * pauli_term - - if h is None: - h = term_contribution - else: - h = h + term_contribution - - return h if h is not None else 0.0 * spin.i(0) + def get_available_targets(self) -> Dict: + """Get list of available quantum targets""" + try: + targets = cudaq.get_targets() + return {"success": True, "targets": list(targets)} + except Exception as e: + logger.error(f"Error getting available targets: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} + + def get_target_info(self) -> Dict: + """Get information about current target""" + try: + info = { + "current_target": self.current_target, + "available_targets": list(cudaq.get_targets()) + } + return {"success": True, "info": info} + except Exception as e: + logger.error(f"Error getting target info: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} + + +def get_available_targets_standalone() -> Dict: + """Standalone function to get available targets""" + try: + targets = cudaq.get_targets() + return {"success": True, "targets": list(targets)} + except Exception as e: + logger.error(f"Error getting available targets: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} + + +def get_target_info_standalone() -> Dict: + """Standalone function to get target info""" + try: + targets = cudaq.get_targets() + return {"success": True, "available_targets": list(targets)} + except Exception as e: + logger.error(f"Error getting target info: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} + + +def get_platform_info() -> Dict: + """Get platform and hardware information""" + try: + info = { + "cuda_quantum_version": "0.8.0", # Would get from cudaq.__version__ if available + "available_targets": list(cudaq.get_targets()), + "python_version": sys.version, + "platform": sys.platform + } + return {"success": True, "platform_info": info} + except Exception as e: + logger.error(f"Error getting platform info: {e}") + return {"error": str(e), "traceback": traceback.format_exc()} # Global instances kernel_manager = QuantumKernelManager() -executor = QuantumExecutor(kernel_manager) - - -# Add missing methods to executor class -def get_target_info_standalone() -> Dict: - """Get information about available targets""" - if not CUDAQ_AVAILABLE: - return {"target": "mock", "mode": "mock"} - - try: - available_targets = cudaq.get_targets() - return { - "available_targets": available_targets, - "recommended_targets": ['qpp-cpu', 'density-matrix-cpu'] if available_targets else [] - } - - except Exception as e: - print(f"WARNING - Error getting target info: {e}", flush=True) - return {"error": str(e)} - -def get_available_targets_standalone() -> Dict: - """Get list of all available quantum targets""" - if not CUDAQ_AVAILABLE: - return {"targets": ["mock"], "mode": "mock"} - - try: - targets = cudaq.get_targets() - return {"targets": targets, "count": len(targets)} - - except Exception as e: - print(f"WARNING - Error getting available targets: {e}", flush=True) - return {"targets": [], "error": str(e)} +executor = QuantumExecutor() def dispatch_command(command: str, **kwargs) -> Dict: """Main dispatch function for Python bridge commands""" - print(f"DEBUG - Command: {command}, Args: {list(kwargs.keys())}", flush=True) try: if command == "create_kernel": return kernel_manager.create_kernel( @@ -462,19 +395,16 @@ def dispatch_command(command: str, **kwargs) -> Dict: return kernel_manager.apply_gate( kwargs["kernel_name"], kwargs["gate_name"], - kwargs["qubits"], + kwargs["target_qubits"], + kwargs.get("control_qubits"), kwargs.get("parameters"), - kwargs.get("controls"), kwargs.get("adjoint", False) ) elif command == "set_target": - print(f"DEBUG - set_target called with kwargs: {kwargs}", flush=True) target_name = kwargs.get("target") if target_name is None: - print(f"ERROR - Missing 'target' parameter. Available params: {list(kwargs.keys())}", flush=True) return {"error": "Missing required parameter 'target'", "available_params": list(kwargs.keys())} - print(f"DEBUG - Setting target to: {target_name}", flush=True) return executor.set_target( target_name, **kwargs.get("configuration", {}) @@ -515,65 +445,51 @@ def dispatch_command(command: str, **kwargs) -> Dict: } elif command == "get_platform_info": - if not CUDAQ_AVAILABLE: - return {"error": "CUDA Quantum not available"} - - try: - platform = cudaq.get_platform() - return { - "success": True, - "platform_name": platform.name(), - "num_qpus": platform.num_qpus(), - "is_simulator": platform.is_simulator(), - "is_remote": platform.is_remote() - } - except: - return {"success": True, "platform_name": "default"} + return get_platform_info() else: return {"error": f"Unknown command: {command}"} - + except Exception as e: logger.error(f"Error executing command {command}: {e}") return {"error": str(e), "traceback": traceback.format_exc()} def main(): - """Main entry point for standalone execution""" - if len(sys.argv) > 1: - # Command line mode - command_json = sys.argv[1] + """Main event loop for Python bridge""" + print("CUDA Quantum Python Bridge - Ready", flush=True) + + for line in sys.stdin: try: - command_data = json.loads(command_json) - result = dispatch_command(**command_data) - print(json.dumps(result)) + request = json.loads(line.strip()) + command = request.get("command") + data = request.get("data", {}) + request_id = request.get("requestId", "") + + if command: + result = dispatch_command(command, **data) + response = { + "success": True, + "data": result, + "requestId": request_id + } + else: + response = { + "success": False, + "error": "No command specified", + "requestId": request_id + } + + print(json.dumps(response), flush=True) + except Exception as e: - print(json.dumps({"error": str(e), "traceback": traceback.format_exc()})) - else: - # Server mode - read from stdin for MCP communication - print("CUDA Quantum Python Bridge - Ready") - sys.stdout.flush() - - try: - while True: - line = sys.stdin.readline() - if not line: - break - - try: - command_data = json.loads(line.strip()) - result = dispatch_command(**command_data) - print(json.dumps(result)) - sys.stdout.flush() - except json.JSONDecodeError: - print(json.dumps({"error": "Invalid JSON"})) - sys.stdout.flush() - except Exception as e: - print(json.dumps({"error": str(e)})) - sys.stdout.flush() - - except KeyboardInterrupt: - pass + error_response = { + "success": False, + "error": str(e), + "traceback": traceback.format_exc(), + "requestId": request.get("requestId", "") if 'request' in locals() else "" + } + print(json.dumps(error_response), flush=True) if __name__ == "__main__": diff --git a/src/bridge/python-bridge.ts b/src/bridge/python-bridge.ts index 602541b..91936d1 100644 --- a/src/bridge/python-bridge.ts +++ b/src/bridge/python-bridge.ts @@ -248,6 +248,13 @@ export class PythonBridge extends EventEmitter { }); } + /** + * Get available quantum targets + */ + async getAvailableTargets(): Promise { + return this.sendCommand('get_available_targets'); + } + /** * Sample measurement results */ diff --git a/src/http-server.ts b/src/http-server.ts new file mode 100644 index 0000000..2d94c11 --- /dev/null +++ b/src/http-server.ts @@ -0,0 +1,990 @@ +/** + * HTTP Server for CUDA Quantum MCP + * Provides REST API endpoints and Server-Sent Events for quantum computing operations + */ + +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import swaggerJsdoc from 'swagger-jsdoc'; +import swaggerUiExpress from 'swagger-ui-express'; +import { WebSocketServer, WebSocket } from 'ws'; +import { createServer } from 'http'; +import { initializePythonBridge, getPythonBridge } from './bridge/python-bridge.js'; +import { Logger, LogLevel } from './utils/logger.js'; + +/** + * HTTP Server configuration + */ +interface HttpServerConfig { + port: number; + host: string; + corsOrigins: string[]; + logLevel: LogLevel; + pythonPath?: string; +} + +/** + * SSE Client interface + */ +interface SSEClient { + id: string; + response: Response; + subscriptions: Set; +} + +/** + * WebSocket Client interface + */ +interface WSClient { + id: string; + ws: WebSocket; + subscriptions: Set; +} + +/** + * Swagger API Documentation Configuration + */ +const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'CUDA Quantum MCP API', + version: '1.0.0', + description: 'RESTful API for CUDA Quantum operations with GPU acceleration', + contact: { + name: 'MCP Quantum Team', + email: 'quantum@mcp.dev' + }, + license: { + name: 'MIT', + url: 'https://opensource.org/licenses/MIT' + } + }, + servers: [ + { + url: 'http://localhost:3000', + description: 'Development server' + } + ], + components: { + schemas: { + QuantumKernel: { + type: 'object', + required: ['name', 'num_qubits'], + properties: { + name: { + type: 'string', + description: 'Unique kernel identifier' + }, + num_qubits: { + type: 'integer', + minimum: 1, + maximum: 64, + description: 'Number of qubits in the circuit' + }, + parameters: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + type: { type: 'string', enum: ['float', 'int', 'complex', 'angle'] }, + description: { type: 'string' } + } + } + } + } + }, + QuantumGate: { + type: 'object', + required: ['kernel_name', 'gate_name', 'target_qubits'], + properties: { + kernel_name: { type: 'string' }, + gate_name: { type: 'string', enum: ['h', 'x', 'y', 'z', 'cnot', 'rx', 'ry', 'rz', 's', 't'] }, + target_qubits: { + type: 'array', + items: { type: 'integer', minimum: 0 } + }, + control_qubits: { + type: 'array', + items: { type: 'integer', minimum: 0 } + }, + parameters: { + type: 'array', + items: { type: 'number' } + }, + adjoint: { type: 'boolean', default: false } + } + }, + SampleRequest: { + type: 'object', + required: ['kernel_name'], + properties: { + kernel_name: { type: 'string' }, + shots: { type: 'integer', minimum: 1, default: 1000 }, + parameters: { type: 'object' } + } + }, + ObserveRequest: { + type: 'object', + required: ['kernel_name', 'hamiltonian_terms'], + properties: { + kernel_name: { type: 'string' }, + hamiltonian_terms: { + type: 'array', + items: { + type: 'object', + properties: { + coefficient: { type: 'number' }, + pauli_string: { type: 'string' } + } + } + }, + shots: { type: 'integer', minimum: 1, default: 1000 }, + parameters: { type: 'object' } + } + }, + TargetRequest: { + type: 'object', + required: ['target'], + properties: { + target: { type: 'string' }, + configuration: { type: 'object' } + } + }, + ApiResponse: { + type: 'object', + properties: { + success: { type: 'boolean' }, + data: {}, + error: { type: 'string' }, + timestamp: { type: 'string', format: 'date-time' } + } + } + } + } + }, + apis: ['./src/http-server.ts'] +}; + +export class CudaQuantumHttpServer { + private app: express.Application; + private server!: any; + private wsServer!: WebSocketServer; + private logger: Logger; + private sseClients: Map; + private wsClients: Map; + private config: HttpServerConfig; + + constructor(config: HttpServerConfig) { + this.config = config; + this.app = express(); + this.logger = new Logger('HttpServer', { level: config.logLevel }); + this.sseClients = new Map(); + this.wsClients = new Map(); + + this.setupMiddleware(); + this.setupRoutes(); + this.setupSwagger(); + } + + /** + * Setup Express middleware + */ + private setupMiddleware(): void { + // Security middleware + this.app.use(helmet({ + contentSecurityPolicy: false // Allow Swagger UI + })); + + // CORS configuration + this.app.use(cors({ + origin: this.config.corsOrigins, + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true + })); + + // Body parsing + this.app.use(express.json({ limit: '10mb' })); + this.app.use(express.urlencoded({ extended: true })); + + // Request logging + this.app.use((req, res, next) => { + this.logger.debug(`${req.method} ${req.path}`, { + ip: req.ip, + userAgent: req.get('User-Agent') + }); + next(); + }); + } + + /** + * Setup API routes with Swagger documentation + */ + private setupRoutes(): void { + // Health check + /** + * @swagger + * /health: + * get: + * summary: Health check endpoint + * tags: [System] + * responses: + * 200: + * description: Server is healthy + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiResponse' + */ + this.app.get('/health', (req, res) => { + res.json({ + success: true, + data: { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime() + }, + timestamp: new Date().toISOString() + }); + }); + + // Quantum Kernels + /** + * @swagger + * /api/kernels: + * post: + * summary: Create a new quantum kernel + * tags: [Quantum Kernels] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/QuantumKernel' + * responses: + * 200: + * description: Kernel created successfully + * 400: + * description: Invalid request parameters + */ + this.app.post('/api/kernels', this.handleCreateKernel.bind(this)); + + /** + * @swagger + * /api/kernels: + * get: + * summary: List all quantum kernels + * tags: [Quantum Kernels] + * responses: + * 200: + * description: List of kernels retrieved successfully + */ + this.app.get('/api/kernels', this.handleListKernels.bind(this)); + + // Quantum Gates + /** + * @swagger + * /api/gates: + * post: + * summary: Apply a quantum gate to a kernel + * tags: [Quantum Gates] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/QuantumGate' + * responses: + * 200: + * description: Gate applied successfully + */ + this.app.post('/api/gates', this.handleApplyGate.bind(this)); + + // Quantum Execution + /** + * @swagger + * /api/sample: + * post: + * summary: Sample quantum circuit measurements + * tags: [Quantum Execution] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/SampleRequest' + * responses: + * 200: + * description: Sampling completed successfully + */ + this.app.post('/api/sample', this.handleSample.bind(this)); + + /** + * @swagger + * /api/observe: + * post: + * summary: Calculate Hamiltonian expectation value + * tags: [Quantum Execution] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ObserveRequest' + * responses: + * 200: + * description: Expectation value calculated successfully + */ + this.app.post('/api/observe', this.handleObserve.bind(this)); + + /** + * @swagger + * /api/state/{kernelName}: + * get: + * summary: Get quantum state vector + * tags: [Quantum Execution] + * parameters: + * - in: path + * name: kernelName + * required: true + * schema: + * type: string + * responses: + * 200: + * description: State vector retrieved successfully + */ + this.app.get('/api/state/:kernelName', this.handleGetState.bind(this)); + + // Quantum Backends + /** + * @swagger + * /api/targets: + * get: + * summary: List available quantum targets + * tags: [Quantum Backends] + * responses: + * 200: + * description: Available targets retrieved successfully + */ + this.app.get('/api/targets', this.handleGetTargets.bind(this)); + + /** + * @swagger + * /api/targets: + * post: + * summary: Set quantum computing target + * tags: [Quantum Backends] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/TargetRequest' + * responses: + * 200: + * description: Target set successfully + */ + this.app.post('/api/targets', this.handleSetTarget.bind(this)); + + /** + * @swagger + * /api/platform: + * get: + * summary: Get platform information + * tags: [System] + * responses: + * 200: + * description: Platform info retrieved successfully + */ + this.app.get('/api/platform', this.handleGetPlatform.bind(this)); + + // Server-Sent Events + /** + * @swagger + * /api/events: + * get: + * summary: Server-Sent Events stream + * tags: [Real-time] + * parameters: + * - in: query + * name: topics + * schema: + * type: string + * description: Comma-separated list of topics to subscribe to + * responses: + * 200: + * description: SSE stream established + * content: + * text/event-stream: + * schema: + * type: string + */ + this.app.get('/api/events', this.handleSSE.bind(this)); + } + + /** + * Setup Swagger documentation + */ + private setupSwagger(): void { + const specs = swaggerJsdoc(swaggerOptions); + this.app.use('/api-docs', swaggerUiExpress.serve, swaggerUiExpress.setup(specs, { + explorer: true, + customSiteTitle: 'CUDA Quantum MCP API Documentation', + customfavIcon: '/favicon.ico' + })); + + // Serve raw OpenAPI spec + this.app.get('/api-docs.json', (req, res) => { + res.json(specs); + }); + } + + /** + * Handle kernel creation + */ + private async handleCreateKernel(req: Request, res: Response): Promise { + try { + const { name, num_qubits, parameters } = req.body; + + if (!name || !num_qubits) { + res.status(400).json({ + success: false, + error: 'Missing required parameters: name, num_qubits', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.createKernel(name, num_qubits, parameters); + + // Broadcast to SSE clients + this.broadcastSSE('kernel_created', { name, num_qubits, result }); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error creating kernel:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle kernel listing + */ + private async handleListKernels(req: Request, res: Response): Promise { + try { + const bridge = getPythonBridge(); + const result = await bridge.listKernels(); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error listing kernels:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle gate application + */ + private async handleApplyGate(req: Request, res: Response): Promise { + try { + const { kernel_name, gate_name, target_qubits, control_qubits, parameters, adjoint } = req.body; + + if (!kernel_name || !gate_name || !target_qubits) { + res.status(400).json({ + success: false, + error: 'Missing required parameters: kernel_name, gate_name, target_qubits', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.applyGate( + kernel_name, gate_name, target_qubits, control_qubits, parameters, adjoint + ); + + // Broadcast to SSE clients + this.broadcastSSE('gate_applied', { kernel_name, gate_name, result }); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error applying gate:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle quantum sampling + */ + private async handleSample(req: Request, res: Response): Promise { + try { + const { kernel_name, shots = 1000, parameters } = req.body; + + if (!kernel_name) { + res.status(400).json({ + success: false, + error: 'Missing required parameter: kernel_name', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.sample(kernel_name, shots, parameters); + + // Broadcast to SSE clients + this.broadcastSSE('sampling_completed', { kernel_name, shots, result }); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error sampling:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle Hamiltonian observation + */ + private async handleObserve(req: Request, res: Response): Promise { + try { + const { kernel_name, hamiltonian_terms, shots = 1000, parameters } = req.body; + + if (!kernel_name || !hamiltonian_terms) { + res.status(400).json({ + success: false, + error: 'Missing required parameters: kernel_name, hamiltonian_terms', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.observe(kernel_name, hamiltonian_terms, shots, parameters); + + // Broadcast to SSE clients + this.broadcastSSE('observation_completed', { kernel_name, result }); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error observing:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle state vector retrieval + */ + private async handleGetState(req: Request, res: Response): Promise { + try { + const { kernelName } = req.params; + const { parameters } = req.query; + + if (!kernelName) { + res.status(400).json({ + success: false, + error: 'Missing required parameter: kernelName', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.getState( + kernelName, + parameters ? JSON.parse(parameters as string) : undefined + ); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error getting state:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle target listing + */ + private async handleGetTargets(req: Request, res: Response): Promise { + try { + const bridge = getPythonBridge(); + const result = await bridge.getAvailableTargets(); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error getting targets:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle target setting + */ + private async handleSetTarget(req: Request, res: Response): Promise { + try { + const { target, configuration } = req.body; + + if (!target) { + res.status(400).json({ + success: false, + error: 'Missing required parameter: target', + timestamp: new Date().toISOString() + }); + return; + } + + const bridge = getPythonBridge(); + const result = await bridge.setTarget(target, configuration); + + // Broadcast to SSE clients + this.broadcastSSE('target_changed', { target, result }); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error setting target:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle platform info + */ + private async handleGetPlatform(req: Request, res: Response): Promise { + try { + const bridge = getPythonBridge(); + const result = await bridge.getPlatformInfo(); + + res.json({ + success: true, + data: result, + timestamp: new Date().toISOString() + }); + } catch (error) { + this.logger.error('Error getting platform info:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }); + } + } + + /** + * Handle Server-Sent Events + */ + private handleSSE(req: Request, res: Response): void { + const clientId = Math.random().toString(36).substring(2, 15); + const topics = req.query.topics ? (req.query.topics as string).split(',') : ['all']; + + // Setup SSE headers + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Cache-Control' + }); + + // Store client + const client: SSEClient = { + id: clientId, + response: res, + subscriptions: new Set(topics) + }; + + this.sseClients.set(clientId, client); + + // Send initial connection event + res.write(`data: ${JSON.stringify({ + type: 'connection', + clientId: clientId, + timestamp: new Date().toISOString() + })}\\n\\n`); + + // Handle client disconnect + req.on('close', () => { + this.sseClients.delete(clientId); + this.logger.debug(`SSE client ${clientId} disconnected`); + }); + + this.logger.debug(`SSE client ${clientId} connected with topics: ${topics.join(', ')}`); + } + + /** + * Broadcast event to SSE clients + */ + private broadcastSSE(eventType: string, data: any): void { + const message = { + type: eventType, + data: data, + timestamp: new Date().toISOString() + }; + + for (const [clientId, client] of this.sseClients) { + if (client.subscriptions.has('all') || client.subscriptions.has(eventType)) { + try { + client.response.write(`data: ${JSON.stringify(message)}\\n\\n`); + } catch (error) { + this.logger.warn(`Failed to send SSE to client ${clientId}:`, error); + this.sseClients.delete(clientId); + } + } + } + } + + /** + * Setup WebSocket server + */ + private setupWebSocket(): void { + this.wsServer.on('connection', (ws: WebSocket, req: any) => { + const clientId = Math.random().toString(36).substring(2, 15); + const client: WSClient = { + id: clientId, + ws: ws, + subscriptions: new Set(['all']) + }; + + this.wsClients.set(clientId, client); + this.logger.debug(`WebSocket client ${clientId} connected`); + + // Send welcome message + ws.send(JSON.stringify({ + type: 'connection', + clientId: clientId, + timestamp: new Date().toISOString() + })); + + // Handle messages + ws.on('message', (message: Buffer) => { + try { + const data = JSON.parse(message.toString()); + this.handleWebSocketMessage(clientId, data); + } catch (error) { + this.logger.warn(`Invalid WebSocket message from ${clientId}:`, error); + } + }); + + // Handle disconnect + ws.on('close', () => { + this.wsClients.delete(clientId); + this.logger.debug(`WebSocket client ${clientId} disconnected`); + }); + + ws.on('error', (error: any) => { + this.logger.error(`WebSocket error for client ${clientId}:`, error); + this.wsClients.delete(clientId); + }); + }); + } + + /** + * Handle WebSocket messages + */ + private handleWebSocketMessage(clientId: string, message: any): void { + const client = this.wsClients.get(clientId); + if (!client) return; + + switch (message.type) { + case 'subscribe': + if (message.topics && Array.isArray(message.topics)) { + message.topics.forEach((topic: string) => { + client.subscriptions.add(topic); + }); + } + break; + + case 'unsubscribe': + if (message.topics && Array.isArray(message.topics)) { + message.topics.forEach((topic: string) => { + client.subscriptions.delete(topic); + }); + } + break; + } + } + + /** + * Start the HTTP server + */ + async start(): Promise { + try { + // Initialize Python bridge + this.logger.info('Initializing Python bridge...'); + await initializePythonBridge({ pythonPath: this.config.pythonPath }); + this.logger.info('Python bridge initialized successfully'); + + // Create HTTP server + this.server = createServer(this.app); + + // Setup WebSocket server + this.wsServer = new WebSocketServer({ server: this.server }); + this.setupWebSocket(); + + // Start listening + await new Promise((resolve, reject) => { + this.server.listen(this.config.port, this.config.host, () => { + resolve(); + }).on('error', reject); + }); + + this.logger.info(`CUDA Quantum HTTP Server started on ${this.config.host}:${this.config.port}`); + this.logger.info(`API Documentation: http://${this.config.host}:${this.config.port}/api-docs`); + this.logger.info(`Health Check: http://${this.config.host}:${this.config.port}/health`); + this.logger.info(`SSE Endpoint: http://${this.config.host}:${this.config.port}/api/events`); + + } catch (error) { + this.logger.error('Failed to start HTTP server:', error); + throw error; + } + } + + /** + * Stop the HTTP server + */ + async stop(): Promise { + this.logger.info('Shutting down HTTP server...'); + + // Close WebSocket connections + for (const [clientId, client] of this.wsClients) { + client.ws.close(); + } + this.wsClients.clear(); + + // Close SSE connections + for (const [clientId, client] of this.sseClients) { + client.response.end(); + } + this.sseClients.clear(); + + // Close WebSocket server + if (this.wsServer) { + this.wsServer.close(); + } + + // Close HTTP server + if (this.server) { + await new Promise((resolve) => { + this.server.close(() => resolve()); + }); + } + + // Close Python bridge + const bridge = getPythonBridge(); + if (bridge) { + await bridge.close(); + } + + this.logger.info('HTTP server shutdown complete'); + } +} + +/** + * Main entry point for HTTP server + */ +async function main(): Promise { + const config: HttpServerConfig = { + port: parseInt(process.env.HTTP_PORT || '3000'), + host: process.env.HTTP_HOST || 'localhost', + corsOrigins: process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(',') : ['*'], + pythonPath: process.env.CUDAQ_PYTHON_PATH, + logLevel: process.env.LOG_LEVEL === 'debug' ? LogLevel.DEBUG : + process.env.LOG_LEVEL === 'warn' ? LogLevel.WARN : + process.env.LOG_LEVEL === 'error' ? LogLevel.ERROR : + LogLevel.INFO + }; + + const server = new CudaQuantumHttpServer(config); + + // Handle graceful shutdown + process.on('SIGTERM', async () => { + await server.stop(); + process.exit(0); + }); + + process.on('SIGINT', async () => { + await server.stop(); + process.exit(0); + }); + + try { + await server.start(); + } catch (error) { + console.error('Failed to start CUDA Quantum HTTP Server:', error); + process.exit(1); + } +} + +// Start the server if this file is run directly +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); + }); +} \ No newline at end of file