mcp-mesh 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ """
2
+ Server discovery step for MCP pipeline.
3
+
4
+ This step discovers existing uvicorn servers that may have been started immediately
5
+ in @mesh.agent decorators to prevent Python interpreter shutdown.
6
+ """
7
+
8
+ import logging
9
+ from typing import Any, Dict, Optional
10
+
11
+ from ..shared import PipelineResult, PipelineStatus, PipelineStep
12
+ from ...shared.server_discovery import ServerDiscoveryUtil
13
+
14
+
15
+ class ServerDiscoveryStep(PipelineStep):
16
+ """
17
+ Discovers existing uvicorn servers that may be running.
18
+
19
+ This step checks if there's already a uvicorn server running on the target port,
20
+ which could happen when @mesh.agent(auto_run=True) starts an immediate uvicorn
21
+ server to prevent Python interpreter shutdown.
22
+ """
23
+
24
+ def __init__(self):
25
+ super().__init__(
26
+ name="server-discovery",
27
+ required=False, # Not required - pipeline can still start new server if none found
28
+ description="Discover existing uvicorn servers",
29
+ )
30
+
31
+ async def execute(self, context: dict[str, Any]) -> PipelineResult:
32
+ """Discover existing uvicorn servers."""
33
+ self.logger.debug("🔍 DISCOVERY: Checking for existing uvicorn servers...")
34
+
35
+ result = PipelineResult(message="Server discovery completed")
36
+
37
+ try:
38
+ # Get agent configuration from context
39
+ agent_config = context.get("agent_config", {})
40
+ target_port = agent_config.get("http_port", 8080)
41
+ target_host = agent_config.get("http_host", "0.0.0.0")
42
+
43
+ self.logger.info(f"🔍 DISCOVERY: Looking for immediate uvicorn server from DecoratorRegistry")
44
+
45
+ # Check DecoratorRegistry for immediate uvicorn server (much more reliable)
46
+ from ...engine.decorator_registry import DecoratorRegistry
47
+ existing_server = DecoratorRegistry.get_immediate_uvicorn_server()
48
+
49
+ # Debug: Show what we found
50
+ if existing_server:
51
+ server_status = existing_server.get("status", "unknown")
52
+ server_type = existing_server.get("type", "unknown")
53
+ self.logger.info(f"🔍 DISCOVERY: Found server - status='{server_status}', type='{server_type}'")
54
+ else:
55
+ self.logger.info(f"🔍 DISCOVERY: No immediate uvicorn server found in registry")
56
+
57
+ if existing_server:
58
+ # Found existing immediate uvicorn server
59
+ server_host = existing_server.get('host', 'unknown')
60
+ server_port = existing_server.get('port', 0)
61
+
62
+ result.add_context("existing_server", existing_server)
63
+ result.add_context("server_reuse", True)
64
+
65
+ # Get the FastAPI app directly from server info
66
+ existing_app = existing_server.get('app')
67
+ if existing_app:
68
+ app_info = {
69
+ 'instance': existing_app,
70
+ 'title': getattr(existing_app, 'title', 'MCP Mesh Agent (Starting)'),
71
+ 'version': getattr(existing_app, 'version', 'unknown'),
72
+ 'object_id': id(existing_app),
73
+ 'type': 'immediate_uvicorn'
74
+ }
75
+ result.add_context("existing_fastapi_app", app_info)
76
+ result.message = (
77
+ f"Found immediate uvicorn server on {server_host}:{server_port} "
78
+ f"with FastAPI app '{app_info.get('title', 'Unknown')}'"
79
+ )
80
+ self.logger.info(
81
+ f"✅ DISCOVERY: Found immediate uvicorn server on {server_host}:{server_port} "
82
+ f"with FastAPI app '{app_info.get('title', 'Unknown')}'"
83
+ )
84
+ else:
85
+ result.message = f"Found immediate uvicorn server on {server_host}:{server_port} (no FastAPI app reference)"
86
+ self.logger.warning(f"⚠️ DISCOVERY: Found immediate uvicorn server but no FastAPI app reference")
87
+
88
+ else:
89
+ # No existing server found
90
+ result.add_context("existing_server", None)
91
+ result.add_context("server_reuse", False)
92
+ result.message = f"No immediate uvicorn server found in DecoratorRegistry"
93
+ self.logger.info(f"🔍 DISCOVERY: No immediate uvicorn server found - pipeline will start new server")
94
+
95
+ # Only discover FastAPI apps if no immediate uvicorn server was found
96
+ if not existing_server:
97
+ self.logger.debug("🔍 DISCOVERY: No immediate uvicorn server found, discovering FastAPI apps via garbage collection")
98
+ fastapi_apps = ServerDiscoveryUtil.discover_fastapi_instances()
99
+ result.add_context("discovered_fastapi_apps", fastapi_apps)
100
+
101
+ if fastapi_apps:
102
+ app_count = len(fastapi_apps)
103
+ result.message += f" | Discovered {app_count} FastAPI app(s)"
104
+ self.logger.info(f"📦 DISCOVERY: Discovered {app_count} FastAPI application(s) for potential mounting")
105
+
106
+ # Log details about discovered apps
107
+ for app_id, app_info in fastapi_apps.items():
108
+ app_title = app_info.get("title", "Unknown")
109
+ route_count = len(app_info.get("routes", []))
110
+ self.logger.debug(f" 📦 App '{app_title}' ({app_id}): {route_count} routes")
111
+ else:
112
+ self.logger.debug("🔍 DISCOVERY: Using FastAPI app from immediate uvicorn server, skipping garbage collection discovery")
113
+
114
+ except Exception as e:
115
+ result.status = PipelineStatus.FAILED
116
+ result.message = f"Server discovery failed: {e}"
117
+ result.add_error(str(e))
118
+ self.logger.error(f"❌ DISCOVERY: Server discovery failed: {e}")
119
+
120
+ return result
121
+
122
+ def _find_associated_fastapi_app(self, server_info: Dict[str, Any]) -> Optional[Dict[str, Any]]:
123
+ """
124
+ Try to find the FastAPI app associated with the existing server.
125
+
126
+ Args:
127
+ server_info: Server information from discovery
128
+
129
+ Returns:
130
+ FastAPI app info if found, None otherwise
131
+ """
132
+ try:
133
+ # Check if server info already has an app
134
+ if 'app' in server_info:
135
+ app = server_info['app']
136
+ return {
137
+ 'instance': app,
138
+ 'title': getattr(app, 'title', 'Unknown'),
139
+ 'version': getattr(app, 'version', 'unknown'),
140
+ 'routes': ServerDiscoveryUtil._extract_route_info(app),
141
+ 'object_id': id(app),
142
+ }
143
+
144
+ # If not, discover all FastAPI apps and try to match
145
+ fastapi_apps = ServerDiscoveryUtil.discover_fastapi_instances()
146
+
147
+ # For immediate uvicorn servers, look for apps with specific titles
148
+ for app_id, app_info in fastapi_apps.items():
149
+ app_title = app_info.get('title', '')
150
+ if 'MCP Mesh Agent' in app_title and 'Starting' in app_title:
151
+ # This looks like our immediate uvicorn app
152
+ self.logger.debug(f"🔍 DISCOVERY: Found immediate uvicorn FastAPI app: {app_title}")
153
+ return app_info
154
+
155
+ # If no immediate uvicorn app found, return the first available app
156
+ if fastapi_apps:
157
+ first_app = next(iter(fastapi_apps.values()))
158
+ self.logger.debug(f"🔍 DISCOVERY: Using first available FastAPI app: {first_app.get('title', 'Unknown')}")
159
+ return first_app
160
+
161
+ except Exception as e:
162
+ self.logger.warning(f"Error finding associated FastAPI app: {e}")
163
+
164
+ return None