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.
Files changed (123) hide show
  1. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/PKG-INFO +1 -1
  2. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/__init__.py +1 -1
  3. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/mcp_client_proxy.py +7 -17
  4. mcp_mesh-0.5.4/_mcp_mesh/engine/threading_utils.py +223 -0
  5. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/unified_mcp_proxy.py +27 -5
  6. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/pyproject.toml +4 -4
  7. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/.gitignore +0 -0
  8. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/LICENSE +0 -0
  9. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/README.md +0 -0
  10. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/__init__.py +0 -0
  11. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/async_mcp_client.py +0 -0
  12. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/decorator_registry.py +0 -0
  13. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/dependency_injector.py +0 -0
  14. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/full_mcp_proxy.py +0 -0
  15. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/http_wrapper.py +0 -0
  16. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/self_dependency_proxy.py +0 -0
  17. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/session_aware_client.py +0 -0
  18. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/session_manager.py +0 -0
  19. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/engine/signature_analyzer.py +0 -0
  20. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator/FILES +0 -0
  21. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator/VERSION +0 -0
  22. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/.openapi-generator-ignore +0 -0
  23. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -0
  24. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -0
  25. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -0
  26. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -0
  27. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -0
  28. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -0
  29. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -0
  30. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -0
  31. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -0
  32. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -0
  33. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -0
  34. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -0
  35. {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
  36. {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
  37. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -0
  38. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -0
  39. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -0
  40. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -0
  41. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -0
  42. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -0
  43. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -0
  44. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -0
  45. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -0
  46. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -0
  47. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -0
  48. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -0
  49. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -0
  50. {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
  51. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -0
  52. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -0
  53. {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
  54. {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
  55. {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
  56. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -0
  57. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -0
  58. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -0
  59. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -0
  60. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -0
  61. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -0
  62. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  63. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -0
  64. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/__init__.py +0 -0
  65. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/__init__.py +0 -0
  66. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -0
  67. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -0
  68. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -0
  69. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -0
  70. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -0
  71. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -0
  72. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +0 -0
  73. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -0
  74. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/__init__.py +0 -0
  75. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/api_pipeline.py +0 -0
  76. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/api_server_setup.py +0 -0
  77. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +0 -0
  78. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/middleware_integration.py +0 -0
  79. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/route_collection.py +0 -0
  80. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/api_startup/route_integration.py +0 -0
  81. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/__init__.py +0 -0
  82. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -0
  83. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -0
  84. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -0
  85. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -0
  86. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -0
  87. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -0
  88. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -0
  89. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/__init__.py +0 -0
  90. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/configuration.py +0 -0
  91. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/decorator_collection.py +0 -0
  92. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +0 -0
  93. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +0 -0
  94. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +0 -0
  95. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +0 -0
  96. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +0 -0
  97. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +0 -0
  98. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/__init__.py +0 -0
  99. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/base_step.py +0 -0
  100. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -0
  101. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/pipeline_types.py +0 -0
  102. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/pipeline/shared/registry_connection.py +0 -0
  103. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/__init__.py +0 -0
  104. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/config_resolver.py +0 -0
  105. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/content_extractor.py +0 -0
  106. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/defaults.py +0 -0
  107. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/fast_heartbeat_status.py +0 -0
  108. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/fastapi_middleware_manager.py +0 -0
  109. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/host_resolver.py +0 -0
  110. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/logging_config.py +0 -0
  111. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/registry_client_wrapper.py +0 -0
  112. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/sse_parser.py +0 -0
  113. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/shared/support_types.py +0 -0
  114. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/agent_context_helper.py +0 -0
  115. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/context.py +0 -0
  116. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/execution_tracer.py +0 -0
  117. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/fastapi_tracing_middleware.py +0 -0
  118. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/redis_metadata_publisher.py +0 -0
  119. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/trace_context_helper.py +0 -0
  120. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/_mcp_mesh/tracing/utils.py +0 -0
  121. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/mesh/__init__.py +0 -0
  122. {mcp_mesh-0.5.3 → mcp_mesh-0.5.4}/mesh/decorators.py +0 -0
  123. {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
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
@@ -31,7 +31,7 @@ from .engine.decorator_registry import (
31
31
  get_decorator_stats,
32
32
  )
33
33
 
34
- __version__ = "0.5.3"
34
+ __version__ = "0.5.4"
35
35
 
36
36
  # Store reference to runtime processor if initialized
37
37
  _runtime_processor = None
@@ -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
- 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)
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
- if self.streaming_capable:
863
- return self.call_tool_streaming(name, arguments)
864
- else:
865
- return self.call_tool_enhanced(name, arguments)
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.3"
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.3"
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.3"
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.3"
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