mcp-mesh 0.5.3__tar.gz → 0.5.4__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.3 → mcp_mesh-0.5.4}/PKG-INFO +1 -1
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/__init__.py +1 -1
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/mcp_client_proxy.py +7 -17
- mcp_mesh-0.5.4/_mcp_mesh/engine/threading_utils.py +223 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/unified_mcp_proxy.py +27 -5
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/pyproject.toml +4 -4
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/.gitignore +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/LICENSE +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/README.md +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/async_mcp_client.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/decorator_registry.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/dependency_injector.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/full_mcp_proxy.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/http_wrapper.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/self_dependency_proxy.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/session_aware_client.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/session_manager.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/signature_analyzer.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator/FILES +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator/VERSION +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator-ignore +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/api_pipeline.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/api_server_setup.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/middleware_integration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/route_collection.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/route_integration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/configuration.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/decorator_collection.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/base_step.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/pipeline_types.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/registry_connection.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/config_resolver.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/content_extractor.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/defaults.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/fast_heartbeat_status.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/fastapi_middleware_manager.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/host_resolver.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/logging_config.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/registry_client_wrapper.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/sse_parser.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/support_types.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/agent_context_helper.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/context.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/execution_tracer.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/fastapi_tracing_middleware.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/redis_metadata_publisher.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/trace_context_helper.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/utils.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/mesh/__init__.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/mesh/decorators.py +0 -0
- {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/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.4
|
|
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
|
|
@@ -12,6 +12,7 @@ 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
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
@@ -47,24 +48,13 @@ class MCPClientProxy:
|
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
def _run_async(self, coro):
|
|
50
|
-
"""Convert async coroutine to sync call.
|
|
51
|
+
"""Convert async coroutine to sync call avoiding atexit bug.
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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)
|
|
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
|
+
)
|
|
68
58
|
|
|
69
59
|
def __call__(self, **kwargs) -> Any:
|
|
70
60
|
"""Callable interface for dependency injection.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Threading utilities for sync-to-async bridging with atexit bug fixes.
|
|
2
|
+
|
|
3
|
+
Provides a consolidated implementation for running async operations from sync contexts
|
|
4
|
+
while avoiding the Python 3.8+ atexit bug that occurs in daemon thread contexts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import queue
|
|
10
|
+
import threading
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any, Union
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ThreadingUtils:
|
|
18
|
+
"""Utilities for safe sync-to-async bridging avoiding Python 3.8+ atexit issues."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def run_sync_from_async(
|
|
22
|
+
coro_or_func: Union[Any, Callable],
|
|
23
|
+
timeout: float = 60.0,
|
|
24
|
+
context_name: str = "operation",
|
|
25
|
+
) -> Any:
|
|
26
|
+
"""Convert async coroutine to sync call avoiding atexit bug.
|
|
27
|
+
|
|
28
|
+
Handles both coroutines and coroutine creation functions safely.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
coro_or_func: Either a coroutine object or a function that returns a coroutine
|
|
32
|
+
timeout: Operation timeout in seconds
|
|
33
|
+
context_name: Name for logging/debugging context
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The result of the async operation
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
TimeoutError: If operation exceeds timeout
|
|
40
|
+
RuntimeError: If operation fails or returns no result
|
|
41
|
+
"""
|
|
42
|
+
import inspect
|
|
43
|
+
|
|
44
|
+
# If it's a function, call it to get the coroutine
|
|
45
|
+
if callable(coro_or_func) and not inspect.iscoroutine(coro_or_func):
|
|
46
|
+
try:
|
|
47
|
+
loop = asyncio.get_event_loop()
|
|
48
|
+
if loop.is_running():
|
|
49
|
+
# In running loop context, use thread-safe approach
|
|
50
|
+
return ThreadingUtils._run_in_thread_safe(
|
|
51
|
+
coro_or_func, timeout, context_name
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
# No running loop, create coroutine and run directly
|
|
55
|
+
coro = coro_or_func()
|
|
56
|
+
return loop.run_until_complete(coro)
|
|
57
|
+
except RuntimeError:
|
|
58
|
+
# No event loop, use thread-safe approach
|
|
59
|
+
return ThreadingUtils._run_in_thread_safe(
|
|
60
|
+
coro_or_func, timeout, context_name
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# It's already a coroutine, handle it
|
|
64
|
+
coro = coro_or_func
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
loop = asyncio.get_event_loop()
|
|
68
|
+
if loop.is_running():
|
|
69
|
+
# Use thread-safe approach for running loops
|
|
70
|
+
return ThreadingUtils._run_coroutine_in_thread(
|
|
71
|
+
coro, timeout, context_name
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
# No running loop, safe to use directly
|
|
75
|
+
return loop.run_until_complete(coro)
|
|
76
|
+
except RuntimeError:
|
|
77
|
+
# No event loop exists, use thread-safe approach
|
|
78
|
+
return ThreadingUtils._run_coroutine_in_thread(coro, timeout, context_name)
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _run_in_thread_safe(
|
|
82
|
+
coro_func: Callable, timeout: float, context_name: str
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Run coroutine creation function in thread-safe manner."""
|
|
85
|
+
result_queue: queue.Queue = queue.Queue()
|
|
86
|
+
|
|
87
|
+
def _thread_runner():
|
|
88
|
+
"""Execute coroutine function in isolated thread context."""
|
|
89
|
+
try:
|
|
90
|
+
# Apply atexit bypass for this thread
|
|
91
|
+
ThreadingUtils._apply_atexit_bypass()
|
|
92
|
+
|
|
93
|
+
# Create fresh event loop
|
|
94
|
+
new_loop = asyncio.new_event_loop()
|
|
95
|
+
asyncio.set_event_loop(new_loop)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Create coroutine in this thread's context
|
|
99
|
+
coro = coro_func()
|
|
100
|
+
result = new_loop.run_until_complete(coro)
|
|
101
|
+
result_queue.put(("success", result))
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(
|
|
104
|
+
f"❌ {context_name} failed in thread: {e}", exc_info=True
|
|
105
|
+
)
|
|
106
|
+
result_queue.put(("error", e))
|
|
107
|
+
finally:
|
|
108
|
+
# Manual cleanup without relying on atexit
|
|
109
|
+
try:
|
|
110
|
+
new_loop.close()
|
|
111
|
+
finally:
|
|
112
|
+
asyncio.set_event_loop(None)
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(
|
|
116
|
+
f"❌ {context_name} thread setup failed: {e}", exc_info=True
|
|
117
|
+
)
|
|
118
|
+
result_queue.put(("error", e))
|
|
119
|
+
|
|
120
|
+
# Use non-daemon thread to avoid atexit issues
|
|
121
|
+
thread = threading.Thread(
|
|
122
|
+
target=_thread_runner, daemon=False, name=f"MCPMesh-{context_name}"
|
|
123
|
+
)
|
|
124
|
+
thread.start()
|
|
125
|
+
thread.join(timeout=timeout)
|
|
126
|
+
|
|
127
|
+
if thread.is_alive():
|
|
128
|
+
logger.error(f"⏰ {context_name} timed out after {timeout}s")
|
|
129
|
+
raise TimeoutError(f"{context_name} timed out after {timeout}s")
|
|
130
|
+
|
|
131
|
+
if result_queue.empty():
|
|
132
|
+
raise RuntimeError(f"No result from {context_name}")
|
|
133
|
+
|
|
134
|
+
status, result = result_queue.get()
|
|
135
|
+
if status == "error":
|
|
136
|
+
raise result
|
|
137
|
+
|
|
138
|
+
logger.debug(f"✅ {context_name} completed successfully in thread")
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _run_coroutine_in_thread(coro: Any, timeout: float, context_name: str) -> Any:
|
|
143
|
+
"""Run existing coroutine in thread-safe manner."""
|
|
144
|
+
result_queue: queue.Queue = queue.Queue()
|
|
145
|
+
|
|
146
|
+
def _thread_runner():
|
|
147
|
+
"""Execute coroutine in isolated thread context."""
|
|
148
|
+
try:
|
|
149
|
+
# Apply atexit bypass for this thread
|
|
150
|
+
ThreadingUtils._apply_atexit_bypass()
|
|
151
|
+
|
|
152
|
+
# Create fresh event loop
|
|
153
|
+
new_loop = asyncio.new_event_loop()
|
|
154
|
+
asyncio.set_event_loop(new_loop)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
result = new_loop.run_until_complete(coro)
|
|
158
|
+
result_queue.put(("success", result))
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(
|
|
161
|
+
f"❌ {context_name} coroutine failed: {e}", exc_info=True
|
|
162
|
+
)
|
|
163
|
+
result_queue.put(("error", e))
|
|
164
|
+
finally:
|
|
165
|
+
# Manual cleanup without relying on atexit
|
|
166
|
+
try:
|
|
167
|
+
new_loop.close()
|
|
168
|
+
finally:
|
|
169
|
+
asyncio.set_event_loop(None)
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(
|
|
173
|
+
f"❌ {context_name} thread setup failed: {e}", exc_info=True
|
|
174
|
+
)
|
|
175
|
+
result_queue.put(("error", e))
|
|
176
|
+
|
|
177
|
+
# Use non-daemon thread to avoid atexit issues
|
|
178
|
+
thread = threading.Thread(
|
|
179
|
+
target=_thread_runner, daemon=False, name=f"MCPMesh-{context_name}"
|
|
180
|
+
)
|
|
181
|
+
thread.start()
|
|
182
|
+
thread.join(timeout=timeout)
|
|
183
|
+
|
|
184
|
+
if thread.is_alive():
|
|
185
|
+
logger.error(f"⏰ {context_name} timed out after {timeout}s")
|
|
186
|
+
raise TimeoutError(f"{context_name} timed out after {timeout}s")
|
|
187
|
+
|
|
188
|
+
if result_queue.empty():
|
|
189
|
+
raise RuntimeError(f"No result from {context_name}")
|
|
190
|
+
|
|
191
|
+
status, result = result_queue.get()
|
|
192
|
+
if status == "error":
|
|
193
|
+
raise result
|
|
194
|
+
|
|
195
|
+
logger.debug(f"✅ {context_name} completed successfully in thread")
|
|
196
|
+
return result
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _apply_atexit_bypass():
|
|
200
|
+
"""Apply atexit bypass for current thread to prevent registration errors."""
|
|
201
|
+
try:
|
|
202
|
+
import atexit
|
|
203
|
+
import threading
|
|
204
|
+
|
|
205
|
+
# Store originals
|
|
206
|
+
original_atexit_register = atexit.register
|
|
207
|
+
original_thread_register = getattr(threading, "_register_atexit", None)
|
|
208
|
+
|
|
209
|
+
# Apply temporary bypass
|
|
210
|
+
def _noop_register(*args, **kwargs):
|
|
211
|
+
"""No-op atexit registration to prevent threading issues."""
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
atexit.register = _noop_register
|
|
215
|
+
if original_thread_register:
|
|
216
|
+
threading._register_atexit = _noop_register
|
|
217
|
+
|
|
218
|
+
# Store cleanup function for potential restoration
|
|
219
|
+
# (In practice, these threads are short-lived so restoration isn't critical)
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
# If atexit bypass fails, log but continue
|
|
223
|
+
logger.warning(f"⚠️ Failed to apply atexit bypass: {e}")
|
|
@@ -10,6 +10,8 @@ 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
|
+
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
14
16
|
|
|
15
17
|
|
|
@@ -829,6 +831,15 @@ class EnhancedUnifiedMCPProxy(UnifiedMCPProxy):
|
|
|
829
831
|
f"auth_required: {self.auth_required}"
|
|
830
832
|
)
|
|
831
833
|
|
|
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
|
+
|
|
832
843
|
async def call_tool_enhanced(self, name: str, arguments: dict = None) -> Any:
|
|
833
844
|
"""Enhanced tool call with retry logic and custom configuration."""
|
|
834
845
|
last_exception = None
|
|
@@ -858,8 +869,19 @@ class EnhancedUnifiedMCPProxy(UnifiedMCPProxy):
|
|
|
858
869
|
raise last_exception
|
|
859
870
|
|
|
860
871
|
def call_tool_auto(self, name: str, arguments: dict = None) -> Any:
|
|
861
|
-
"""Automatically choose streaming vs non-streaming based on configuration.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
)
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "mcp-mesh"
|
|
9
|
-
version = "0.5.
|
|
9
|
+
version = "0.5.4"
|
|
10
10
|
description = "Kubernetes-native platform for distributed MCP applications"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
license = { text = "MIT" }
|
|
@@ -117,7 +117,7 @@ extend-exclude = '''
|
|
|
117
117
|
'''
|
|
118
118
|
|
|
119
119
|
[tool.ruff]
|
|
120
|
-
target-version = "0.5.
|
|
120
|
+
target-version = "0.5.4"
|
|
121
121
|
line-length = 88
|
|
122
122
|
|
|
123
123
|
[tool.ruff.lint]
|
|
@@ -154,7 +154,7 @@ ignore = [
|
|
|
154
154
|
"tests/**" = ["E712", "F841", "B007", "C401", "F401"] # Relax style requirements for test files
|
|
155
155
|
|
|
156
156
|
[tool.mypy]
|
|
157
|
-
python_version = "0.5.
|
|
157
|
+
python_version = "0.5.4"
|
|
158
158
|
check_untyped_defs = false # Temporarily relaxed
|
|
159
159
|
disallow_any_generics = false # Temporarily relaxed
|
|
160
160
|
disallow_incomplete_defs = false # Temporarily relaxed
|
|
@@ -166,7 +166,7 @@ warn_return_any = false # Temporarily relaxed
|
|
|
166
166
|
exclude = ["tests/", ".*agent_server_generated.*", ".*registry_client_generated.*"] # Skip type checking for test and generated files
|
|
167
167
|
|
|
168
168
|
[tool.pytest.ini_options]
|
|
169
|
-
minversion = "0.5.
|
|
169
|
+
minversion = "0.5.4"
|
|
170
170
|
addopts = "-ra -q --strict-markers --strict-config"
|
|
171
171
|
testpaths = [
|
|
172
172
|
"tests",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py
RENAMED
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|