mcp-mesh 0.7.21__py3-none-any.whl → 0.8.0__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.
Files changed (124) hide show
  1. _mcp_mesh/__init__.py +1 -1
  2. _mcp_mesh/engine/dependency_injector.py +13 -15
  3. _mcp_mesh/engine/http_wrapper.py +69 -10
  4. _mcp_mesh/engine/mesh_llm_agent.py +29 -10
  5. _mcp_mesh/engine/mesh_llm_agent_injector.py +77 -41
  6. _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
  7. _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
  8. _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
  9. _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
  10. _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
  11. _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
  12. _mcp_mesh/engine/response_parser.py +61 -15
  13. _mcp_mesh/engine/signature_analyzer.py +58 -68
  14. _mcp_mesh/engine/unified_mcp_proxy.py +19 -35
  15. _mcp_mesh/pipeline/__init__.py +9 -20
  16. _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
  17. _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
  18. _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +429 -0
  19. _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
  20. _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
  21. _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
  22. _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
  23. _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
  24. _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
  25. _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +710 -0
  26. _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
  27. _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
  28. _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +31 -8
  29. _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
  30. _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +23 -11
  31. _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
  32. _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
  33. _mcp_mesh/reload.py +1 -3
  34. _mcp_mesh/shared/__init__.py +2 -8
  35. _mcp_mesh/shared/config_resolver.py +124 -80
  36. _mcp_mesh/shared/defaults.py +89 -14
  37. _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
  38. _mcp_mesh/shared/host_resolver.py +8 -46
  39. _mcp_mesh/shared/server_discovery.py +115 -86
  40. _mcp_mesh/shared/simple_shutdown.py +44 -86
  41. _mcp_mesh/tracing/execution_tracer.py +2 -6
  42. _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
  43. _mcp_mesh/tracing/trace_context_helper.py +3 -13
  44. _mcp_mesh/tracing/utils.py +29 -15
  45. _mcp_mesh/utils/fastmcp_schema_extractor.py +5 -4
  46. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/METADATA +7 -5
  47. mcp_mesh-0.8.0.dist-info/RECORD +85 -0
  48. mesh/__init__.py +12 -1
  49. mesh/decorators.py +248 -33
  50. mesh/helpers.py +52 -0
  51. mesh/types.py +40 -13
  52. _mcp_mesh/generated/.openapi-generator/FILES +0 -50
  53. _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
  54. _mcp_mesh/generated/.openapi-generator-ignore +0 -15
  55. _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
  56. _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
  57. _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
  58. _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
  59. _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
  60. _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
  61. _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
  62. _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
  63. _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
  64. _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
  65. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
  66. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
  67. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
  68. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
  69. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
  70. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
  71. _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
  72. _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
  73. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
  74. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
  75. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
  76. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
  77. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
  78. _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
  79. _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
  80. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
  81. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
  82. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
  83. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
  84. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
  85. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
  86. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
  87. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
  88. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
  89. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
  90. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
  91. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
  92. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
  93. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
  94. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
  95. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
  96. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
  97. _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
  98. _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
  99. _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
  100. _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
  101. _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
  102. _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
  103. _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  104. _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
  105. _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
  106. _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
  107. _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
  108. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
  109. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
  110. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
  111. _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
  112. _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
  113. _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
  114. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
  115. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
  116. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
  117. _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
  118. _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
  119. _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
  120. _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
  121. _mcp_mesh/shared/registry_client_wrapper.py +0 -515
  122. mcp_mesh-0.7.21.dist-info/RECORD +0 -152
  123. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/WHEEL +0 -0
  124. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,429 @@
1
+ """
2
+ Rust-backed heartbeat implementation for API services.
3
+
4
+ Replaces the Python API heartbeat pipeline with the Rust core runtime.
5
+ The Rust core handles:
6
+ - Registry communication (HEAD/POST heartbeats)
7
+ - Topology change detection
8
+ - Event emission
9
+
10
+ Python handles:
11
+ - DI updates when topology changes (route wrapper updates)
12
+ - FastAPI app health status
13
+ """
14
+
15
+ import asyncio
16
+ import json
17
+ import logging
18
+ from typing import Any, Optional
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Lazy import to avoid ImportError if Rust core not built
23
+ _rust_core = None
24
+
25
+
26
+ def _get_rust_core():
27
+ """Lazy import of Rust core module."""
28
+ global _rust_core
29
+ if _rust_core is None:
30
+ try:
31
+ import mcp_mesh_core
32
+
33
+ _rust_core = mcp_mesh_core
34
+ logger.debug("Rust core module loaded successfully for API heartbeat")
35
+ except ImportError as e:
36
+ logger.warning(f"Rust core not available for API heartbeat: {e}")
37
+ raise
38
+ return _rust_core
39
+
40
+
41
+ def _build_api_agent_spec(context: dict[str, Any], service_id: str = None) -> Any:
42
+ """
43
+ Build AgentSpec from API service context.
44
+
45
+ Converts the API service metadata and route wrappers into a Rust AgentSpec.
46
+ For API services:
47
+ - function_name = METHOD:path (e.g., "GET:/api/v1/data")
48
+ - capability = "" (API routes don't provide capabilities, they consume them)
49
+ - dependencies = list of capabilities the route depends on
50
+
51
+ Args:
52
+ context: Pipeline context containing display_config, agent_config, etc.
53
+ service_id: Service ID from heartbeat config (passed explicitly)
54
+ """
55
+ core = _get_rust_core()
56
+
57
+ # Get service ID - prefer explicit parameter, fallback to context
58
+ if not service_id:
59
+ service_id = context.get("service_id") or context.get(
60
+ "agent_id", "unknown-api-service"
61
+ )
62
+ display_config = context.get("display_config", {})
63
+ agent_config = context.get("agent_config", {})
64
+
65
+ # Get registry URL
66
+ from ...shared.config_resolver import get_config_value
67
+
68
+ # Default is handled by Rust core
69
+ registry_url = get_config_value(
70
+ "MCP_MESH_REGISTRY_URL",
71
+ override=agent_config.get("registry_url"),
72
+ )
73
+
74
+ # Get heartbeat interval
75
+ from ...shared.defaults import MeshDefaults
76
+
77
+ heartbeat_interval = int(
78
+ get_config_value(
79
+ "MCP_MESH_HEALTH_INTERVAL",
80
+ override=agent_config.get("health_interval"),
81
+ default=MeshDefaults.HEALTH_INTERVAL,
82
+ )
83
+ )
84
+
85
+ # Get HTTP config from display_config
86
+ http_host = display_config.get("display_host", "127.0.0.1")
87
+ http_port = display_config.get("display_port", 8080)
88
+ namespace = agent_config.get("namespace", "default")
89
+ version = agent_config.get("version", "1.0.0")
90
+
91
+ # Build tool specs from route wrappers
92
+ from ...engine.decorator_registry import DecoratorRegistry
93
+
94
+ tools = []
95
+ route_wrappers = DecoratorRegistry.get_all_route_wrappers()
96
+
97
+ for route_id, route_info in route_wrappers.items():
98
+ dependencies = route_info.get("dependencies", [])
99
+
100
+ # Only include routes with dependencies (routes without deps don't need registry)
101
+ if not dependencies:
102
+ continue
103
+
104
+ # Build dependency specs
105
+ deps = []
106
+ for dep_cap in dependencies:
107
+ # Tags must be serialized to JSON string (Rust core expects string, not list)
108
+ dep_spec = core.DependencySpec(
109
+ capability=dep_cap,
110
+ tags=json.dumps([]),
111
+ version=None,
112
+ )
113
+ deps.append(dep_spec)
114
+
115
+ # Create ToolSpec for this route
116
+ # For API routes: function_name is the route_id (METHOD:path)
117
+ # capability is empty since routes consume, not provide capabilities
118
+ tool_spec = core.ToolSpec(
119
+ function_name=route_id, # e.g., "GET:/api/v1/benchmark-services"
120
+ capability="", # API routes don't provide capabilities
121
+ version="1.0.0",
122
+ description="",
123
+ tags=[],
124
+ dependencies=deps if deps else None,
125
+ input_schema=None,
126
+ llm_filter=None,
127
+ llm_provider=None,
128
+ )
129
+ tools.append(tool_spec)
130
+
131
+ # Create AgentSpec
132
+ spec = core.AgentSpec(
133
+ name=service_id,
134
+ registry_url=registry_url,
135
+ version=version,
136
+ description="",
137
+ http_port=http_port,
138
+ http_host=http_host,
139
+ namespace=namespace,
140
+ agent_type="api", # API services only consume capabilities, not provide them
141
+ tools=tools if tools else None,
142
+ llm_agents=None, # API services don't have LLM agents
143
+ heartbeat_interval=heartbeat_interval,
144
+ )
145
+
146
+ logger.info(
147
+ f"Built API AgentSpec: name={service_id}, routes_with_deps={len(tools)}, "
148
+ f"registry={registry_url}"
149
+ )
150
+
151
+ return spec
152
+
153
+
154
+ async def _handle_api_mesh_event(event: Any, context: dict[str, Any]) -> None:
155
+ """
156
+ Handle a mesh event from the Rust core for API services.
157
+
158
+ Dispatches to appropriate handler based on event type.
159
+ """
160
+ event_type = event.event_type
161
+
162
+ if event_type == "agent_registered":
163
+ logger.info(f"API service registered with ID: {event.agent_id}")
164
+
165
+ elif event_type == "registration_failed":
166
+ logger.error(f"API service registration failed: {event.error}")
167
+
168
+ elif event_type == "dependency_available":
169
+ await _handle_api_dependency_change(
170
+ capability=event.capability,
171
+ endpoint=event.endpoint,
172
+ function_name=event.function_name,
173
+ agent_id=event.agent_id,
174
+ available=True,
175
+ context=context,
176
+ )
177
+
178
+ elif event_type == "dependency_changed":
179
+ await _handle_api_dependency_change(
180
+ capability=event.capability,
181
+ endpoint=event.endpoint,
182
+ function_name=event.function_name,
183
+ agent_id=event.agent_id,
184
+ available=True,
185
+ context=context,
186
+ )
187
+
188
+ elif event_type == "dependency_unavailable":
189
+ await _handle_api_dependency_change(
190
+ capability=event.capability,
191
+ endpoint=None,
192
+ function_name=None,
193
+ agent_id=None,
194
+ available=False,
195
+ context=context,
196
+ )
197
+
198
+ elif event_type == "llm_tools_updated":
199
+ # API services typically don't use LLM tools, but handle gracefully
200
+ logger.debug(f"LLM tools update for API service (ignored): {event.function_id}")
201
+
202
+ elif event_type == "health_check_due":
203
+ logger.debug("Health check due for API service (not implemented yet)")
204
+
205
+ elif event_type == "registry_disconnected":
206
+ logger.warning(f"Registry disconnected for API service: {event.reason}")
207
+
208
+ elif event_type == "shutdown":
209
+ logger.info("Rust core shutdown event received for API service")
210
+
211
+ else:
212
+ logger.debug(f"Unhandled event type for API service: {event_type}")
213
+
214
+
215
+ async def _handle_api_dependency_change(
216
+ capability: str,
217
+ endpoint: Optional[str],
218
+ function_name: Optional[str],
219
+ agent_id: Optional[str],
220
+ available: bool,
221
+ context: dict[str, Any],
222
+ ) -> None:
223
+ """
224
+ Handle dependency availability change for API services.
225
+
226
+ Updates route wrappers with new/changed/removed dependencies.
227
+ API services use route wrappers which have direct _mesh_update_dependency methods.
228
+ """
229
+ logger.info(
230
+ f"API dependency change: {capability} -> "
231
+ f"{'available' if available else 'unavailable'} "
232
+ f"at {endpoint}/{function_name}"
233
+ )
234
+
235
+ from ...engine.decorator_registry import DecoratorRegistry
236
+ from ...engine.unified_mcp_proxy import EnhancedUnifiedMCPProxy
237
+
238
+ route_wrappers = DecoratorRegistry.get_all_route_wrappers()
239
+
240
+ if not available:
241
+ # Dependency became unavailable - clear it from all route wrappers
242
+ for route_id, route_info in route_wrappers.items():
243
+ dependencies = route_info.get("dependencies", [])
244
+ wrapper = route_info.get("wrapper")
245
+
246
+ if not wrapper or not hasattr(wrapper, "_mesh_update_dependency"):
247
+ continue
248
+
249
+ # Find which dependency index(es) match this capability
250
+ for dep_index, dep_cap in enumerate(dependencies):
251
+ if dep_cap == capability:
252
+ # Set to None to indicate unavailable
253
+ wrapper._mesh_update_dependency(dep_index, None)
254
+ logger.info(
255
+ f"Cleared dependency '{capability}' at index {dep_index} "
256
+ f"for route '{route_id}'"
257
+ )
258
+ return
259
+
260
+ # Dependency is available - update all route wrappers that need it
261
+ for route_id, route_info in route_wrappers.items():
262
+ dependencies = route_info.get("dependencies", [])
263
+ wrapper = route_info.get("wrapper")
264
+
265
+ if not wrapper or not hasattr(wrapper, "_mesh_update_dependency"):
266
+ continue
267
+
268
+ # Find which dependency index(es) match this capability
269
+ for dep_index, dep_cap in enumerate(dependencies):
270
+ if dep_cap == capability:
271
+ # Check for self-dependency (rare for API services but handle it)
272
+ current_service_id = context.get("service_id") or context.get(
273
+ "agent_id"
274
+ )
275
+ if not current_service_id:
276
+ # Use config resolver for consistent env var handling
277
+ from ...shared.config_resolver import get_config_value
278
+
279
+ current_service_id = get_config_value("MCP_MESH_AGENT_ID")
280
+
281
+ is_self_dependency = (
282
+ current_service_id and agent_id and current_service_id == agent_id
283
+ )
284
+
285
+ if is_self_dependency:
286
+ # Self-dependency for API services - use SelfDependencyProxy
287
+ from ...engine.self_dependency_proxy import SelfDependencyProxy
288
+
289
+ # For API services, try to find the function in mesh tools
290
+ mesh_tools = DecoratorRegistry.get_mesh_tools()
291
+ wrapper_func = mesh_tools.get(function_name)
292
+
293
+ if wrapper_func:
294
+ proxy = SelfDependencyProxy(
295
+ wrapper_func.function, function_name
296
+ )
297
+ logger.debug(
298
+ f"Created SelfDependencyProxy for API route '{route_id}' "
299
+ f"dependency '{capability}'"
300
+ )
301
+ else:
302
+ # Fallback to HTTP proxy
303
+ proxy = EnhancedUnifiedMCPProxy(endpoint, function_name)
304
+ logger.debug(
305
+ f"Created EnhancedUnifiedMCPProxy (fallback) for API route "
306
+ f"'{route_id}' dependency '{capability}'"
307
+ )
308
+ else:
309
+ # Cross-service dependency - create HTTP proxy
310
+ proxy = EnhancedUnifiedMCPProxy(endpoint, function_name)
311
+ logger.debug(
312
+ f"Created EnhancedUnifiedMCPProxy for API route '{route_id}' "
313
+ f"dependency '{capability}' -> {endpoint}"
314
+ )
315
+
316
+ # Update the route wrapper
317
+ wrapper._mesh_update_dependency(dep_index, proxy)
318
+ logger.info(
319
+ f"Updated dependency '{capability}' at index {dep_index} "
320
+ f"for route '{route_id}' -> {endpoint}/{function_name}"
321
+ )
322
+
323
+
324
+ async def rust_api_heartbeat_task(heartbeat_config: dict[str, Any]) -> None:
325
+ """
326
+ Rust-backed heartbeat task for API services that runs in FastAPI lifespan.
327
+
328
+ This is a drop-in replacement for api_heartbeat_lifespan_task.
329
+ Instead of running Python heartbeat pipeline, it starts the Rust core
330
+ and listens for events.
331
+
332
+ Args:
333
+ heartbeat_config: Configuration containing service_id, interval, context
334
+ """
335
+ service_id = heartbeat_config.get("service_id", "unknown-api-service")
336
+ context = heartbeat_config.get("context", {})
337
+ standalone_mode = heartbeat_config.get("standalone_mode", False)
338
+
339
+ if standalone_mode:
340
+ logger.info(
341
+ f"Rust API heartbeat in standalone mode for service '{service_id}' "
342
+ "(no registry communication)"
343
+ )
344
+ return
345
+
346
+ try:
347
+ core = _get_rust_core()
348
+ except ImportError as e:
349
+ logger.error(
350
+ f"Rust core not available for API service '{service_id}': {e}. "
351
+ "The mcp_mesh_core module must be built and installed."
352
+ )
353
+ raise RuntimeError(
354
+ f"Rust core (mcp_mesh_core) is required but not available: {e}"
355
+ ) from e
356
+
357
+ logger.info(f"Starting Rust-backed heartbeat for API service '{service_id}'")
358
+
359
+ handle = None
360
+ try:
361
+ # Build AgentSpec from API service context, passing service_id explicitly
362
+ spec = _build_api_agent_spec(context, service_id=service_id)
363
+
364
+ # Start Rust core runtime
365
+ handle = core.start_agent(spec)
366
+ logger.info(f"Rust core started for API service '{service_id}'")
367
+
368
+ # Event loop - process events from Rust core
369
+ while True:
370
+ # Check for Python shutdown signal
371
+ try:
372
+ from ...shared.simple_shutdown import should_stop_heartbeat
373
+
374
+ if should_stop_heartbeat():
375
+ logger.info(
376
+ f"Stopping Rust API heartbeat for service '{service_id}' due to shutdown"
377
+ )
378
+ handle.shutdown()
379
+ break
380
+ except ImportError:
381
+ pass
382
+
383
+ try:
384
+ # Wait for next event from Rust core with timeout
385
+ # Timeout allows periodic shutdown checks
386
+ try:
387
+ event = await asyncio.wait_for(handle.next_event(), timeout=1.0)
388
+ except TimeoutError:
389
+ # No event in 1 second, loop back to check shutdown signal
390
+ continue
391
+
392
+ if event.event_type == "shutdown":
393
+ logger.info(f"Rust core shutdown for API service '{service_id}'")
394
+ break
395
+
396
+ # Handle the event
397
+ await _handle_api_mesh_event(event, context)
398
+
399
+ except Exception as e:
400
+ logger.error(f"Error handling Rust event for API service: {e}")
401
+ # Continue processing events
402
+
403
+ except asyncio.CancelledError:
404
+ logger.info(f"Rust API heartbeat task cancelled for service '{service_id}'")
405
+ raise
406
+ except Exception as e:
407
+ logger.error(f"Rust API heartbeat failed for service '{service_id}': {e}")
408
+ raise
409
+ finally:
410
+ # Always ensure graceful shutdown of Rust core to prevent daemon thread issues
411
+ # This is critical: without shutdown(), Rust background threads may try to
412
+ # write to stdout via tracing after Python's stdout is finalized
413
+ if handle is not None:
414
+ try:
415
+ handle.shutdown()
416
+ # Give Rust core a moment to clean up before Python exits
417
+ # Use time.sleep as fallback if asyncio is shutting down
418
+ try:
419
+ await asyncio.sleep(0.2)
420
+ except (asyncio.CancelledError, RuntimeError):
421
+ # Event loop might be shutting down, use blocking sleep
422
+ import time
423
+
424
+ time.sleep(0.2)
425
+ logger.debug(
426
+ f"Rust core shutdown complete for API service '{service_id}'"
427
+ )
428
+ except Exception as e:
429
+ logger.warning(f"Error during Rust core shutdown for API service: {e}")
@@ -43,22 +43,20 @@ class APIPipeline(MeshPipeline):
43
43
  """Setup the API pipeline steps."""
44
44
  # Essential API integration steps
45
45
  steps = [
46
- RouteCollectionStep(), # Collect @mesh.route decorators
47
- FastAPIAppDiscoveryStep(), # Find user's FastAPI app instances
48
- RouteIntegrationStep(), # Apply dependency injection to routes
49
- TracingMiddlewareIntegrationStep(), # Add tracing middleware to FastAPI apps
50
- APIServerSetupStep(), # Prepare service registration metadata
46
+ RouteCollectionStep(), # Collect @mesh.route decorators
47
+ FastAPIAppDiscoveryStep(), # Find user's FastAPI app instances
48
+ RouteIntegrationStep(), # Apply dependency injection to routes
49
+ TracingMiddlewareIntegrationStep(), # Add tracing middleware to FastAPI apps
50
+ APIServerSetupStep(), # Prepare service registration metadata
51
51
  # Note: Heartbeat integration will be added in next phase
52
52
  # Note: User controls FastAPI server startup (uvicorn/gunicorn)
53
53
  ]
54
54
 
55
55
  self.add_steps(steps)
56
56
  self.logger.debug(f"API pipeline configured with {len(steps)} steps")
57
-
57
+
58
58
  # Log the pipeline strategy
59
59
  self.logger.info(
60
60
  f"🌐 [DEBUG] API Pipeline initialized: dependency injection for @mesh.route decorators"
61
61
  )
62
- self.logger.debug(
63
- f"📋 Pipeline steps: {[step.name for step in steps]}"
64
- )
62
+ self.logger.debug(f"📋 Pipeline steps: {[step.name for step in steps]}")