mcp-mesh 0.5.5__py3-none-any.whl → 0.5.7__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.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/dependency_injector.py +122 -57
- _mcp_mesh/engine/signature_analyzer.py +23 -14
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +107 -58
- _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +58 -30
- _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +13 -0
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +40 -4
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +4 -154
- _mcp_mesh/shared/simple_shutdown.py +217 -0
- {mcp_mesh-0.5.5.dist-info → mcp_mesh-0.5.7.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.5.dist-info → mcp_mesh-0.5.7.dist-info}/RECORD +14 -14
- mesh/decorators.py +29 -20
- _mcp_mesh/shared/graceful_shutdown_manager.py +0 -236
- {mcp_mesh-0.5.5.dist-info → mcp_mesh-0.5.7.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.5.dist-info → mcp_mesh-0.5.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Graceful Shutdown Manager for MCP Mesh agents.
|
|
3
|
-
|
|
4
|
-
This utility class manages graceful shutdown functionality, including signal handlers,
|
|
5
|
-
shutdown context management, and coordination with FastAPI lifecycle and registry.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import logging
|
|
10
|
-
import os
|
|
11
|
-
import signal
|
|
12
|
-
import sys
|
|
13
|
-
from typing import Any, Optional
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class GracefulShutdownManager:
|
|
19
|
-
"""Manages graceful shutdown for MCP Mesh agents."""
|
|
20
|
-
|
|
21
|
-
def __init__(self):
|
|
22
|
-
self._shutdown_requested: bool = False
|
|
23
|
-
self._shutdown_context: dict[str, Any] = {}
|
|
24
|
-
|
|
25
|
-
def set_shutdown_context(self, context: dict[str, Any]) -> None:
|
|
26
|
-
"""Set context for graceful shutdown (called from pipeline)."""
|
|
27
|
-
self._shutdown_context.update(context)
|
|
28
|
-
|
|
29
|
-
# Extract FastAPI app from context for coordinated shutdown
|
|
30
|
-
fastapi_app = context.get("fastapi_app")
|
|
31
|
-
if fastapi_app:
|
|
32
|
-
self._shutdown_context["fastapi_app"] = fastapi_app
|
|
33
|
-
logger.debug(
|
|
34
|
-
f"🔧 FastAPI app stored for coordinated shutdown: {type(fastapi_app)}"
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
logger.debug(
|
|
38
|
-
f"🔧 Shutdown context updated: agent_id={context.get('agent_id')}, registry_url={context.get('registry_url')}"
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
def install_signal_handlers(self) -> None:
|
|
42
|
-
"""Install signal handlers that set the shutdown flag."""
|
|
43
|
-
|
|
44
|
-
def shutdown_signal_handler(signum, frame):
|
|
45
|
-
"""Handle shutdown signals by setting the shutdown flag."""
|
|
46
|
-
logger.info(
|
|
47
|
-
f"🚨 SIGNAL HANDLER: Received signal {signum}, setting shutdown flag"
|
|
48
|
-
)
|
|
49
|
-
self._shutdown_requested = True
|
|
50
|
-
|
|
51
|
-
# Install handlers for common termination signals
|
|
52
|
-
signal.signal(signal.SIGTERM, shutdown_signal_handler)
|
|
53
|
-
signal.signal(signal.SIGINT, shutdown_signal_handler)
|
|
54
|
-
|
|
55
|
-
logger.info("🛡️ Signal handlers installed for graceful shutdown")
|
|
56
|
-
|
|
57
|
-
def is_shutdown_requested(self) -> bool:
|
|
58
|
-
"""Check if shutdown has been requested."""
|
|
59
|
-
return self._shutdown_requested
|
|
60
|
-
|
|
61
|
-
def perform_graceful_shutdown_from_main_thread(self) -> None:
|
|
62
|
-
"""Perform graceful shutdown from main thread (non-async context)."""
|
|
63
|
-
try:
|
|
64
|
-
# Check if we have FastAPI app for coordinated shutdown
|
|
65
|
-
fastapi_app = self._shutdown_context.get("fastapi_app")
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
fastapi_app
|
|
69
|
-
and hasattr(fastapi_app, "state")
|
|
70
|
-
and hasattr(fastapi_app.state, "shutdown_step")
|
|
71
|
-
):
|
|
72
|
-
# Use FastAPI lifespan shutdown mechanism
|
|
73
|
-
logger.info("🚨 Triggering coordinated FastAPI shutdown...")
|
|
74
|
-
shutdown_step = fastapi_app.state.shutdown_step
|
|
75
|
-
|
|
76
|
-
# Create minimal context and call the existing graceful shutdown
|
|
77
|
-
loop = asyncio.new_event_loop()
|
|
78
|
-
asyncio.set_event_loop(loop)
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
loop.run_until_complete(
|
|
82
|
-
shutdown_step._graceful_shutdown(fastapi_app)
|
|
83
|
-
)
|
|
84
|
-
logger.info("✅ FastAPI coordinated shutdown completed")
|
|
85
|
-
return
|
|
86
|
-
finally:
|
|
87
|
-
loop.close()
|
|
88
|
-
|
|
89
|
-
# Fallback: Direct registry call if no FastAPI coordination available
|
|
90
|
-
logger.info("🚨 Using fallback direct registry shutdown...")
|
|
91
|
-
|
|
92
|
-
# Get registry_url from context or environment (with default)
|
|
93
|
-
registry_url = self._shutdown_context.get("registry_url")
|
|
94
|
-
if not registry_url:
|
|
95
|
-
registry_url = os.getenv(
|
|
96
|
-
"MCP_MESH_REGISTRY_URL", "http://localhost:8000"
|
|
97
|
-
)
|
|
98
|
-
logger.debug(
|
|
99
|
-
f"🔧 Using registry URL from environment/default: {registry_url}"
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
# Get agent_id from context or generate the same one used for registration
|
|
103
|
-
agent_id = self._shutdown_context.get("agent_id")
|
|
104
|
-
if not agent_id:
|
|
105
|
-
agent_id = self._get_or_create_agent_id()
|
|
106
|
-
logger.debug(f"🔧 Using agent ID from shared state: {agent_id}")
|
|
107
|
-
|
|
108
|
-
if not registry_url or not agent_id:
|
|
109
|
-
logger.warning(
|
|
110
|
-
f"⚠️ Cannot perform graceful shutdown: missing registry_url={registry_url} or agent_id={agent_id}"
|
|
111
|
-
)
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
# Create registry client and perform shutdown synchronously
|
|
115
|
-
loop = asyncio.new_event_loop()
|
|
116
|
-
asyncio.set_event_loop(loop)
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
loop.run_until_complete(
|
|
120
|
-
self._perform_graceful_shutdown_async(registry_url, agent_id)
|
|
121
|
-
)
|
|
122
|
-
finally:
|
|
123
|
-
loop.close()
|
|
124
|
-
|
|
125
|
-
except Exception as e:
|
|
126
|
-
logger.error(f"❌ Graceful shutdown error: {e}")
|
|
127
|
-
|
|
128
|
-
async def _perform_graceful_shutdown_async(
|
|
129
|
-
self, registry_url: str, agent_id: str
|
|
130
|
-
) -> None:
|
|
131
|
-
"""Async graceful shutdown implementation."""
|
|
132
|
-
try:
|
|
133
|
-
# Create registry client for shutdown
|
|
134
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.api_client import (
|
|
135
|
-
ApiClient,
|
|
136
|
-
)
|
|
137
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.configuration import (
|
|
138
|
-
Configuration,
|
|
139
|
-
)
|
|
140
|
-
from _mcp_mesh.shared.registry_client_wrapper import RegistryClientWrapper
|
|
141
|
-
|
|
142
|
-
config = Configuration(host=registry_url)
|
|
143
|
-
api_client = ApiClient(configuration=config)
|
|
144
|
-
registry_wrapper = RegistryClientWrapper(api_client)
|
|
145
|
-
|
|
146
|
-
# Perform graceful unregistration (this sends DELETE /heartbeats)
|
|
147
|
-
success = await registry_wrapper.unregister_agent(agent_id)
|
|
148
|
-
if success:
|
|
149
|
-
logger.info(
|
|
150
|
-
f"✅ Agent '{agent_id}' successfully unregistered from registry"
|
|
151
|
-
)
|
|
152
|
-
else:
|
|
153
|
-
logger.warning(
|
|
154
|
-
f"⚠️ Failed to unregister agent '{agent_id}' from registry"
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
except Exception as e:
|
|
158
|
-
logger.error(f"❌ Graceful shutdown error: {e}")
|
|
159
|
-
|
|
160
|
-
def _get_or_create_agent_id(self) -> str:
|
|
161
|
-
"""Get agent ID using the existing decorator registry function."""
|
|
162
|
-
from mesh.decorators import _get_or_create_agent_id
|
|
163
|
-
|
|
164
|
-
return _get_or_create_agent_id()
|
|
165
|
-
|
|
166
|
-
def start_blocking_loop_with_shutdown_support(self, thread) -> None:
|
|
167
|
-
"""
|
|
168
|
-
Start the main thread blocking loop with graceful shutdown support.
|
|
169
|
-
|
|
170
|
-
This keeps the main thread alive while monitoring for shutdown signals
|
|
171
|
-
and maintaining the server thread.
|
|
172
|
-
"""
|
|
173
|
-
logger.info(
|
|
174
|
-
"🔒 MAIN THREAD: Blocking to keep alive (prevents threading shutdown state)"
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
# Install signal handlers
|
|
178
|
-
self.install_signal_handlers()
|
|
179
|
-
|
|
180
|
-
try:
|
|
181
|
-
while True:
|
|
182
|
-
# Check shutdown flag (set by signal handlers)
|
|
183
|
-
if self.is_shutdown_requested():
|
|
184
|
-
logger.info(
|
|
185
|
-
"🚨 MAIN THREAD: Shutdown requested, performing graceful cleanup..."
|
|
186
|
-
)
|
|
187
|
-
self.perform_graceful_shutdown_from_main_thread()
|
|
188
|
-
break
|
|
189
|
-
|
|
190
|
-
if thread.is_alive():
|
|
191
|
-
thread.join(timeout=1) # Check every second
|
|
192
|
-
else:
|
|
193
|
-
logger.warning("⚠️ Server thread died, exiting...")
|
|
194
|
-
break
|
|
195
|
-
except KeyboardInterrupt:
|
|
196
|
-
logger.info("👋 MAIN THREAD: Received interrupt, shutting down gracefully")
|
|
197
|
-
self.perform_graceful_shutdown_from_main_thread()
|
|
198
|
-
except Exception as e:
|
|
199
|
-
logger.error(f"❌ MAIN THREAD: Error in blocking loop: {e}")
|
|
200
|
-
|
|
201
|
-
logger.info("🏁 MAIN THREAD: Exiting blocking loop")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# Global instance for backward compatibility with existing decorators.py usage
|
|
205
|
-
_global_shutdown_manager = GracefulShutdownManager()
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def get_global_shutdown_manager() -> GracefulShutdownManager:
|
|
209
|
-
"""Get the global shutdown manager instance."""
|
|
210
|
-
return _global_shutdown_manager
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# Convenience functions for backward compatibility
|
|
214
|
-
def set_shutdown_context(context: dict[str, Any]) -> None:
|
|
215
|
-
"""Set context for graceful shutdown (global convenience function)."""
|
|
216
|
-
_global_shutdown_manager.set_shutdown_context(context)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def install_graceful_shutdown_handlers() -> None:
|
|
220
|
-
"""Install signal handlers that set the shutdown flag (global convenience function)."""
|
|
221
|
-
_global_shutdown_manager.install_signal_handlers()
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def is_shutdown_requested() -> bool:
|
|
225
|
-
"""Check if shutdown has been requested (global convenience function)."""
|
|
226
|
-
return _global_shutdown_manager.is_shutdown_requested()
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def perform_graceful_shutdown_from_main_thread() -> None:
|
|
230
|
-
"""Perform graceful shutdown from main thread (global convenience function)."""
|
|
231
|
-
_global_shutdown_manager.perform_graceful_shutdown_from_main_thread()
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def start_blocking_loop_with_shutdown_support(thread) -> None:
|
|
235
|
-
"""Start the main thread blocking loop with graceful shutdown support (global convenience function)."""
|
|
236
|
-
_global_shutdown_manager.start_blocking_loop_with_shutdown_support(thread)
|
|
File without changes
|
|
File without changes
|