mcp-mesh 0.5.4__tar.gz → 0.5.6__tar.gz

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 (127) hide show
  1. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/PKG-INFO +1 -1
  2. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/__init__.py +5 -2
  3. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/decorator_registry.py +95 -0
  4. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/mcp_client_proxy.py +17 -7
  5. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/unified_mcp_proxy.py +43 -40
  6. mcp_mesh-0.5.6/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +139 -0
  7. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +4 -0
  8. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +13 -0
  9. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/__init__.py +2 -0
  10. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +306 -163
  11. mcp_mesh-0.5.6/_mcp_mesh/pipeline/mcp_startup/server_discovery.py +164 -0
  12. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +198 -160
  13. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +7 -4
  14. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/mesh_pipeline.py +4 -0
  15. mcp_mesh-0.5.6/_mcp_mesh/shared/server_discovery.py +312 -0
  16. mcp_mesh-0.5.6/_mcp_mesh/shared/simple_shutdown.py +217 -0
  17. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/mesh/decorators.py +303 -36
  18. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/pyproject.toml +4 -4
  19. mcp_mesh-0.5.4/_mcp_mesh/engine/threading_utils.py +0 -223
  20. mcp_mesh-0.5.4/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +0 -302
  21. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/.gitignore +0 -0
  22. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/LICENSE +0 -0
  23. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/README.md +0 -0
  24. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/__init__.py +0 -0
  25. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/async_mcp_client.py +0 -0
  26. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/dependency_injector.py +0 -0
  27. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/full_mcp_proxy.py +0 -0
  28. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/http_wrapper.py +0 -0
  29. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/self_dependency_proxy.py +0 -0
  30. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/session_aware_client.py +0 -0
  31. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/session_manager.py +0 -0
  32. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/signature_analyzer.py +0 -0
  33. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator/FILES +0 -0
  34. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator/VERSION +0 -0
  35. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator-ignore +0 -0
  36. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -0
  37. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -0
  38. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -0
  39. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -0
  40. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -0
  41. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -0
  42. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -0
  43. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -0
  44. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -0
  45. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -0
  46. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -0
  47. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -0
  48. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -0
  49. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -0
  50. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -0
  51. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -0
  52. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -0
  53. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -0
  54. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -0
  55. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -0
  56. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -0
  57. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -0
  58. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -0
  59. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -0
  60. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -0
  61. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -0
  62. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -0
  63. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -0
  64. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -0
  65. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -0
  66. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -0
  67. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -0
  68. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -0
  69. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -0
  70. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -0
  71. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -0
  72. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -0
  73. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -0
  74. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -0
  75. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  76. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -0
  77. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/__init__.py +0 -0
  78. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/__init__.py +0 -0
  79. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -0
  80. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -0
  81. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -0
  82. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -0
  83. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -0
  84. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -0
  85. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +0 -0
  86. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -0
  87. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/__init__.py +0 -0
  88. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/api_pipeline.py +0 -0
  89. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/api_server_setup.py +0 -0
  90. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/middleware_integration.py +0 -0
  91. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/route_collection.py +0 -0
  92. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/route_integration.py +0 -0
  93. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/__init__.py +0 -0
  94. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -0
  95. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -0
  96. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -0
  97. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -0
  98. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -0
  99. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/configuration.py +0 -0
  100. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/decorator_collection.py +0 -0
  101. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +0 -0
  102. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +0 -0
  103. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +0 -0
  104. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/__init__.py +0 -0
  105. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/base_step.py +0 -0
  106. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/pipeline_types.py +0 -0
  107. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/registry_connection.py +0 -0
  108. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/__init__.py +0 -0
  109. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/config_resolver.py +0 -0
  110. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/content_extractor.py +0 -0
  111. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/defaults.py +0 -0
  112. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/fast_heartbeat_status.py +0 -0
  113. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/fastapi_middleware_manager.py +0 -0
  114. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/host_resolver.py +0 -0
  115. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/logging_config.py +0 -0
  116. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/registry_client_wrapper.py +0 -0
  117. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/sse_parser.py +0 -0
  118. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/support_types.py +0 -0
  119. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/agent_context_helper.py +0 -0
  120. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/context.py +0 -0
  121. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/execution_tracer.py +0 -0
  122. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/fastapi_tracing_middleware.py +0 -0
  123. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/redis_metadata_publisher.py +0 -0
  124. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/trace_context_helper.py +0 -0
  125. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/utils.py +0 -0
  126. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/mesh/__init__.py +0 -0
  127. {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/mesh/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-mesh
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: Kubernetes-native platform for distributed MCP applications
5
5
  Project-URL: Homepage, https://github.com/dhyansraj/mcp-mesh
6
6
  Project-URL: Documentation, https://github.com/dhyansraj/mcp-mesh/tree/main/docs
@@ -31,7 +31,7 @@ from .engine.decorator_registry import (
31
31
  get_decorator_stats,
32
32
  )
33
33
 
34
- __version__ = "0.5.4"
34
+ __version__ = "0.5.6"
35
35
 
36
36
  # Store reference to runtime processor if initialized
37
37
  _runtime_processor = None
@@ -59,7 +59,10 @@ def initialize_runtime():
59
59
 
60
60
 
61
61
  # Auto-initialize runtime if enabled
62
- if os.getenv("MCP_MESH_ENABLED", "true").lower() == "true":
62
+ if (
63
+ os.getenv("MCP_MESH_ENABLED", "true").lower() == "true"
64
+ and os.getenv("MCP_MESH_AUTO_RUN", "true").lower() == "true"
65
+ ):
63
66
  # Use debounced initialization instead of immediate MCP startup
64
67
  # This allows the system to determine MCP vs API pipeline based on decorators
65
68
  try:
@@ -55,6 +55,15 @@ class DecoratorRegistry:
55
55
 
56
56
  # Registry for new decorator types (extensibility)
57
57
  _custom_decorators: dict[str, dict[str, DecoratedFunction]] = {}
58
+
59
+ # Immediate uvicorn server storage (for preventing shutdown state)
60
+ _immediate_uvicorn_server: Optional[dict[str, Any]] = None
61
+
62
+ # FastMCP lifespan storage (for proper integration with FastAPI)
63
+ _fastmcp_lifespan: Optional[Any] = None
64
+
65
+ # FastMCP HTTP app storage (the same app instance whose lifespan was extracted)
66
+ _fastmcp_http_app: Optional[Any] = None
58
67
 
59
68
  @classmethod
60
69
  def register_mesh_agent(cls, func: Callable, metadata: dict[str, Any]) -> None:
@@ -508,6 +517,92 @@ class DecoratorRegistry:
508
517
 
509
518
  return {}
510
519
 
520
+ @classmethod
521
+ def store_immediate_uvicorn_server(cls, server_info: dict[str, Any]) -> None:
522
+ """
523
+ Store reference to immediate uvicorn server started in decorator.
524
+
525
+ Args:
526
+ server_info: Dictionary containing server information:
527
+ - 'app': FastAPI app instance
528
+ - 'host': Server host
529
+ - 'port': Server port
530
+ - 'thread': Thread object
531
+ - Any other relevant server metadata
532
+ """
533
+ cls._immediate_uvicorn_server = server_info
534
+ logger.debug(f"🔄 REGISTRY: Stored immediate uvicorn server reference: {server_info.get('host')}:{server_info.get('port')}")
535
+
536
+ @classmethod
537
+ def get_immediate_uvicorn_server(cls) -> Optional[dict[str, Any]]:
538
+ """
539
+ Get stored immediate uvicorn server reference.
540
+
541
+ Returns:
542
+ Server info dict if available, None otherwise
543
+ """
544
+ return cls._immediate_uvicorn_server
545
+
546
+ @classmethod
547
+ def clear_immediate_uvicorn_server(cls) -> None:
548
+ """Clear stored immediate uvicorn server reference."""
549
+ cls._immediate_uvicorn_server = None
550
+ logger.debug("🔄 REGISTRY: Cleared immediate uvicorn server reference")
551
+
552
+ @classmethod
553
+ def store_fastmcp_lifespan(cls, lifespan: Any) -> None:
554
+ """
555
+ Store FastMCP lifespan for integration with FastAPI.
556
+
557
+ Args:
558
+ lifespan: FastMCP lifespan function
559
+ """
560
+ cls._fastmcp_lifespan = lifespan
561
+ logger.debug("🔄 REGISTRY: Stored FastMCP lifespan for FastAPI integration")
562
+
563
+ @classmethod
564
+ def get_fastmcp_lifespan(cls) -> Optional[Any]:
565
+ """
566
+ Get stored FastMCP lifespan.
567
+
568
+ Returns:
569
+ FastMCP lifespan if available, None otherwise
570
+ """
571
+ return cls._fastmcp_lifespan
572
+
573
+ @classmethod
574
+ def clear_fastmcp_lifespan(cls) -> None:
575
+ """Clear stored FastMCP lifespan reference."""
576
+ cls._fastmcp_lifespan = None
577
+ logger.debug("🔄 REGISTRY: Cleared FastMCP lifespan reference")
578
+
579
+ @classmethod
580
+ def store_fastmcp_http_app(cls, http_app: Any) -> None:
581
+ """
582
+ Store FastMCP HTTP app (the same instance whose lifespan was extracted).
583
+
584
+ Args:
585
+ http_app: FastMCP HTTP app instance
586
+ """
587
+ cls._fastmcp_http_app = http_app
588
+ logger.debug("🔄 REGISTRY: Stored FastMCP HTTP app for mounting")
589
+
590
+ @classmethod
591
+ def get_fastmcp_http_app(cls) -> Optional[Any]:
592
+ """
593
+ Get stored FastMCP HTTP app.
594
+
595
+ Returns:
596
+ FastMCP HTTP app if available, None otherwise
597
+ """
598
+ return cls._fastmcp_http_app
599
+
600
+ @classmethod
601
+ def clear_fastmcp_http_app(cls) -> None:
602
+ """Clear stored FastMCP HTTP app reference."""
603
+ cls._fastmcp_http_app = None
604
+ logger.debug("🔄 REGISTRY: Cleared FastMCP HTTP app reference")
605
+
511
606
 
512
607
  # Convenience functions for external access
513
608
  def get_all_mesh_agents() -> dict[str, DecoratedFunction]:
@@ -12,7 +12,6 @@ from typing import Any, Optional
12
12
  from ..shared.content_extractor import ContentExtractor
13
13
  from ..shared.sse_parser import SSEParser
14
14
  from .async_mcp_client import AsyncMCPClient
15
- from .threading_utils import ThreadingUtils
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
@@ -48,13 +47,24 @@ class MCPClientProxy:
48
47
  )
49
48
 
50
49
  def _run_async(self, coro):
51
- """Convert async coroutine to sync call avoiding atexit bug.
50
+ """Convert async coroutine to sync call."""
52
51
 
53
- Uses shared ThreadingUtils for consistent DNS-safe threading behavior.
54
- """
55
- return ThreadingUtils.run_sync_from_async(
56
- coro, timeout=60.0, context_name=f"MCPClient.{self.function_name}"
57
- )
52
+ try:
53
+ # Try to get existing event loop
54
+ loop = asyncio.get_event_loop()
55
+ if loop.is_running():
56
+ # We're in an async context, need to run in thread
57
+ import concurrent.futures
58
+
59
+ with concurrent.futures.ThreadPoolExecutor() as executor:
60
+ future = executor.submit(asyncio.run, coro)
61
+ return future.result()
62
+ else:
63
+ # No running loop, safe to use loop.run_until_complete
64
+ return loop.run_until_complete(coro)
65
+ except RuntimeError:
66
+ # No event loop exists, create new one
67
+ return asyncio.run(coro)
58
68
 
59
69
  def __call__(self, **kwargs) -> Any:
60
70
  """Callable interface for dependency injection.
@@ -10,8 +10,6 @@ import uuid
10
10
  from collections.abc import AsyncIterator
11
11
  from typing import Any, Optional
12
12
 
13
- from .threading_utils import ThreadingUtils
14
-
15
13
  logger = logging.getLogger(__name__)
16
14
 
17
15
 
@@ -58,11 +56,27 @@ class UnifiedMCPProxy:
58
56
  f"🔧 UnifiedMCPProxy initialized with kwargs: {self.kwargs_config}"
59
57
  )
60
58
 
59
+ def _is_ip_address(self, hostname: str) -> bool:
60
+ """Check if hostname is an IP address vs DNS name.
61
+
62
+ Args:
63
+ hostname: Hostname to check
64
+
65
+ Returns:
66
+ True if IP address, False if DNS name
67
+ """
68
+ import ipaddress
69
+ try:
70
+ ipaddress.ip_address(hostname)
71
+ return True
72
+ except ValueError:
73
+ return False
74
+
61
75
  def _create_fastmcp_client(self, endpoint: str):
62
- """Create FastMCP client with automatic trace header injection.
76
+ """Create FastMCP client with DNS detection for threading conflict avoidance.
63
77
 
64
- This method automatically detects trace context and adds distributed tracing
65
- headers when available, while maintaining full backward compatibility.
78
+ This method detects DNS names vs IP addresses and forces HTTP fallback for DNS names
79
+ to avoid FastMCP client threading conflicts in containerized environments.
66
80
 
67
81
  Args:
68
82
  endpoint: MCP endpoint URL
@@ -71,6 +85,14 @@ class UnifiedMCPProxy:
71
85
  FastMCP Client instance with or without trace headers
72
86
  """
73
87
  try:
88
+ # Extract hostname from endpoint URL for DNS detection
89
+ from urllib.parse import urlparse
90
+ parsed = urlparse(endpoint)
91
+ hostname = parsed.hostname or parsed.netloc.split(':')[0]
92
+
93
+ # DNS resolution works perfectly with FastMCP - no need to force HTTP fallback
94
+ self.logger.debug(f"✅ Using FastMCP client for endpoint: {hostname}")
95
+
74
96
  from fastmcp import Client
75
97
  from fastmcp.client.transports import StreamableHttpTransport
76
98
 
@@ -85,16 +107,14 @@ class UnifiedMCPProxy:
85
107
  # Create standard client when no trace context available
86
108
  return Client(endpoint)
87
109
 
88
- except ImportError:
89
- # If StreamableHttpTransport not available, fall back to standard client
90
- from fastmcp import Client
91
-
92
- return Client(endpoint)
110
+ except ImportError as e:
111
+ # DNS names or FastMCP not available - this will trigger HTTP fallback
112
+ self.logger.debug(f"🔄 FastMCP client unavailable: {e}")
113
+ raise # Re-raise to trigger _fallback_http_call
93
114
  except Exception as e:
94
- # Any other error, fall back to standard client
95
- from fastmcp import Client
96
-
97
- return Client(endpoint)
115
+ # Any other error - this will trigger HTTP fallback
116
+ self.logger.debug(f"🔄 FastMCP client error: {e}")
117
+ raise ImportError(f"FastMCP client failed: {e}") # Convert to ImportError to trigger fallback
98
118
 
99
119
  def _get_trace_headers(self) -> dict[str, str]:
100
120
  """Extract trace headers from current context for distributed tracing.
@@ -341,6 +361,7 @@ class UnifiedMCPProxy:
341
361
  """
342
362
  import time
343
363
 
364
+
344
365
  start_time = time.time()
345
366
 
346
367
  try:
@@ -350,6 +371,7 @@ class UnifiedMCPProxy:
350
371
 
351
372
  # Create client with automatic trace header injection
352
373
  client_instance = self._create_fastmcp_client(mcp_endpoint)
374
+
353
375
  async with client_instance as client:
354
376
 
355
377
  # Use FastMCP's call_tool which returns CallToolResult object
@@ -394,7 +416,8 @@ class UnifiedMCPProxy:
394
416
  self.logger.warning(f"FastMCP Client failed: {e}, falling back to HTTP")
395
417
  # Try HTTP fallback
396
418
  try:
397
- return await self._fallback_http_call(name, arguments)
419
+ result = await self._fallback_http_call(name, arguments)
420
+ return result
398
421
  except Exception as fallback_error:
399
422
  raise RuntimeError(
400
423
  f"Tool call to '{name}' failed: {e}, fallback also failed: {fallback_error}"
@@ -831,15 +854,6 @@ class EnhancedUnifiedMCPProxy(UnifiedMCPProxy):
831
854
  f"auth_required: {self.auth_required}"
832
855
  )
833
856
 
834
- def __call__(self, **kwargs) -> Any:
835
- """Synchronous callable interface for dependency injection.
836
-
837
- This method provides a sync interface for dependency injection.
838
- Returns the actual result, not a coroutine.
839
- """
840
- # Use the enhanced tool call with proper sync-to-async bridging
841
- return self.call_tool_auto(self.function_name, kwargs)
842
-
843
857
  async def call_tool_enhanced(self, name: str, arguments: dict = None) -> Any:
844
858
  """Enhanced tool call with retry logic and custom configuration."""
845
859
  last_exception = None
@@ -869,19 +883,8 @@ class EnhancedUnifiedMCPProxy(UnifiedMCPProxy):
869
883
  raise last_exception
870
884
 
871
885
  def call_tool_auto(self, name: str, arguments: dict = None) -> Any:
872
- """Automatically choose streaming vs non-streaming based on configuration.
873
-
874
- Uses shared ThreadingUtils for consistent DNS-safe threading behavior.
875
- """
876
- # Create coroutine function based on streaming capability
877
- def coro_func():
878
- if self.streaming_capable:
879
- return self.call_tool_streaming(name, arguments)
880
- else:
881
- return self.call_tool_enhanced(name, arguments)
882
-
883
- return ThreadingUtils.run_sync_from_async(
884
- coro_func,
885
- timeout=self.timeout or 60.0,
886
- context_name=f"UnifiedMCPProxy.{name}",
887
- )
886
+ """Automatically choose streaming vs non-streaming based on configuration."""
887
+ if self.streaming_capable:
888
+ return self.call_tool_streaming(name, arguments)
889
+ else:
890
+ return self.call_tool_enhanced(name, arguments)
@@ -0,0 +1,139 @@
1
+ import logging
2
+ from typing import Any, Dict, List
3
+
4
+ from ..shared import PipelineResult, PipelineStatus, PipelineStep
5
+ from ...shared.server_discovery import ServerDiscoveryUtil
6
+
7
+
8
+ class FastAPIAppDiscoveryStep(PipelineStep):
9
+ """
10
+ Discovers existing FastAPI application instances in the user's code.
11
+
12
+ This step scans the Python runtime to find FastAPI applications that
13
+ have been instantiated by the user, without modifying them in any way.
14
+
15
+ The goal is minimal intervention - we only discover what exists,
16
+ we don't create or modify anything.
17
+ """
18
+
19
+ def __init__(self):
20
+ super().__init__(
21
+ name="fastapi-discovery",
22
+ required=True,
23
+ description="Discover existing FastAPI application instances",
24
+ )
25
+
26
+ async def execute(self, context: dict[str, Any]) -> PipelineResult:
27
+ """Discover FastAPI applications."""
28
+ self.logger.debug("Discovering FastAPI applications...")
29
+
30
+ result = PipelineResult(message="FastAPI discovery completed")
31
+
32
+ try:
33
+ # Get route decorators from context (from RouteCollectionStep)
34
+ mesh_routes = context.get("mesh_routes", {})
35
+
36
+ if not mesh_routes:
37
+ result.status = PipelineStatus.SKIPPED
38
+ result.message = "No @mesh.route decorators found"
39
+ self.logger.info("⚠️ No @mesh.route decorators found to process")
40
+ return result
41
+
42
+ # Discover FastAPI instances using shared utility
43
+ fastapi_apps = ServerDiscoveryUtil.discover_fastapi_instances()
44
+
45
+ if not fastapi_apps:
46
+ # This is not necessarily an error - user might be using FastAPI differently
47
+ result.status = PipelineStatus.FAILED
48
+ result.message = "No FastAPI applications found"
49
+ result.add_error("No FastAPI applications discovered in runtime")
50
+ self.logger.error(
51
+ "❌ No FastAPI applications found. @mesh.route decorators require "
52
+ "an existing FastAPI app instance. Please create a FastAPI app before "
53
+ "using @mesh.route decorators."
54
+ )
55
+ return result
56
+
57
+ # Analyze which routes belong to which apps
58
+ route_mapping = self._map_routes_to_apps(fastapi_apps, mesh_routes)
59
+
60
+ # Store discovery results in context
61
+ result.add_context("fastapi_apps", fastapi_apps)
62
+ result.add_context("route_mapping", route_mapping)
63
+ result.add_context("discovered_app_count", len(fastapi_apps))
64
+
65
+ # Update result message
66
+ route_count = sum(len(routes) for routes in route_mapping.values())
67
+ result.message = (
68
+ f"Discovered {len(fastapi_apps)} FastAPI app(s) with "
69
+ f"{route_count} @mesh.route decorated handlers"
70
+ )
71
+
72
+ self.logger.info(
73
+ f"📦 FastAPI Discovery: {len(fastapi_apps)} app(s), "
74
+ f"{route_count} @mesh.route handlers, {len(mesh_routes)} total routes"
75
+ )
76
+
77
+ # Log details for debugging
78
+ for app_id, app_info in fastapi_apps.items():
79
+ app_title = app_info.get("title", "Unknown")
80
+ routes_in_app = len(route_mapping.get(app_id, {}))
81
+ self.logger.debug(
82
+ f" App '{app_title}' ({app_id}): {routes_in_app} @mesh.route handlers"
83
+ )
84
+
85
+ except Exception as e:
86
+ result.status = PipelineStatus.FAILED
87
+ result.message = f"FastAPI discovery failed: {e}"
88
+ result.add_error(str(e))
89
+ self.logger.error(f"❌ FastAPI discovery failed: {e}")
90
+
91
+ return result
92
+
93
+
94
+ def _map_routes_to_apps(
95
+ self,
96
+ fastapi_apps: Dict[str, Dict[str, Any]],
97
+ mesh_routes: Dict[str, Any]
98
+ ) -> Dict[str, Dict[str, Any]]:
99
+ """
100
+ Map @mesh.route decorated functions to their FastAPI applications.
101
+
102
+ Args:
103
+ fastapi_apps: Discovered FastAPI applications
104
+ mesh_routes: @mesh.route decorated functions from DecoratorRegistry
105
+
106
+ Returns:
107
+ Dict mapping app_id -> {route_name -> route_info} for routes that have @mesh.route
108
+ """
109
+ route_mapping = {}
110
+
111
+ for app_id, app_info in fastapi_apps.items():
112
+ app_routes = {}
113
+
114
+ for route_info in app_info["routes"]:
115
+ endpoint_name = route_info["endpoint_name"]
116
+
117
+ # Check if this route handler has @mesh.route decorator
118
+ if endpoint_name in mesh_routes:
119
+ mesh_route_data = mesh_routes[endpoint_name]
120
+
121
+ # Combine FastAPI route info with @mesh.route metadata
122
+ combined_info = {
123
+ **route_info, # FastAPI route info
124
+ "mesh_metadata": mesh_route_data.metadata, # @mesh.route metadata
125
+ "dependencies": mesh_route_data.metadata.get("dependencies", []),
126
+ "mesh_decorator": mesh_route_data, # Full DecoratedFunction object
127
+ }
128
+
129
+ app_routes[endpoint_name] = combined_info
130
+
131
+ self.logger.debug(
132
+ f"Mapped route '{endpoint_name}' to app '{app_info['title']}' "
133
+ f"with {len(combined_info['dependencies'])} dependencies"
134
+ )
135
+
136
+ if app_routes:
137
+ route_mapping[app_id] = app_routes
138
+
139
+ return route_mapping
@@ -43,6 +43,8 @@ class HeartbeatOrchestrator:
43
43
  self._heartbeat_count += 1
44
44
 
45
45
  try:
46
+
47
+
46
48
  # Prepare heartbeat context with validation
47
49
  heartbeat_context = self._prepare_heartbeat_context(agent_id, context)
48
50
 
@@ -65,10 +67,12 @@ class HeartbeatOrchestrator:
65
67
  import asyncio
66
68
 
67
69
  try:
70
+
68
71
  result = await asyncio.wait_for(
69
72
  self.pipeline.execute_heartbeat_cycle(heartbeat_context),
70
73
  timeout=30.0,
71
74
  )
75
+
72
76
  except TimeoutError:
73
77
  self.logger.error(
74
78
  f"❌ Heartbeat #{self._heartbeat_count} timed out after 30 seconds"
@@ -44,6 +44,19 @@ async def heartbeat_lifespan_task(heartbeat_config: dict[str, Any]) -> None:
44
44
 
45
45
  try:
46
46
  while True:
47
+ # Check if shutdown is complete before executing heartbeat
48
+ try:
49
+ from ...shared.simple_shutdown import should_stop_heartbeat
50
+
51
+ if should_stop_heartbeat():
52
+ logger.info(
53
+ f"🛑 Heartbeat stopped for agent '{agent_id}' due to shutdown"
54
+ )
55
+ break
56
+ except ImportError:
57
+ # If simple_shutdown is not available, continue normally
58
+ pass
59
+
47
60
  try:
48
61
  # Execute heartbeat pipeline
49
62
  success = await heartbeat_orchestrator.execute_heartbeat(
@@ -13,6 +13,7 @@ from .heartbeat_loop import HeartbeatLoopStep
13
13
  from .heartbeat_preparation import HeartbeatPreparationStep
14
14
  from .startup_orchestrator import (
15
15
  MeshOrchestrator,
16
+ clear_debounce_coordinator,
16
17
  get_debounce_coordinator,
17
18
  get_global_orchestrator,
18
19
  start_runtime,
@@ -28,6 +29,7 @@ __all__ = [
28
29
  "HeartbeatPreparationStep",
29
30
  "MeshOrchestrator",
30
31
  "StartupPipeline",
32
+ "clear_debounce_coordinator",
31
33
  "get_global_orchestrator",
32
34
  "get_debounce_coordinator",
33
35
  "start_runtime",