|
23 | 23 | """ |
24 | 24 |
|
25 | 25 | import logging |
| 26 | +from collections.abc import AsyncIterator |
| 27 | +from contextlib import asynccontextmanager |
26 | 28 | from typing import Any |
27 | 29 |
|
| 30 | +import httpx |
28 | 31 | from fastmcp import FastMCP |
29 | 32 |
|
30 | 33 | from qiskit_code_assistant_mcp_server.constants import ( |
31 | 34 | QCA_MCP_DEBUG_LEVEL, |
| 35 | + QCA_REQUEST_TIMEOUT, |
| 36 | + QCA_TOOL_X_CALLER, |
32 | 37 | validate_configuration, |
33 | 38 | ) |
34 | 39 | from qiskit_code_assistant_mcp_server.qca import ( |
|
41 | 46 | get_service_status, |
42 | 47 | list_models, |
43 | 48 | ) |
44 | | -from qiskit_code_assistant_mcp_server.utils import close_http_client |
| 49 | +from qiskit_code_assistant_mcp_server.utils import ( |
| 50 | + _get_token, |
| 51 | + clear_http_client, |
| 52 | + set_http_client, |
| 53 | +) |
45 | 54 |
|
46 | 55 |
|
47 | 56 | # Configure logging |
48 | 57 | logging.basicConfig(level=getattr(logging, QCA_MCP_DEBUG_LEVEL, logging.INFO)) |
49 | 58 | logger = logging.getLogger(__name__) |
50 | 59 |
|
| 60 | + |
| 61 | +@asynccontextmanager |
| 62 | +async def lifespan(server: FastMCP) -> AsyncIterator[None]: |
| 63 | + """Manage the httpx client lifecycle.""" |
| 64 | + headers = { |
| 65 | + "x-caller": QCA_TOOL_X_CALLER, |
| 66 | + "Accept": "application/json", |
| 67 | + "Authorization": f"Bearer {_get_token()}", |
| 68 | + } |
| 69 | + async with httpx.AsyncClient( |
| 70 | + headers=headers, |
| 71 | + timeout=httpx.Timeout(QCA_REQUEST_TIMEOUT), |
| 72 | + limits=httpx.Limits(max_keepalive_connections=5, max_connections=10), |
| 73 | + ) as client: |
| 74 | + set_http_client(client) |
| 75 | + yield |
| 76 | + clear_http_client() |
| 77 | + |
| 78 | + |
51 | 79 | # Initialize FastMCP server |
52 | 80 | mcp = FastMCP( |
53 | 81 | "Qiskit Code Assistant", |
|
72 | 100 | Browse qca://models to discover available models and qca://status to check \ |
73 | 101 | service health.\ |
74 | 102 | """, |
| 103 | + lifespan=lifespan, |
75 | 104 | ) |
76 | 105 |
|
77 | 106 | logger.info("Qiskit Code Assistant MCP Server initialized") |
@@ -267,28 +296,8 @@ def setup_model(model_id: str) -> str: |
267 | 296 |
|
268 | 297 |
|
269 | 298 | if __name__ == "__main__": |
270 | | - import atexit |
271 | | - |
272 | 299 | logger.info("Starting Qiskit Code Assistant MCP Server") |
273 | | - |
274 | | - # Register cleanup function |
275 | | - def cleanup() -> None: |
276 | | - import asyncio |
277 | | - |
278 | | - try: |
279 | | - asyncio.run(close_http_client()) |
280 | | - logger.info("HTTP client closed successfully") |
281 | | - except Exception as e: |
282 | | - logger.error(f"Error closing HTTP client: {e}") |
283 | | - |
284 | | - atexit.register(cleanup) |
285 | | - |
286 | | - try: |
287 | | - mcp.run(transport="stdio", show_banner=False) |
288 | | - except KeyboardInterrupt: |
289 | | - logger.info("Server interrupted, shutting down...") |
290 | | - finally: |
291 | | - cleanup() |
| 300 | + mcp.run(transport="stdio", show_banner=False) |
292 | 301 |
|
293 | 302 |
|
294 | 303 | # Assisted by watsonx Code Assistant |
0 commit comments