|
1 | | -import asyncio |
2 | | -import logging |
3 | | -import signal |
4 | | -from contextlib import asynccontextmanager |
5 | | -from typing import Optional |
6 | | - |
7 | 1 | import click |
8 | | -import uvicorn |
9 | | -import yaml |
10 | | -from mcp.server.streamable_http_manager import ( # MCP SDK component |
11 | | - StreamableHTTPSessionManager, |
12 | | -) |
13 | | -from starlette.applications import Starlette |
14 | | -from starlette.routing import Mount, Route # Import Mount |
15 | | - |
16 | | -from eval_protocol.mcp_agent.config import AppConfig |
17 | | -from eval_protocol.mcp_agent.intermediary_server import RewardKitIntermediaryServer |
18 | | - |
19 | | -logger = logging.getLogger(__name__) |
20 | | - |
21 | | -# Global server instance to be managed by signal handlers |
22 | | -# This will now be the Uvicorn server instance. |
23 | | -_uvicorn_server_instance_ref: Optional[uvicorn.Server] = None # Keep a global ref if needed for signals |
24 | | -# Keep a reference to our MCP server for lifespan management |
25 | | -_mcp_server_instance_ref: Optional[RewardKitIntermediaryServer] = None |
26 | | -# _session_manager_ref is not needed globally if lifespan_wrapper handles it. |
27 | | - |
28 | | - |
29 | | -# Custom app_lifespan is no longer needed if StreamableHTTPSessionManager.lifespan_wrapper is used. |
30 | | - |
31 | | - |
32 | | -async def main_async(config_path: str, host: str, port: int): |
33 | | - """ |
34 | | - Asynchronous main function to load config, set up the ASGI application, |
35 | | - and run it with Uvicorn. |
36 | | - """ |
37 | | - global _uvicorn_server_instance_ref, _mcp_server_instance_ref # _session_manager_ref removed from globals |
38 | | - try: |
39 | | - with open(config_path, "r") as f: |
40 | | - raw_config = yaml.safe_load(f) |
41 | | - app_config = AppConfig(**raw_config) |
42 | | - except FileNotFoundError: |
43 | | - logger.error(f"Configuration file not found: {config_path}") |
44 | | - return |
45 | | - except yaml.YAMLError as e: |
46 | | - logger.error(f"Error parsing YAML configuration file {config_path}: {e}") |
47 | | - return |
48 | | - except Exception as e: |
49 | | - logger.error(f"Error loading or validating AppConfig from {config_path}: {e}") |
50 | | - return |
51 | | - |
52 | | - # Configure logging early |
53 | | - server_root_log_level_str = app_config.log_level.upper() |
54 | | - server_root_log_level = getattr(logging, server_root_log_level_str, logging.INFO) |
55 | | - |
56 | | - logging.basicConfig( |
57 | | - level=server_root_log_level, # Root logger for the server process |
58 | | - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", |
59 | | - datefmt="%Y-%m-%d %H:%M:%S", # Added datefmt for consistency |
60 | | - ) |
61 | | - logger.info(f"Configuration loaded from {config_path}. Server root log level set to {server_root_log_level_str}.") |
62 | | - |
63 | | - # Ensure eval_protocol.mcp_agent namespace respects this level |
64 | | - rk_mcp_agent_logger = logging.getLogger("eval_protocol.mcp_agent") |
65 | | - rk_mcp_agent_logger.setLevel(server_root_log_level) |
66 | | - |
67 | | - # Be very explicit for the intermediary_server logger as well |
68 | | - intermediary_server_logger = logging.getLogger("eval_protocol.mcp_agent.intermediary_server") |
69 | | - intermediary_server_logger.setLevel(server_root_log_level) |
70 | | - # Also ensure its handlers respect this level |
71 | | - for handler in intermediary_server_logger.handlers: |
72 | | - handler.setLevel(server_root_log_level) |
73 | | - # If it's propagating to the 'eval_protocol.mcp_agent' parent, ensure that parent's handlers are also correct. |
74 | | - # The parent rk_mcp_agent_logger already had its level set. |
75 | | - |
76 | | - # Quiet down other noisy libraries for the server unless server itself is in DEBUG mode |
77 | | - if server_root_log_level > logging.DEBUG: # e.g. if INFO or WARNING |
78 | | - libraries_to_quiet = [ |
79 | | - "httpx", |
80 | | - "mcp", |
81 | | - "uvicorn", |
82 | | - "starlette", |
83 | | - "asyncio", |
84 | | - "hpack", |
85 | | - "httpcore", |
86 | | - ] |
87 | | - for lib_name in libraries_to_quiet: |
88 | | - logging.getLogger(lib_name).setLevel(logging.WARNING) |
89 | | - |
90 | | - logger.info( |
91 | | - f"Log level for 'eval_protocol.mcp_agent' namespace set to {logging.getLevelName(logging.getLogger('eval_protocol.mcp_agent').getEffectiveLevel())}" |
92 | | - ) |
93 | | - |
94 | | - # 1. Instantiate RewardKitIntermediaryServer |
95 | | - _mcp_server_instance_ref = RewardKitIntermediaryServer( |
96 | | - app_config=app_config |
97 | | - ) # Store globally for lifespan_wrapper |
98 | | - |
99 | | - # 2. Instantiate StreamableHTTPSessionManager |
100 | | - # Pass the internal _mcp_server (the MCPServer instance) from our FastMCP subclass |
101 | | - if _mcp_server_instance_ref is None: |
102 | | - logger.error("Failed to initialize RewardKitIntermediaryServer") |
103 | | - return |
104 | | - |
105 | | - session_manager = StreamableHTTPSessionManager( |
106 | | - app=_mcp_server_instance_ref._mcp_server, # type: ignore[attr-defined] |
107 | | - event_store=None, |
108 | | - json_response=True, # Changed to True |
109 | | - ) |
110 | | - |
111 | | - # 3. Create Starlette app, using session_manager.lifespan_wrapper |
112 | | - # This wrapper should handle the startup/shutdown of both the session_manager's task group |
113 | | - # and the underlying _mcp_server_instance_ref. |
114 | | - routes = [ |
115 | | - Mount("/mcp", app=session_manager.handle_request), |
116 | | - ] |
117 | | - |
118 | | - # The lifespan_wrapper approach was incorrect as the method doesn't exist. |
119 | | - # We will now use a custom lifespan for the MCPServer and run Uvicorn |
120 | | - # within the context of session_manager.run() if it's an async context manager. |
121 | | - |
122 | | - @asynccontextmanager |
123 | | - async def mcp_server_lifespan_only(app_for_lifespan: Starlette): |
124 | | - # This lifespan only manages the _mcp_server_instance_ref |
125 | | - if _mcp_server_instance_ref: |
126 | | - logger.info("MCP Server Lifespan: Starting up RewardKitIntermediaryServer...") |
127 | | - await _mcp_server_instance_ref.startup() |
128 | | - logger.info("MCP Server Lifespan: RewardKitIntermediaryServer startup complete.") |
129 | | - yield |
130 | | - if _mcp_server_instance_ref: |
131 | | - logger.info("MCP Server Lifespan: Shutting down RewardKitIntermediaryServer...") |
132 | | - await _mcp_server_instance_ref.shutdown() |
133 | | - logger.info("MCP Server Lifespan: RewardKitIntermediaryServer shutdown complete.") |
134 | | - |
135 | | - routes = [ |
136 | | - Mount("/mcp", app=session_manager.handle_request), |
137 | | - ] |
138 | | - starlette_app = Starlette(routes=routes, lifespan=mcp_server_lifespan_only) |
139 | | - |
140 | | - # 4. Configure Uvicorn |
141 | | - config = uvicorn.Config( |
142 | | - app=starlette_app, # Starlette app with its own lifespan for MCPServer |
143 | | - host=host, |
144 | | - port=port, |
145 | | - log_level=app_config.log_level.lower(), |
146 | | - log_config=None, # Prevent Uvicorn from overriding our basicConfig for app loggers |
147 | | - ) |
148 | | - uvicorn_server = uvicorn.Server(config) |
149 | | - _uvicorn_server_instance_ref = uvicorn_server |
150 | | - |
151 | | - logger.info(f"Starting RewardKit Intermediary MCP Server on {host}:{port}/mcp.") |
152 | | - |
153 | | - try: |
154 | | - if hasattr(session_manager, "run"): |
155 | | - # Call run() to get the potential context manager |
156 | | - sm_context_manager = session_manager.run() |
157 | | - if hasattr(sm_context_manager, "__aenter__") and hasattr(sm_context_manager, "__aexit__"): |
158 | | - logger.info( |
159 | | - "Attempting to run Uvicorn server within context returned by StreamableHTTPSessionManager.run()..." |
160 | | - ) |
161 | | - async with sm_context_manager: # type: ignore |
162 | | - logger.info("Context from StreamableHTTPSessionManager.run() entered. Serving Uvicorn...") |
163 | | - await uvicorn_server.serve() |
164 | | - else: |
165 | | - logger.error( |
166 | | - "Object returned by StreamableHTTPSessionManager.run() is not an async context manager. Falling back to direct Uvicorn serve." |
167 | | - ) |
168 | | - await uvicorn_server.serve() |
169 | | - else: |
170 | | - logger.error( |
171 | | - "StreamableHTTPSessionManager does not have a 'run' method. Falling back to direct Uvicorn serve." |
172 | | - ) |
173 | | - await uvicorn_server.serve() |
174 | | - |
175 | | - except asyncio.CancelledError: |
176 | | - logger.info("Server operation cancelled (main_async level).") |
177 | | - except Exception as e: |
178 | | - logger.error( |
179 | | - f"An error occurred during server operation (main_async level): {e}", |
180 | | - exc_info=True, |
181 | | - ) |
182 | | - finally: |
183 | | - logger.info("Uvicorn server has shut down (main_async finally).") |
184 | | - |
185 | | - |
186 | | -# Signal handling is now primarily managed by Uvicorn. |
187 | | -# If we needed custom logic *before* Uvicorn handles signals, it would be more complex. |
188 | | -# For now, relying on Uvicorn's graceful shutdown which triggers the ASGI lifespan. |
189 | 2 |
|
190 | 3 |
|
191 | 4 | @click.command() |
192 | 5 | @click.option( |
193 | 6 | "--config", |
194 | 7 | "config_path", |
195 | 8 | default="mcp_agent_config.yaml", |
196 | | - help="Path to the YAML configuration file for the MCP agent server.", |
197 | | - type=click.Path(exists=True, dir_okay=False), |
| 9 | + help="(deprecated) path to MCP agent config", |
198 | 10 | ) |
199 | | -@click.option("--host", default="0.0.0.0", help="Host for the server to listen on.") |
200 | | -@click.option("--port", default=8001, type=int, help="Port for the server to listen on.") |
| 11 | +@click.option("--host", default="0.0.0.0") |
| 12 | +@click.option("--port", default=8001, type=int) |
201 | 13 | def main_cli(config_path: str, host: str, port: int): |
202 | | - """ |
203 | | - CLI entry point to run the RewardKit Intermediary MCP Server using Uvicorn. |
204 | | - """ |
205 | | - try: |
206 | | - asyncio.run(main_async(config_path, host, port)) |
207 | | - except KeyboardInterrupt: # This will be caught by Uvicorn first usually |
208 | | - logger.info("CLI interrupted by KeyboardInterrupt. Uvicorn should handle shutdown.") |
209 | | - finally: |
210 | | - logger.info("MCP Agent Server CLI finished.") |
| 14 | + click.echo("eval_protocol.mcp_agent.main is deprecated and disabled.") |
211 | 15 |
|
212 | 16 |
|
213 | 17 | if __name__ == "__main__": |
|
0 commit comments