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.
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/PKG-INFO +1 -1
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/__init__.py +5 -2
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/decorator_registry.py +95 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/mcp_client_proxy.py +17 -7
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/unified_mcp_proxy.py +43 -40
- mcp_mesh-0.5.6/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +139 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +4 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +13 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/__init__.py +2 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +306 -163
- mcp_mesh-0.5.6/_mcp_mesh/pipeline/mcp_startup/server_discovery.py +164 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +198 -160
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +7 -4
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/mesh_pipeline.py +4 -0
- mcp_mesh-0.5.6/_mcp_mesh/shared/server_discovery.py +312 -0
- mcp_mesh-0.5.6/_mcp_mesh/shared/simple_shutdown.py +217 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/mesh/decorators.py +303 -36
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/pyproject.toml +4 -4
- mcp_mesh-0.5.4/_mcp_mesh/engine/threading_utils.py +0 -223
- mcp_mesh-0.5.4/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +0 -302
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/.gitignore +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/LICENSE +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/README.md +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/async_mcp_client.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/dependency_injector.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/full_mcp_proxy.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/http_wrapper.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/self_dependency_proxy.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/session_aware_client.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/session_manager.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/engine/signature_analyzer.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator/FILES +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator/VERSION +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/.openapi-generator-ignore +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -0
- {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
- {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
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -0
- {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
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -0
- {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
- {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
- {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
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/api_pipeline.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/api_server_setup.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/middleware_integration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/route_collection.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/api_startup/route_integration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/configuration.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/decorator_collection.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/base_step.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/pipeline_types.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/shared/registry_connection.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/__init__.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/config_resolver.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/content_extractor.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/defaults.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/fast_heartbeat_status.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/fastapi_middleware_manager.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/host_resolver.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/logging_config.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/registry_client_wrapper.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/sse_parser.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/shared/support_types.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/agent_context_helper.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/context.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/execution_tracer.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/fastapi_tracing_middleware.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/redis_metadata_publisher.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/trace_context_helper.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/tracing/utils.py +0 -0
- {mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/mesh/__init__.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
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
|
|
50
|
+
"""Convert async coroutine to sync call."""
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
76
|
+
"""Create FastMCP client with DNS detection for threading conflict avoidance.
|
|
63
77
|
|
|
64
|
-
This method
|
|
65
|
-
|
|
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
|
-
#
|
|
90
|
-
|
|
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
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
{mcp_mesh-0.5.4 → mcp_mesh-0.5.6}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py
RENAMED
|
@@ -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",
|