mcp-mesh 0.7.21__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/dependency_injector.py +13 -15
- _mcp_mesh/engine/http_wrapper.py +69 -10
- _mcp_mesh/engine/mesh_llm_agent.py +29 -10
- _mcp_mesh/engine/mesh_llm_agent_injector.py +77 -41
- _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
- _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
- _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
- _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
- _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
- _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
- _mcp_mesh/engine/response_parser.py +61 -15
- _mcp_mesh/engine/signature_analyzer.py +58 -68
- _mcp_mesh/engine/unified_mcp_proxy.py +19 -35
- _mcp_mesh/pipeline/__init__.py +9 -20
- _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
- _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
- _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +429 -0
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
- _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
- _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
- _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
- _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
- _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +710 -0
- _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
- _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +31 -8
- _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +23 -11
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
- _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
- _mcp_mesh/reload.py +1 -3
- _mcp_mesh/shared/__init__.py +2 -8
- _mcp_mesh/shared/config_resolver.py +124 -80
- _mcp_mesh/shared/defaults.py +89 -14
- _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
- _mcp_mesh/shared/host_resolver.py +8 -46
- _mcp_mesh/shared/server_discovery.py +115 -86
- _mcp_mesh/shared/simple_shutdown.py +44 -86
- _mcp_mesh/tracing/execution_tracer.py +2 -6
- _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
- _mcp_mesh/tracing/trace_context_helper.py +3 -13
- _mcp_mesh/tracing/utils.py +29 -15
- _mcp_mesh/utils/fastmcp_schema_extractor.py +5 -4
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/METADATA +7 -5
- mcp_mesh-0.8.0.dist-info/RECORD +85 -0
- mesh/__init__.py +12 -1
- mesh/decorators.py +248 -33
- mesh/helpers.py +52 -0
- mesh/types.py +40 -13
- _mcp_mesh/generated/.openapi-generator/FILES +0 -50
- _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
- _mcp_mesh/generated/.openapi-generator-ignore +0 -15
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
- _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
- _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
- _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
- _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
- _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
- _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
- _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
- _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
- _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
- _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
- _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
- _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
- _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
- _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
- _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
- _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
- _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
- _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
- _mcp_mesh/shared/registry_client_wrapper.py +0 -515
- mcp_mesh-0.7.21.dist-info/RECORD +0 -152
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,21 +5,67 @@ Function signature analysis for MCP Mesh dependency injection.
|
|
|
5
5
|
import inspect
|
|
6
6
|
from typing import Any, get_type_hints
|
|
7
7
|
|
|
8
|
-
from mesh.types import
|
|
8
|
+
from mesh.types import McpMeshTool, MeshLlmAgent
|
|
9
|
+
|
|
10
|
+
# Also support deprecated McpMeshAgent for backwards compatibility
|
|
11
|
+
try:
|
|
12
|
+
from mesh.types import McpMeshAgent
|
|
13
|
+
except ImportError:
|
|
14
|
+
McpMeshAgent = McpMeshTool # type: ignore
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_mesh_tool_type(param_type: Any) -> bool:
|
|
18
|
+
"""Check if a type is McpMeshTool or deprecated McpMeshAgent."""
|
|
19
|
+
# Direct McpMeshTool type
|
|
20
|
+
if (
|
|
21
|
+
param_type == McpMeshTool
|
|
22
|
+
or (hasattr(param_type, "__name__") and param_type.__name__ == "McpMeshTool")
|
|
23
|
+
or (
|
|
24
|
+
hasattr(param_type, "__origin__")
|
|
25
|
+
and param_type.__origin__ == type(McpMeshTool)
|
|
26
|
+
)
|
|
27
|
+
):
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
# Support deprecated McpMeshAgent
|
|
31
|
+
if (
|
|
32
|
+
param_type == McpMeshAgent
|
|
33
|
+
or (hasattr(param_type, "__name__") and param_type.__name__ == "McpMeshAgent")
|
|
34
|
+
or (
|
|
35
|
+
hasattr(param_type, "__origin__")
|
|
36
|
+
and param_type.__origin__ == type(McpMeshAgent)
|
|
37
|
+
)
|
|
38
|
+
):
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
# Union type (e.g., McpMeshTool | None)
|
|
42
|
+
if hasattr(param_type, "__args__"):
|
|
43
|
+
for arg in param_type.__args__:
|
|
44
|
+
if arg == McpMeshTool or (
|
|
45
|
+
hasattr(arg, "__name__") and arg.__name__ == "McpMeshTool"
|
|
46
|
+
):
|
|
47
|
+
return True
|
|
48
|
+
# Support deprecated McpMeshAgent in unions
|
|
49
|
+
if arg == McpMeshAgent or (
|
|
50
|
+
hasattr(arg, "__name__") and arg.__name__ == "McpMeshAgent"
|
|
51
|
+
):
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
return False
|
|
9
55
|
|
|
10
56
|
|
|
11
57
|
def get_mesh_agent_positions(func: Any) -> list[int]:
|
|
12
58
|
"""
|
|
13
|
-
Get positions of
|
|
59
|
+
Get positions of McpMeshTool parameters in function signature.
|
|
14
60
|
|
|
15
61
|
Args:
|
|
16
62
|
func: Function to analyze
|
|
17
63
|
|
|
18
64
|
Returns:
|
|
19
|
-
List of parameter positions (0-indexed) that are
|
|
65
|
+
List of parameter positions (0-indexed) that are McpMeshTool types
|
|
20
66
|
|
|
21
67
|
Example:
|
|
22
|
-
def greet(name: str, date_svc:
|
|
68
|
+
def greet(name: str, date_svc: McpMeshTool, file_svc: McpMeshTool):
|
|
23
69
|
pass
|
|
24
70
|
|
|
25
71
|
get_mesh_agent_positions(greet) → [1, 2]
|
|
@@ -32,40 +78,12 @@ def get_mesh_agent_positions(func: Any) -> list[int]:
|
|
|
32
78
|
sig = inspect.signature(func)
|
|
33
79
|
param_names = list(sig.parameters.keys())
|
|
34
80
|
|
|
35
|
-
# Find positions of
|
|
81
|
+
# Find positions of McpMeshTool parameters
|
|
36
82
|
mesh_positions = []
|
|
37
83
|
for i, param_name in enumerate(param_names):
|
|
38
84
|
if param_name in type_hints:
|
|
39
85
|
param_type = type_hints[param_name]
|
|
40
|
-
|
|
41
|
-
# Check if it's McpMeshAgent type (handle different import paths and Union types)
|
|
42
|
-
is_agent = False
|
|
43
|
-
|
|
44
|
-
# Direct McpMeshAgent type
|
|
45
|
-
if (
|
|
46
|
-
param_type == McpMeshAgent
|
|
47
|
-
or (
|
|
48
|
-
hasattr(param_type, "__name__")
|
|
49
|
-
and param_type.__name__ == "McpMeshAgent"
|
|
50
|
-
)
|
|
51
|
-
or (
|
|
52
|
-
hasattr(param_type, "__origin__")
|
|
53
|
-
and param_type.__origin__ == type(McpMeshAgent)
|
|
54
|
-
)
|
|
55
|
-
):
|
|
56
|
-
is_agent = True
|
|
57
|
-
|
|
58
|
-
# Union type (e.g., McpMeshAgent | None)
|
|
59
|
-
elif hasattr(param_type, "__args__"):
|
|
60
|
-
# Check if any arg in the union is McpMeshAgent
|
|
61
|
-
for arg in param_type.__args__:
|
|
62
|
-
if arg == McpMeshAgent or (
|
|
63
|
-
hasattr(arg, "__name__") and arg.__name__ == "McpMeshAgent"
|
|
64
|
-
):
|
|
65
|
-
is_agent = True
|
|
66
|
-
break
|
|
67
|
-
|
|
68
|
-
if is_agent:
|
|
86
|
+
if _is_mesh_tool_type(param_type):
|
|
69
87
|
mesh_positions.append(i)
|
|
70
88
|
|
|
71
89
|
return mesh_positions
|
|
@@ -81,13 +99,13 @@ def get_mesh_agent_positions(func: Any) -> list[int]:
|
|
|
81
99
|
|
|
82
100
|
def get_mesh_agent_parameter_names(func: Any) -> list[str]:
|
|
83
101
|
"""
|
|
84
|
-
Get names of
|
|
102
|
+
Get names of McpMeshTool parameters in function signature.
|
|
85
103
|
|
|
86
104
|
Args:
|
|
87
105
|
func: Function to analyze
|
|
88
106
|
|
|
89
107
|
Returns:
|
|
90
|
-
List of parameter names that are
|
|
108
|
+
List of parameter names that are McpMeshTool types
|
|
91
109
|
"""
|
|
92
110
|
try:
|
|
93
111
|
type_hints = get_type_hints(func)
|
|
@@ -97,35 +115,7 @@ def get_mesh_agent_parameter_names(func: Any) -> list[str]:
|
|
|
97
115
|
for param_name, param in sig.parameters.items():
|
|
98
116
|
if param_name in type_hints:
|
|
99
117
|
param_type = type_hints[param_name]
|
|
100
|
-
|
|
101
|
-
# Check if it's McpMeshAgent type (handle different import paths and Union types)
|
|
102
|
-
is_mesh_agent = False
|
|
103
|
-
|
|
104
|
-
# Direct McpMeshAgent type
|
|
105
|
-
if (
|
|
106
|
-
param_type == McpMeshAgent
|
|
107
|
-
or (
|
|
108
|
-
hasattr(param_type, "__name__")
|
|
109
|
-
and param_type.__name__ == "McpMeshAgent"
|
|
110
|
-
)
|
|
111
|
-
or (
|
|
112
|
-
hasattr(param_type, "__origin__")
|
|
113
|
-
and param_type.__origin__ == type(McpMeshAgent)
|
|
114
|
-
)
|
|
115
|
-
):
|
|
116
|
-
is_mesh_agent = True
|
|
117
|
-
|
|
118
|
-
# Union type (e.g., McpMeshAgent | None)
|
|
119
|
-
elif hasattr(param_type, "__args__"):
|
|
120
|
-
# Check if any arg in the union is McpMeshAgent
|
|
121
|
-
for arg in param_type.__args__:
|
|
122
|
-
if arg == McpMeshAgent or (
|
|
123
|
-
hasattr(arg, "__name__") and arg.__name__ == "McpMeshAgent"
|
|
124
|
-
):
|
|
125
|
-
is_mesh_agent = True
|
|
126
|
-
break
|
|
127
|
-
|
|
128
|
-
if is_mesh_agent:
|
|
118
|
+
if _is_mesh_tool_type(param_type):
|
|
129
119
|
mesh_param_names.append(param_name)
|
|
130
120
|
|
|
131
121
|
return mesh_param_names
|
|
@@ -136,7 +126,7 @@ def get_mesh_agent_parameter_names(func: Any) -> list[str]:
|
|
|
136
126
|
|
|
137
127
|
def validate_mesh_dependencies(func: Any, dependencies: list[dict]) -> tuple[bool, str]:
|
|
138
128
|
"""
|
|
139
|
-
Validate that the number of dependencies matches
|
|
129
|
+
Validate that the number of dependencies matches McpMeshTool parameters.
|
|
140
130
|
|
|
141
131
|
Args:
|
|
142
132
|
func: Function to validate
|
|
@@ -149,9 +139,9 @@ def validate_mesh_dependencies(func: Any, dependencies: list[dict]) -> tuple[boo
|
|
|
149
139
|
|
|
150
140
|
if len(dependencies) != len(mesh_positions):
|
|
151
141
|
return False, (
|
|
152
|
-
f"Function {func.__name__} has {len(mesh_positions)}
|
|
142
|
+
f"Function {func.__name__} has {len(mesh_positions)} McpMeshTool parameters "
|
|
153
143
|
f"but {len(dependencies)} dependencies declared. "
|
|
154
|
-
f"Each
|
|
144
|
+
f"Each McpMeshTool parameter needs a corresponding dependency."
|
|
155
145
|
)
|
|
156
146
|
|
|
157
147
|
return True, ""
|
|
@@ -16,6 +16,8 @@ from ..shared.logging_config import (
|
|
|
16
16
|
get_trace_prefix,
|
|
17
17
|
)
|
|
18
18
|
from ..shared.sse_parser import SSEParser
|
|
19
|
+
from ..tracing.context import TraceContext
|
|
20
|
+
from ..tracing.utils import generate_span_id
|
|
19
21
|
|
|
20
22
|
logger = logging.getLogger(__name__)
|
|
21
23
|
|
|
@@ -23,7 +25,7 @@ logger = logging.getLogger(__name__)
|
|
|
23
25
|
class UnifiedMCPProxy:
|
|
24
26
|
"""Unified MCP proxy using FastMCP's built-in client.
|
|
25
27
|
|
|
26
|
-
This provides the implementation for
|
|
28
|
+
This provides the implementation for McpMeshTool type parameters,
|
|
27
29
|
offering all MCP protocol features using FastMCP's superior client.
|
|
28
30
|
|
|
29
31
|
Features:
|
|
@@ -178,37 +180,6 @@ class UnifiedMCPProxy:
|
|
|
178
180
|
f"FastMCP client failed: {e}"
|
|
179
181
|
) # Convert to ImportError to trigger fallback
|
|
180
182
|
|
|
181
|
-
def _get_trace_headers(self) -> dict[str, str]:
|
|
182
|
-
"""Extract trace headers from current context for distributed tracing.
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
Dict of trace headers or empty dict if no trace context available
|
|
186
|
-
"""
|
|
187
|
-
try:
|
|
188
|
-
from ..tracing.context import TraceContext
|
|
189
|
-
|
|
190
|
-
current_trace = TraceContext.get_current()
|
|
191
|
-
if current_trace:
|
|
192
|
-
headers = {
|
|
193
|
-
"X-Trace-ID": current_trace.trace_id,
|
|
194
|
-
"X-Parent-Span": current_trace.span_id, # Current span becomes parent for downstream
|
|
195
|
-
}
|
|
196
|
-
self.logger.info(
|
|
197
|
-
f"🔗 TRACE_PROPAGATION: Injecting headers trace_id={current_trace.trace_id[:8]}... "
|
|
198
|
-
f"parent_span={current_trace.span_id[:8]}..."
|
|
199
|
-
)
|
|
200
|
-
return headers
|
|
201
|
-
else:
|
|
202
|
-
self.logger.warning("🔗 TRACE_PROPAGATION: No trace context available")
|
|
203
|
-
return {}
|
|
204
|
-
|
|
205
|
-
except Exception as e:
|
|
206
|
-
# Never fail MCP calls due to tracing issues
|
|
207
|
-
self.logger.warning(
|
|
208
|
-
f"🔗 TRACE_PROPAGATION: Exception getting trace context: {e}"
|
|
209
|
-
)
|
|
210
|
-
return {}
|
|
211
|
-
|
|
212
183
|
def _configure_from_kwargs(self):
|
|
213
184
|
"""Auto-configure proxy settings from kwargs."""
|
|
214
185
|
# Basic configuration
|
|
@@ -433,6 +404,19 @@ class UnifiedMCPProxy:
|
|
|
433
404
|
# Get trace prefix if available
|
|
434
405
|
tp = get_trace_prefix()
|
|
435
406
|
|
|
407
|
+
# Inject trace context into arguments for downstream agents
|
|
408
|
+
# This is the fallback mechanism for agents that can't access HTTP headers (e.g., TypeScript)
|
|
409
|
+
args_with_trace = dict(arguments) if arguments else {}
|
|
410
|
+
current_trace = TraceContext.get_current()
|
|
411
|
+
if current_trace:
|
|
412
|
+
# Use current function's span_id as parent for downstream call
|
|
413
|
+
# Don't generate a new span - that creates unpublished "ghost" spans that break the tree
|
|
414
|
+
args_with_trace["_trace_id"] = current_trace.trace_id
|
|
415
|
+
args_with_trace["_parent_span"] = current_trace.span_id
|
|
416
|
+
self.logger.debug(
|
|
417
|
+
f"{tp}🔗 Injecting trace context: trace_id={current_trace.trace_id[:8]}..., parent_span={current_trace.span_id[:8]}..."
|
|
418
|
+
)
|
|
419
|
+
|
|
436
420
|
# Log cross-agent call - summary line
|
|
437
421
|
arg_keys = list(arguments.keys()) if arguments else []
|
|
438
422
|
self.logger.debug(
|
|
@@ -451,7 +435,7 @@ class UnifiedMCPProxy:
|
|
|
451
435
|
async with client_instance as client:
|
|
452
436
|
|
|
453
437
|
# Use FastMCP's call_tool which returns CallToolResult object
|
|
454
|
-
result = await client.call_tool(name,
|
|
438
|
+
result = await client.call_tool(name, args_with_trace)
|
|
455
439
|
|
|
456
440
|
# Calculate performance metrics
|
|
457
441
|
end_time = time.time()
|
|
@@ -479,12 +463,12 @@ class UnifiedMCPProxy:
|
|
|
479
463
|
self.logger.warning(
|
|
480
464
|
f"FastMCP Client not available: {e}, falling back to HTTP"
|
|
481
465
|
)
|
|
482
|
-
return await self._fallback_http_call(name,
|
|
466
|
+
return await self._fallback_http_call(name, args_with_trace)
|
|
483
467
|
except Exception as e:
|
|
484
468
|
self.logger.warning(f"FastMCP Client failed: {e}, falling back to HTTP")
|
|
485
469
|
# Try HTTP fallback
|
|
486
470
|
try:
|
|
487
|
-
result = await self._fallback_http_call(name,
|
|
471
|
+
result = await self._fallback_http_call(name, args_with_trace)
|
|
488
472
|
return result
|
|
489
473
|
except Exception as fallback_error:
|
|
490
474
|
raise RuntimeError(
|
_mcp_mesh/pipeline/__init__.py
CHANGED
|
@@ -2,31 +2,22 @@
|
|
|
2
2
|
MCP Mesh Pipeline Architecture
|
|
3
3
|
|
|
4
4
|
This module provides a clean, explicit pipeline-based architecture for processing
|
|
5
|
-
decorators and managing the mesh agent lifecycle.
|
|
6
|
-
|
|
5
|
+
decorators and managing the mesh agent lifecycle. The Rust core handles registry
|
|
6
|
+
communication including heartbeats, dependency resolution, and deregistration.
|
|
7
7
|
|
|
8
8
|
Key Components:
|
|
9
9
|
- MeshPipeline: Main orchestrator that executes steps in sequence
|
|
10
10
|
- PipelineStep: Interface for individual processing steps
|
|
11
11
|
- PipelineResult: Result container with status and context
|
|
12
|
-
- Built-in steps for common operations (collection, config,
|
|
12
|
+
- Built-in steps for common operations (collection, config, etc.)
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
from .mcp_heartbeat import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
15
|
+
from .mcp_heartbeat import rust_heartbeat_task
|
|
16
|
+
from .mcp_startup import (ConfigurationStep, DecoratorCollectionStep,
|
|
17
|
+
FastAPIServerSetupStep, FastMCPServerDiscoveryStep,
|
|
18
|
+
HeartbeatLoopStep, HeartbeatPreparationStep,
|
|
19
|
+
StartupPipeline)
|
|
20
20
|
from .shared import MeshPipeline, PipelineResult, PipelineStatus, PipelineStep
|
|
21
|
-
from .mcp_startup import (
|
|
22
|
-
ConfigurationStep,
|
|
23
|
-
DecoratorCollectionStep,
|
|
24
|
-
FastAPIServerSetupStep,
|
|
25
|
-
FastMCPServerDiscoveryStep,
|
|
26
|
-
HeartbeatLoopStep,
|
|
27
|
-
HeartbeatPreparationStep,
|
|
28
|
-
StartupPipeline,
|
|
29
|
-
)
|
|
30
21
|
|
|
31
22
|
__all__ = [
|
|
32
23
|
"MeshPipeline",
|
|
@@ -39,8 +30,6 @@ __all__ = [
|
|
|
39
30
|
"FastMCPServerDiscoveryStep",
|
|
40
31
|
"HeartbeatLoopStep",
|
|
41
32
|
"HeartbeatPreparationStep",
|
|
42
|
-
"RegistryConnectionStep",
|
|
43
|
-
"HeartbeatSendStep",
|
|
44
|
-
"DependencyResolutionStep",
|
|
45
33
|
"StartupPipeline",
|
|
34
|
+
"rust_heartbeat_task",
|
|
46
35
|
]
|
|
@@ -3,14 +3,19 @@ API heartbeat pipeline for FastAPI integration.
|
|
|
3
3
|
|
|
4
4
|
Provides periodic service registration and health monitoring
|
|
5
5
|
for FastAPI applications using @mesh.route decorators.
|
|
6
|
+
|
|
7
|
+
Uses Rust core for registry communication, dependency resolution,
|
|
8
|
+
and deregistration.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
|
-
from .
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
from .api_lifespan_integration import (api_heartbeat_lifespan_task,
|
|
12
|
+
create_api_lifespan_handler,
|
|
13
|
+
integrate_api_heartbeat_with_fastapi)
|
|
14
|
+
from .rust_api_heartbeat import rust_api_heartbeat_task
|
|
11
15
|
|
|
12
16
|
__all__ = [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
+
"api_heartbeat_lifespan_task",
|
|
18
|
+
"create_api_lifespan_handler",
|
|
19
|
+
"integrate_api_heartbeat_with_fastapi",
|
|
20
|
+
"rust_api_heartbeat_task",
|
|
21
|
+
]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
FastAPI lifespan integration for API heartbeat pipeline.
|
|
3
3
|
|
|
4
|
-
Handles the execution of API heartbeat
|
|
4
|
+
Handles the execution of API heartbeat as a background task
|
|
5
5
|
during FastAPI application lifespan for @mesh.route decorator services.
|
|
6
|
+
|
|
7
|
+
Uses the Rust core for registry communication.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
import asyncio
|
|
@@ -14,65 +16,35 @@ logger = logging.getLogger(__name__)
|
|
|
14
16
|
|
|
15
17
|
async def api_heartbeat_lifespan_task(heartbeat_config: dict[str, Any]) -> None:
|
|
16
18
|
"""
|
|
17
|
-
API heartbeat task that runs in FastAPI lifespan
|
|
19
|
+
API heartbeat task that runs in FastAPI lifespan.
|
|
20
|
+
|
|
21
|
+
Uses Rust-backed heartbeat for registry communication.
|
|
18
22
|
|
|
19
23
|
Args:
|
|
20
|
-
heartbeat_config: Configuration containing service_id, interval,
|
|
24
|
+
heartbeat_config: Configuration containing service_id, interval,
|
|
21
25
|
and context for API heartbeat execution
|
|
22
26
|
"""
|
|
23
|
-
service_id = heartbeat_config
|
|
24
|
-
interval = heartbeat_config["interval"] # Already validated by get_config_value in setup
|
|
25
|
-
context = heartbeat_config["context"]
|
|
27
|
+
service_id = heartbeat_config.get("service_id", "unknown-api-service")
|
|
26
28
|
standalone_mode = heartbeat_config.get("standalone_mode", False)
|
|
27
29
|
|
|
28
30
|
# Check if running in standalone mode
|
|
29
31
|
if standalone_mode:
|
|
30
32
|
logger.info(
|
|
31
|
-
f"💓
|
|
33
|
+
f"💓 API heartbeat in standalone mode for service '{service_id}' "
|
|
32
34
|
f"(no registry communication)"
|
|
33
35
|
)
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
# Create API heartbeat orchestrator for pipeline execution
|
|
37
|
-
from .api_heartbeat_orchestrator import APIHeartbeatOrchestrator
|
|
36
|
+
return
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
# Use Rust-backed heartbeat
|
|
39
|
+
from .rust_api_heartbeat import rust_api_heartbeat_task
|
|
40
40
|
|
|
41
|
-
logger.info(f"💓
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
while True:
|
|
45
|
-
try:
|
|
46
|
-
# Execute API heartbeat pipeline
|
|
47
|
-
success = await api_heartbeat_orchestrator.execute_api_heartbeat(
|
|
48
|
-
service_id, context
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
if not success:
|
|
52
|
-
# Log failure but continue to next cycle (pipeline handles detailed logging)
|
|
53
|
-
logger.debug(
|
|
54
|
-
f"💔 API heartbeat pipeline failed for service '{service_id}' - "
|
|
55
|
-
f"continuing to next cycle"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
except Exception as e:
|
|
59
|
-
# Log pipeline execution error but continue to next cycle for resilience
|
|
60
|
-
logger.error(
|
|
61
|
-
f"❌ API heartbeat pipeline execution error for service '{service_id}': {e}"
|
|
62
|
-
)
|
|
63
|
-
# Continue to next cycle - heartbeat should be resilient
|
|
64
|
-
|
|
65
|
-
# Wait for next heartbeat interval
|
|
66
|
-
await asyncio.sleep(interval)
|
|
67
|
-
|
|
68
|
-
except asyncio.CancelledError:
|
|
69
|
-
logger.info(f"🛑 API heartbeat pipeline task cancelled for service '{service_id}'")
|
|
70
|
-
raise
|
|
41
|
+
logger.info(f"💓 Using Rust-backed heartbeat for API service '{service_id}'")
|
|
42
|
+
await rust_api_heartbeat_task(heartbeat_config)
|
|
71
43
|
|
|
72
44
|
|
|
73
45
|
def create_api_lifespan_handler(heartbeat_config: dict[str, Any]) -> Any:
|
|
74
46
|
"""
|
|
75
|
-
Create a FastAPI lifespan context manager that runs API heartbeat
|
|
47
|
+
Create a FastAPI lifespan context manager that runs API heartbeat.
|
|
76
48
|
|
|
77
49
|
Args:
|
|
78
50
|
heartbeat_config: Configuration for API heartbeat execution
|
|
@@ -100,11 +72,13 @@ def create_api_lifespan_handler(heartbeat_config: dict[str, Any]) -> Any:
|
|
|
100
72
|
# Cleanup: cancel heartbeat task
|
|
101
73
|
logger.info(f"🛑 Shutting down FastAPI lifespan for service '{service_id}'")
|
|
102
74
|
heartbeat_task.cancel()
|
|
103
|
-
|
|
75
|
+
|
|
104
76
|
try:
|
|
105
77
|
await heartbeat_task
|
|
106
78
|
except asyncio.CancelledError:
|
|
107
|
-
logger.info(
|
|
79
|
+
logger.info(
|
|
80
|
+
f"✅ API heartbeat task cancelled for service '{service_id}'"
|
|
81
|
+
)
|
|
108
82
|
|
|
109
83
|
return api_lifespan
|
|
110
84
|
|
|
@@ -113,18 +87,18 @@ def integrate_api_heartbeat_with_fastapi(
|
|
|
113
87
|
fastapi_app: Any, heartbeat_config: dict[str, Any]
|
|
114
88
|
) -> None:
|
|
115
89
|
"""
|
|
116
|
-
Integrate API heartbeat
|
|
90
|
+
Integrate API heartbeat with FastAPI lifespan events.
|
|
117
91
|
|
|
118
92
|
Args:
|
|
119
93
|
fastapi_app: FastAPI application instance
|
|
120
94
|
heartbeat_config: Configuration for heartbeat execution
|
|
121
95
|
"""
|
|
122
96
|
service_id = heartbeat_config.get("service_id", "unknown")
|
|
123
|
-
|
|
97
|
+
|
|
124
98
|
try:
|
|
125
99
|
# Check if FastAPI app already has a lifespan handler
|
|
126
100
|
existing_lifespan = getattr(fastapi_app, "router.lifespan_context", None)
|
|
127
|
-
|
|
101
|
+
|
|
128
102
|
if existing_lifespan is not None:
|
|
129
103
|
logger.warning(
|
|
130
104
|
f"⚠️ FastAPI app already has lifespan handler - "
|
|
@@ -144,4 +118,4 @@ def integrate_api_heartbeat_with_fastapi(
|
|
|
144
118
|
f"❌ Failed to integrate API heartbeat with FastAPI lifespan "
|
|
145
119
|
f"for service '{service_id}': {e}"
|
|
146
120
|
)
|
|
147
|
-
raise
|
|
121
|
+
raise
|