mcp-mesh 0.6.4__py3-none-any.whl → 0.7.1__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/decorator_registry.py +50 -11
- _mcp_mesh/engine/http_wrapper.py +10 -2
- _mcp_mesh/engine/mesh_llm_agent.py +98 -6
- _mcp_mesh/engine/unified_mcp_proxy.py +10 -2
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +82 -100
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +150 -96
- _mcp_mesh/pipeline/api_startup/route_integration.py +91 -92
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +7 -0
- _mcp_mesh/tracing/execution_tracer.py +41 -13
- {mcp_mesh-0.6.4.dist-info → mcp_mesh-0.7.1.dist-info}/METADATA +1 -1
- {mcp_mesh-0.6.4.dist-info → mcp_mesh-0.7.1.dist-info}/RECORD +15 -15
- mesh/decorators.py +43 -0
- {mcp_mesh-0.6.4.dist-info → mcp_mesh-0.7.1.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.6.4.dist-info → mcp_mesh-0.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
|
+
from ...engine.decorator_registry import DecoratorRegistry
|
|
4
5
|
from ...engine.dependency_injector import get_global_injector
|
|
5
6
|
from ..shared import PipelineResult, PipelineStatus, PipelineStep
|
|
6
7
|
|
|
@@ -8,11 +9,11 @@ from ..shared import PipelineResult, PipelineStatus, PipelineStep
|
|
|
8
9
|
class RouteIntegrationStep(PipelineStep):
|
|
9
10
|
"""
|
|
10
11
|
Integrates dependency injection into FastAPI route handlers.
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
This step takes the discovered FastAPI apps and @mesh.route decorated handlers,
|
|
13
14
|
then applies dependency injection by replacing the route.endpoint with a
|
|
14
15
|
dependency injection wrapper.
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
Uses the existing dependency injection engine from MCP tools - route handlers
|
|
17
18
|
are just functions, so the same injection logic applies perfectly.
|
|
18
19
|
"""
|
|
@@ -34,13 +35,13 @@ class RouteIntegrationStep(PipelineStep):
|
|
|
34
35
|
# Get discovery results from context
|
|
35
36
|
fastapi_apps = context.get("fastapi_apps", {})
|
|
36
37
|
route_mapping = context.get("route_mapping", {})
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
if not fastapi_apps:
|
|
39
40
|
result.status = PipelineStatus.SKIPPED
|
|
40
41
|
result.message = "No FastAPI applications found"
|
|
41
42
|
self.logger.warning("⚠️ No FastAPI applications to integrate")
|
|
42
43
|
return result
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
if not route_mapping:
|
|
45
46
|
result.status = PipelineStatus.SKIPPED
|
|
46
47
|
result.message = "No @mesh.route handlers found"
|
|
@@ -50,26 +51,26 @@ class RouteIntegrationStep(PipelineStep):
|
|
|
50
51
|
# Apply dependency injection to each app's routes
|
|
51
52
|
integration_results = {}
|
|
52
53
|
total_integrated = 0
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
for app_id, app_info in fastapi_apps.items():
|
|
55
56
|
if app_id not in route_mapping:
|
|
56
57
|
continue
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
app_results = self._integrate_app_routes(
|
|
59
60
|
app_info, route_mapping[app_id]
|
|
60
61
|
)
|
|
61
62
|
integration_results[app_id] = app_results
|
|
62
63
|
total_integrated += app_results["integrated_count"]
|
|
63
|
-
|
|
64
|
-
self.logger.
|
|
65
|
-
f"
|
|
64
|
+
|
|
65
|
+
self.logger.debug(
|
|
66
|
+
f"Integrated {app_results['integrated_count']} routes in "
|
|
66
67
|
f"'{app_info['title']}'"
|
|
67
68
|
)
|
|
68
69
|
|
|
69
70
|
# Store integration results in context
|
|
70
71
|
result.add_context("integration_results", integration_results)
|
|
71
72
|
result.add_context("total_integrated_routes", total_integrated)
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
# Update result message
|
|
74
75
|
result.message = f"Integrated {total_integrated} route handlers with dependency injection"
|
|
75
76
|
|
|
@@ -86,22 +87,22 @@ class RouteIntegrationStep(PipelineStep):
|
|
|
86
87
|
return result
|
|
87
88
|
|
|
88
89
|
def _integrate_app_routes(
|
|
89
|
-
self, app_info:
|
|
90
|
-
) ->
|
|
90
|
+
self, app_info: dict[str, Any], route_mapping: dict[str, Any]
|
|
91
|
+
) -> dict[str, Any]:
|
|
91
92
|
"""
|
|
92
93
|
Apply dependency injection to routes in a single FastAPI app.
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
Args:
|
|
95
96
|
app_info: FastAPI app information from discovery
|
|
96
97
|
route_mapping: Route mapping for this specific app
|
|
97
|
-
|
|
98
|
+
|
|
98
99
|
Returns:
|
|
99
100
|
Integration results for this app
|
|
100
101
|
"""
|
|
101
102
|
app = app_info["instance"]
|
|
102
103
|
app_title = app_info["title"]
|
|
103
104
|
injector = get_global_injector()
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
integration_results = {
|
|
106
107
|
"app_title": app_title,
|
|
107
108
|
"integrated_count": 0,
|
|
@@ -109,45 +110,41 @@ class RouteIntegrationStep(PipelineStep):
|
|
|
109
110
|
"error_count": 0,
|
|
110
111
|
"route_details": {},
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
# Process each @mesh.route decorated handler
|
|
114
115
|
for route_name, route_info in route_mapping.items():
|
|
115
116
|
try:
|
|
116
|
-
result_detail = self._integrate_single_route(
|
|
117
|
-
app, route_info, injector
|
|
118
|
-
)
|
|
117
|
+
result_detail = self._integrate_single_route(app, route_info, injector)
|
|
119
118
|
integration_results["route_details"][route_name] = result_detail
|
|
120
|
-
|
|
119
|
+
|
|
121
120
|
if result_detail["status"] == "integrated":
|
|
122
121
|
integration_results["integrated_count"] += 1
|
|
123
122
|
elif result_detail["status"] == "skipped":
|
|
124
123
|
integration_results["skipped_count"] += 1
|
|
125
124
|
else:
|
|
126
125
|
integration_results["error_count"] += 1
|
|
127
|
-
|
|
126
|
+
|
|
128
127
|
except Exception as e:
|
|
129
|
-
self.logger.error(
|
|
130
|
-
f"❌ Failed to integrate route '{route_name}': {e}"
|
|
131
|
-
)
|
|
128
|
+
self.logger.error(f"❌ Failed to integrate route '{route_name}': {e}")
|
|
132
129
|
integration_results["error_count"] += 1
|
|
133
130
|
integration_results["route_details"][route_name] = {
|
|
134
131
|
"status": "error",
|
|
135
|
-
"error": str(e)
|
|
132
|
+
"error": str(e),
|
|
136
133
|
}
|
|
137
|
-
|
|
134
|
+
|
|
138
135
|
return integration_results
|
|
139
136
|
|
|
140
137
|
def _integrate_single_route(
|
|
141
|
-
self, app, route_info:
|
|
142
|
-
) ->
|
|
138
|
+
self, app, route_info: dict[str, Any], injector
|
|
139
|
+
) -> dict[str, Any]:
|
|
143
140
|
"""
|
|
144
141
|
Apply dependency injection to a single route handler.
|
|
145
|
-
|
|
142
|
+
|
|
146
143
|
Args:
|
|
147
144
|
app: FastAPI application instance
|
|
148
145
|
route_info: Route information including dependencies
|
|
149
146
|
injector: Dependency injector instance
|
|
150
|
-
|
|
147
|
+
|
|
151
148
|
Returns:
|
|
152
149
|
Integration result details
|
|
153
150
|
"""
|
|
@@ -156,163 +153,165 @@ class RouteIntegrationStep(PipelineStep):
|
|
|
156
153
|
dependencies = route_info["dependencies"]
|
|
157
154
|
path = route_info["path"]
|
|
158
155
|
methods = route_info["methods"]
|
|
159
|
-
|
|
156
|
+
|
|
160
157
|
# Extract dependency names for injector
|
|
161
158
|
dependency_names = [dep["capability"] for dep in dependencies]
|
|
162
|
-
|
|
159
|
+
|
|
163
160
|
self.logger.debug(
|
|
164
|
-
f"
|
|
161
|
+
f"Integrating route {methods} {path} -> {endpoint_name}() "
|
|
165
162
|
f"with dependencies: {dependency_names}"
|
|
166
163
|
)
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
|
|
169
165
|
# Skip if no dependencies
|
|
170
166
|
if not dependency_names:
|
|
171
|
-
self.logger.debug(f"
|
|
167
|
+
self.logger.debug(f"Route '{endpoint_name}' has no dependencies, skipping")
|
|
172
168
|
return {
|
|
173
169
|
"status": "skipped",
|
|
174
170
|
"reason": "no_dependencies",
|
|
175
|
-
"dependency_count": 0
|
|
171
|
+
"dependency_count": 0,
|
|
176
172
|
}
|
|
177
|
-
|
|
173
|
+
|
|
178
174
|
# Check if function already has an injection wrapper (from @mesh.route decorator)
|
|
179
175
|
# The function might be the wrapper itself (if decorator order is correct)
|
|
180
|
-
is_already_wrapper = getattr(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
self.logger.debug(
|
|
184
|
-
f"🔍 Checking function {original_handler} at {hex(id(original_handler))}: "
|
|
185
|
-
f"is_wrapper={is_already_wrapper}, has_wrapper_ref={'yes' if existing_wrapper else 'no'}"
|
|
176
|
+
is_already_wrapper = getattr(
|
|
177
|
+
original_handler, "_mesh_is_injection_wrapper", False
|
|
186
178
|
)
|
|
187
|
-
|
|
179
|
+
existing_wrapper = getattr(original_handler, "_mesh_injection_wrapper", None)
|
|
180
|
+
|
|
188
181
|
if is_already_wrapper:
|
|
189
182
|
self.logger.debug(
|
|
190
|
-
f"
|
|
183
|
+
f"Function '{endpoint_name}' is already an injection wrapper from @mesh.route decorator"
|
|
191
184
|
)
|
|
192
185
|
wrapped_handler = original_handler # Use the function as-is
|
|
193
186
|
elif existing_wrapper:
|
|
194
|
-
self.logger.debug(f"🔍 Existing wrapper: {existing_wrapper} at {hex(id(existing_wrapper))}")
|
|
195
187
|
self.logger.debug(
|
|
196
|
-
f"
|
|
188
|
+
f"Route '{endpoint_name}' already has injection wrapper from @mesh.route decorator, using existing wrapper"
|
|
197
189
|
)
|
|
198
190
|
wrapped_handler = existing_wrapper
|
|
199
191
|
else:
|
|
200
192
|
# Create dependency injection wrapper using existing engine
|
|
201
193
|
self.logger.debug(
|
|
202
|
-
f"
|
|
194
|
+
f"Creating new injection wrapper for route '{endpoint_name}'"
|
|
203
195
|
)
|
|
204
196
|
try:
|
|
205
197
|
wrapped_handler = injector.create_injection_wrapper(
|
|
206
198
|
original_handler, dependency_names
|
|
207
199
|
)
|
|
208
|
-
|
|
200
|
+
|
|
209
201
|
# Preserve original handler metadata on wrapper
|
|
210
202
|
wrapped_handler._mesh_route_metadata = getattr(
|
|
211
|
-
original_handler,
|
|
203
|
+
original_handler, "_mesh_route_metadata", {}
|
|
212
204
|
)
|
|
213
205
|
wrapped_handler._original_handler = original_handler
|
|
214
206
|
wrapped_handler._mesh_dependencies = dependency_names
|
|
215
207
|
except Exception as e:
|
|
216
|
-
self.logger.error(
|
|
208
|
+
self.logger.error(
|
|
209
|
+
f"Failed to create injection wrapper for {endpoint_name}: {e}"
|
|
210
|
+
)
|
|
217
211
|
return {
|
|
218
212
|
"status": "failed",
|
|
219
213
|
"reason": f"wrapper_creation_failed: {e}",
|
|
220
|
-
"dependency_count": len(dependency_names)
|
|
214
|
+
"dependency_count": len(dependency_names),
|
|
221
215
|
}
|
|
222
|
-
|
|
216
|
+
|
|
223
217
|
# CRITICAL FIX: Check if there are multiple wrapper instances for this function
|
|
224
218
|
# If so, use the one that actually receives dependency updates
|
|
225
219
|
from ...engine.dependency_injector import get_global_injector
|
|
220
|
+
|
|
226
221
|
injector = get_global_injector()
|
|
227
|
-
|
|
222
|
+
|
|
228
223
|
# Find all functions that depend on the first dependency of this route
|
|
229
224
|
if dependency_names:
|
|
230
|
-
first_dep = dependency_names[
|
|
225
|
+
first_dep = dependency_names[
|
|
226
|
+
0
|
|
227
|
+
] # Use first dependency to find all instances
|
|
231
228
|
affected_functions = injector._dependency_mapping.get(first_dep, set())
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
|
|
234
230
|
# Check if there are multiple instances and if so, prefer the one that's NOT __main__
|
|
235
231
|
if len(affected_functions) > 1:
|
|
236
|
-
non_main_functions = [
|
|
232
|
+
non_main_functions = [
|
|
233
|
+
f for f in affected_functions if not f.startswith("__main__.")
|
|
234
|
+
]
|
|
237
235
|
if non_main_functions:
|
|
238
236
|
# Found a non-main instance, try to get that wrapper instead
|
|
239
237
|
preferred_func_id = non_main_functions[0] # Take first non-main
|
|
240
|
-
preferred_wrapper = injector._function_registry.get(
|
|
238
|
+
preferred_wrapper = injector._function_registry.get(
|
|
239
|
+
preferred_func_id
|
|
240
|
+
)
|
|
241
241
|
if preferred_wrapper:
|
|
242
|
-
self.logger.debug(
|
|
243
|
-
f"🔄 SWITCHING to preferred wrapper '{preferred_func_id}': "
|
|
244
|
-
f"{preferred_wrapper} at {hex(id(preferred_wrapper))}"
|
|
245
|
-
)
|
|
246
242
|
wrapped_handler = preferred_wrapper
|
|
247
|
-
|
|
243
|
+
|
|
244
|
+
# Register the route wrapper in DecoratorRegistry for path-based dependency resolution
|
|
245
|
+
# This creates a mapping from METHOD:path -> wrapper function
|
|
246
|
+
for method in methods:
|
|
247
|
+
DecoratorRegistry.register_route_wrapper(
|
|
248
|
+
method=method,
|
|
249
|
+
path=path,
|
|
250
|
+
wrapper=wrapped_handler,
|
|
251
|
+
dependencies=dependency_names,
|
|
252
|
+
)
|
|
253
|
+
|
|
248
254
|
# Find and replace the route handler in FastAPI
|
|
249
255
|
route_replaced = self._replace_route_handler(
|
|
250
256
|
app, path, methods, original_handler, wrapped_handler
|
|
251
257
|
)
|
|
252
|
-
|
|
258
|
+
|
|
253
259
|
if route_replaced:
|
|
254
260
|
self.logger.debug(
|
|
255
|
-
f"
|
|
256
|
-
f"{len(dependency_names)} dependencies"
|
|
261
|
+
f"Route '{endpoint_name}' integrated with {len(dependency_names)} dependencies"
|
|
257
262
|
)
|
|
258
263
|
return {
|
|
259
264
|
"status": "integrated",
|
|
260
265
|
"dependency_count": len(dependency_names),
|
|
261
266
|
"dependencies": dependency_names,
|
|
262
267
|
"original_handler": original_handler,
|
|
263
|
-
"wrapped_handler": wrapped_handler
|
|
268
|
+
"wrapped_handler": wrapped_handler,
|
|
264
269
|
}
|
|
265
270
|
else:
|
|
266
271
|
self.logger.warning(
|
|
267
272
|
f"⚠️ Failed to find route to replace for '{endpoint_name}'"
|
|
268
273
|
)
|
|
269
|
-
return {
|
|
270
|
-
"status": "error",
|
|
271
|
-
"error": "route_not_found_for_replacement"
|
|
272
|
-
}
|
|
274
|
+
return {"status": "error", "error": "route_not_found_for_replacement"}
|
|
273
275
|
|
|
274
276
|
def _replace_route_handler(
|
|
275
277
|
self, app, path: str, methods: list, original_handler, wrapped_handler
|
|
276
278
|
) -> bool:
|
|
277
279
|
"""
|
|
278
280
|
Replace the route handler in FastAPI's router.
|
|
279
|
-
|
|
281
|
+
|
|
280
282
|
Args:
|
|
281
283
|
app: FastAPI application instance
|
|
282
284
|
path: Route path to find
|
|
283
285
|
methods: HTTP methods for the route
|
|
284
286
|
original_handler: Original handler function
|
|
285
287
|
wrapped_handler: New wrapped handler function
|
|
286
|
-
|
|
288
|
+
|
|
287
289
|
Returns:
|
|
288
290
|
True if replacement was successful, False otherwise
|
|
289
291
|
"""
|
|
290
292
|
try:
|
|
291
293
|
# Find the matching route in FastAPI's router
|
|
292
294
|
for route in app.router.routes:
|
|
293
|
-
if (
|
|
294
|
-
hasattr(route,
|
|
295
|
-
hasattr(route,
|
|
296
|
-
|
|
295
|
+
if (
|
|
296
|
+
hasattr(route, "endpoint")
|
|
297
|
+
and hasattr(route, "path")
|
|
298
|
+
and hasattr(route, "methods")
|
|
299
|
+
):
|
|
300
|
+
|
|
297
301
|
# Match by path and endpoint function
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
if route.path == path and route.endpoint is original_handler:
|
|
303
|
+
|
|
301
304
|
# Replace the endpoint with our wrapped version
|
|
302
305
|
route.endpoint = wrapped_handler
|
|
303
|
-
|
|
304
|
-
self.logger.debug(
|
|
305
|
-
f"🔄 Replaced handler for {methods} {path}: "
|
|
306
|
-
f"{original_handler.__name__} -> wrapped version"
|
|
307
|
-
)
|
|
306
|
+
|
|
308
307
|
return True
|
|
309
|
-
|
|
308
|
+
|
|
310
309
|
# If we get here, we didn't find the route
|
|
311
310
|
self.logger.warning(
|
|
312
|
-
f"
|
|
311
|
+
f"Could not find route {methods} {path} to replace handler"
|
|
313
312
|
)
|
|
314
313
|
return False
|
|
315
|
-
|
|
314
|
+
|
|
316
315
|
except Exception as e:
|
|
317
316
|
self.logger.error(f"❌ Error replacing route handler: {e}")
|
|
318
|
-
return False
|
|
317
|
+
return False
|
|
@@ -422,6 +422,10 @@ class FastAPIServerSetupStep(PipelineStep):
|
|
|
422
422
|
except ImportError as e:
|
|
423
423
|
raise Exception(f"FastAPI not available: {e}")
|
|
424
424
|
|
|
425
|
+
# Note: Trace context middleware for distributed tracing is added in
|
|
426
|
+
# mesh/decorators.py BEFORE the FastAPI app starts. This ensures the middleware
|
|
427
|
+
# is properly registered since middleware cannot be added after the app starts.
|
|
428
|
+
|
|
425
429
|
async def _add_k8s_endpoints(
|
|
426
430
|
self,
|
|
427
431
|
app: Any,
|
|
@@ -890,6 +894,9 @@ mcp_mesh_up{{agent="{agent_name}"}} 1
|
|
|
890
894
|
"✅ SERVER REUSE: FastMCP lifespan already integrated, mounting same HTTP app"
|
|
891
895
|
)
|
|
892
896
|
|
|
897
|
+
# Note: Trace context middleware is added in decorators.py BEFORE the app starts
|
|
898
|
+
# We cannot add middleware after the application has started
|
|
899
|
+
|
|
893
900
|
# FastMCP lifespan is already integrated, mount the same HTTP app that was used for lifespan
|
|
894
901
|
for server_key, server_instance in fastmcp_servers.items():
|
|
895
902
|
try:
|
|
@@ -26,8 +26,8 @@ class ExecutionTracer:
|
|
|
26
26
|
def __init__(self, function_name: str, logger_instance: logging.Logger):
|
|
27
27
|
self.function_name = function_name
|
|
28
28
|
self.logger = logger_instance
|
|
29
|
-
self.start_time:
|
|
30
|
-
self.trace_context:
|
|
29
|
+
self.start_time: float | None = None
|
|
30
|
+
self.trace_context: Any | None = None
|
|
31
31
|
self.execution_metadata: dict = {}
|
|
32
32
|
|
|
33
33
|
def start_execution(
|
|
@@ -41,6 +41,7 @@ class ExecutionTracer:
|
|
|
41
41
|
"""Start execution tracking and log function start."""
|
|
42
42
|
try:
|
|
43
43
|
from .context import TraceContext
|
|
44
|
+
from .utils import generate_trace_id
|
|
44
45
|
|
|
45
46
|
self.start_time = time.time()
|
|
46
47
|
self.trace_context = TraceContext.get_current()
|
|
@@ -60,31 +61,58 @@ class ExecutionTracer:
|
|
|
60
61
|
agent_metadata = get_agent_metadata_with_fallback(self.logger)
|
|
61
62
|
self.execution_metadata.update(agent_metadata)
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Keep the same trace_id but create unique span_id per function call
|
|
66
|
-
function_span_id = generate_span_id()
|
|
67
|
-
|
|
64
|
+
# Generate a new child span ID for this function execution
|
|
65
|
+
function_span_id = generate_span_id()
|
|
68
66
|
|
|
67
|
+
if self.trace_context:
|
|
68
|
+
# Have trace context - use existing trace_id, create child span
|
|
69
|
+
# Current trace's span_id becomes this function's parent_span
|
|
69
70
|
self.execution_metadata.update(
|
|
70
71
|
{
|
|
71
72
|
"trace_id": self.trace_context.trace_id,
|
|
72
73
|
"span_id": function_span_id, # New child span for this function
|
|
73
|
-
"parent_span":
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
"parent_span": self.trace_context.span_id, # Parent's span becomes our parent
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Update TraceContext for nested calls - this span becomes the new current span
|
|
79
|
+
TraceContext.set_current(
|
|
80
|
+
trace_id=self.trace_context.trace_id,
|
|
81
|
+
span_id=function_span_id,
|
|
82
|
+
parent_span=self.trace_context.span_id,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
# No trace context (FastMCP context propagation issue) - generate root trace
|
|
86
|
+
# This ensures traces always have IDs even when contextvar propagation fails
|
|
87
|
+
root_trace_id = generate_trace_id()
|
|
88
|
+
self.execution_metadata.update(
|
|
89
|
+
{
|
|
90
|
+
"trace_id": root_trace_id,
|
|
91
|
+
"span_id": function_span_id,
|
|
92
|
+
"parent_span": None, # Root span has no parent
|
|
78
93
|
}
|
|
79
94
|
)
|
|
80
95
|
|
|
96
|
+
# CRITICAL: Set the TraceContext so outgoing cross-agent calls can propagate it
|
|
97
|
+
# Without this, inject_trace_headers_to_request() won't find the trace context
|
|
98
|
+
# and cross-agent traces won't be linked with parent_span
|
|
99
|
+
TraceContext.set_current(
|
|
100
|
+
trace_id=root_trace_id,
|
|
101
|
+
span_id=function_span_id,
|
|
102
|
+
parent_span=None,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
self.logger.debug(
|
|
106
|
+
f"Generated root trace for {self.function_name}: trace_id={root_trace_id}"
|
|
107
|
+
)
|
|
108
|
+
|
|
81
109
|
except Exception as e:
|
|
82
110
|
self.logger.warning(
|
|
83
111
|
f"Failed to setup execution logging for {self.function_name}: {e}"
|
|
84
112
|
)
|
|
85
113
|
|
|
86
114
|
def end_execution(
|
|
87
|
-
self, result: Any = None, success: bool = True, error:
|
|
115
|
+
self, result: Any = None, success: bool = True, error: str | None = None
|
|
88
116
|
) -> None:
|
|
89
117
|
"""End execution tracking and log function completion."""
|
|
90
118
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-mesh
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
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
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
_mcp_mesh/__init__.py,sha256=
|
|
1
|
+
_mcp_mesh/__init__.py,sha256=L1BUnhnz_Jt2PYiHAa2DEJQNAaVnKGiUB59l5zOtpxM,2719
|
|
2
2
|
_mcp_mesh/engine/__init__.py,sha256=2ennzbo7yJcpkXO9BqN69TruLjJfmJY4Y5VEsG644K4,3630
|
|
3
3
|
_mcp_mesh/engine/async_mcp_client.py,sha256=UcbQjxtgVfeRw6DHTZhAzN1gkcKlTg-lUPEePRPQWAU,6306
|
|
4
4
|
_mcp_mesh/engine/base_injector.py,sha256=qzRLZqFP2VvEFagVovkpdldvDmm3VwPHm6tHwV58a2k,5648
|
|
5
|
-
_mcp_mesh/engine/decorator_registry.py,sha256=
|
|
5
|
+
_mcp_mesh/engine/decorator_registry.py,sha256=zXuER3Sh-4NpqlLMtTxSxn93xBcZTDdEEjKWSCauU2U,28206
|
|
6
6
|
_mcp_mesh/engine/dependency_injector.py,sha256=1bjeJ7pHUPEF_IoTF-7_Wm1pDLHphtfcFfSrUPWrWI4,31230
|
|
7
7
|
_mcp_mesh/engine/full_mcp_proxy.py,sha256=PlRv7GSKqn5riOCqeCVulVdtq3z1Ug76mOkwMsOFHXw,25297
|
|
8
|
-
_mcp_mesh/engine/http_wrapper.py,sha256=
|
|
8
|
+
_mcp_mesh/engine/http_wrapper.py,sha256=OHbbxHBLyUGDoamHZ2hpYnFKapW_djQ60Y_vMOL6J70,21173
|
|
9
9
|
_mcp_mesh/engine/llm_config.py,sha256=95bOsGWro5E1JGq7oZtEYhVdrzcIJqjht_r5vEdJVz4,2049
|
|
10
10
|
_mcp_mesh/engine/llm_errors.py,sha256=h7BiI14u-jL8vtvBfFbFDDrN7gIw8PQjXIl5AP1SBuA,3276
|
|
11
11
|
_mcp_mesh/engine/mcp_client_proxy.py,sha256=eJStwy_VQJexYYD8bOh_m4Ld3Bb8Ae_dt8N1CC41qBc,17625
|
|
12
|
-
_mcp_mesh/engine/mesh_llm_agent.py,sha256=
|
|
12
|
+
_mcp_mesh/engine/mesh_llm_agent.py,sha256=AMW6Tu64aujFYwM0p0ZSd9YYaOikKZUN3utnqI0kT38,29015
|
|
13
13
|
_mcp_mesh/engine/mesh_llm_agent_injector.py,sha256=isufzCBExli8tdLUZOaPuea3uQs3C_yeVXbOVSF0YIU,27270
|
|
14
14
|
_mcp_mesh/engine/response_parser.py,sha256=NsOuGD7HJ0BFiiDUCp9v9cjLzVaU86HShVKzsrNnulk,8786
|
|
15
15
|
_mcp_mesh/engine/self_dependency_proxy.py,sha256=OkKt0-B_ADnJlWtHiHItoZCBZ7Su0iz2unEPFfXvrs4,3302
|
|
@@ -18,7 +18,7 @@ _mcp_mesh/engine/session_manager.py,sha256=MCr0_fXBaUjXM51WU5EhDkiGvBdfzYQFVNb9D
|
|
|
18
18
|
_mcp_mesh/engine/signature_analyzer.py,sha256=ftn9XsX0ZHWIaACdjgBVtCuIdqVU_4ST8cvcpzu4HTk,12339
|
|
19
19
|
_mcp_mesh/engine/tool_executor.py,sha256=Bf_9d02EEY9_yHm1p1-5YZ4rY6MPxn4SVpI6-3sm1uo,5456
|
|
20
20
|
_mcp_mesh/engine/tool_schema_builder.py,sha256=SQCxQIrSfdLu9-dLqiFurQLK7dhl0dc0xa0ibaxU-iE,3644
|
|
21
|
-
_mcp_mesh/engine/unified_mcp_proxy.py,sha256=
|
|
21
|
+
_mcp_mesh/engine/unified_mcp_proxy.py,sha256=RIkYQGf04xxKui4kLa6N-L7KvH7XTrOy6I2AeMY9imY,36762
|
|
22
22
|
_mcp_mesh/engine/provider_handlers/__init__.py,sha256=LLTCOgnuM3dlogbLmrpiMK3oB5L22eAmDC4BfxJ-L2I,593
|
|
23
23
|
_mcp_mesh/engine/provider_handlers/base_provider_handler.py,sha256=J-SPFFFG1eFSUVvfsv7y4EuNM4REjSxaYWC5E_lC6Pc,4195
|
|
24
24
|
_mcp_mesh/engine/provider_handlers/claude_handler.py,sha256=CCmlsWiCfIcgrLbAZzeSnl0g2pq0uDffT8zOj4F-sPQ,15727
|
|
@@ -78,12 +78,12 @@ _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py,s
|
|
|
78
78
|
_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py,sha256=9Q_8WaVl0MxswRnHpkqq9GKnvOW54HW4tkrTM9oda14,4461
|
|
79
79
|
_mcp_mesh/pipeline/__init__.py,sha256=9Aplh4m1z-rYTQys0JQLYlq9wTPdI72eSOhUPqcnvpA,1557
|
|
80
80
|
_mcp_mesh/pipeline/api_heartbeat/__init__.py,sha256=IXTLoQLAPqQEWZ8VMWc5W_cQJkDv95rlVGXyXoQDjHk,473
|
|
81
|
-
_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py,sha256=
|
|
81
|
+
_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py,sha256=IvkvVQNyZr0Uqe2lViRpdj9kaLrxtTJ8io-gwjtK5FI,23247
|
|
82
82
|
_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py,sha256=PY4bbuZgxy3r0ccuBl-OuJvcPSMhyGz4FomxwYFhuvM,4821
|
|
83
83
|
_mcp_mesh/pipeline/api_heartbeat/api_health_check.py,sha256=kDmFeOG_4tyqyJSBZjPcc7xTzGpP4vq6ObW_WBqXvzM,5130
|
|
84
84
|
_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py,sha256=uBswzWOBzU8p_C0AE2DF8UwIWG4rP2zecHfPqKzNuC0,10367
|
|
85
85
|
_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py,sha256=so8IyKT-Wg7lQk3ULdox9CAOrowzxpUs76e93PQnCik,13520
|
|
86
|
-
_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py,sha256=
|
|
86
|
+
_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py,sha256=vszesutlAFXv9B4XXFutEMEBBhN54hF8eztTtDudLaI,15785
|
|
87
87
|
_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py,sha256=WBo2crcaGfxi8Q46TU-i5OMhAv0sQKz7Z9jps-GLkvM,5183
|
|
88
88
|
_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py,sha256=6N0JdXdnLkaXau4t8syt9DLgv9Y51SPfTXYK3DefBk8,3846
|
|
89
89
|
_mcp_mesh/pipeline/api_startup/__init__.py,sha256=eivolkSKot2bJTWP2BV8-RKRT1Zm7SGQYuEUiTxusOQ,577
|
|
@@ -92,7 +92,7 @@ _mcp_mesh/pipeline/api_startup/api_server_setup.py,sha256=Qy0wbXyIWIQYA7CjiGVZwn
|
|
|
92
92
|
_mcp_mesh/pipeline/api_startup/fastapi_discovery.py,sha256=MV3hvDXvX7r1Mrn6LAReu9W3hKt-5-jDhPpPYwZXnco,5770
|
|
93
93
|
_mcp_mesh/pipeline/api_startup/middleware_integration.py,sha256=ybImXZlmIR6yA-wYg5Zy_ZMFF9YgToLkk4jnBeZJ7WY,6267
|
|
94
94
|
_mcp_mesh/pipeline/api_startup/route_collection.py,sha256=UjA-F5_RbGVU5TfDT19Np5_x2PtYkNn2mGFyivDsk24,2031
|
|
95
|
-
_mcp_mesh/pipeline/api_startup/route_integration.py,sha256=
|
|
95
|
+
_mcp_mesh/pipeline/api_startup/route_integration.py,sha256=qq1AVaWna-CWEXyehyDL3EyeYKgo5aMtei8uBNdvkZ8,12448
|
|
96
96
|
_mcp_mesh/pipeline/mcp_heartbeat/__init__.py,sha256=nRNjZ3VD_9bPLQuJ6Nc02gE7KSLcMP7TMquB0hP6hHs,844
|
|
97
97
|
_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py,sha256=vW-qrpneBLxxQtUEwEjE7aUTv5cIO9rjDg3Bxv7nj4I,18846
|
|
98
98
|
_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py,sha256=2SKHHFTxlYwad_D8a6E7NNtWfH89jBrIO5dQAwM3Xdw,4468
|
|
@@ -105,7 +105,7 @@ _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py,sha256=4abbOKN3echwX82PV
|
|
|
105
105
|
_mcp_mesh/pipeline/mcp_startup/__init__.py,sha256=gS0xNmVx66bkLUMw64olMsN40ZLPH3ymwlLixZ4NuTs,1239
|
|
106
106
|
_mcp_mesh/pipeline/mcp_startup/configuration.py,sha256=6LRLIxrqFMU76qrBb6GjGknUlKPZZ9iqOlxE7F9ZhLs,2808
|
|
107
107
|
_mcp_mesh/pipeline/mcp_startup/decorator_collection.py,sha256=RHC6MHtfP9aP0hZ-IJjISZu72e0Pml3LU0qr7dc284w,2294
|
|
108
|
-
_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py,sha256=
|
|
108
|
+
_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py,sha256=O3q3ZNZQsNZ0pL5SkkgQoOxfofzD202H0boshiT2FzI,44074
|
|
109
109
|
_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py,sha256=Pm24wrSuRGsgeUrHvMPDnNh6RhIZoznnMAUwAkllohk,10661
|
|
110
110
|
_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py,sha256=v85B0ynomvYu87eIvLe-aSZ7-Iwov2VtM4Fg3PkmrZs,3865
|
|
111
111
|
_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py,sha256=sOpzxRc0kYiXwSW9lvv8DSjliT85oZCWPODeJRuiqgg,15635
|
|
@@ -133,17 +133,17 @@ _mcp_mesh/shared/sse_parser.py,sha256=OEPnfL9xL3rsjQrbyvfUO82WljPSDeO6Z61uUwN1NA
|
|
|
133
133
|
_mcp_mesh/shared/support_types.py,sha256=k-ICF_UwDkHxQ1D5LwFZrp-UrNb4E5dzw02CRuLW9iI,7264
|
|
134
134
|
_mcp_mesh/tracing/agent_context_helper.py,sha256=BIJ3Kc4Znd6emMAu97aUhSoxSIza3qYUmObLgc9ONiA,4765
|
|
135
135
|
_mcp_mesh/tracing/context.py,sha256=2ozqKEYfx4Qxj64DnbwoVIbMkhNLbaV8BNWtkzAPA7I,2516
|
|
136
|
-
_mcp_mesh/tracing/execution_tracer.py,sha256=
|
|
136
|
+
_mcp_mesh/tracing/execution_tracer.py,sha256=H28EhZPb7Lyz2fG_toAbsQ7_zKANFxyBsIFlWBZg4FY,9167
|
|
137
137
|
_mcp_mesh/tracing/fastapi_tracing_middleware.py,sha256=o-xyAb1hB_GIFXv0hqUeTwhDDEoFj3_brygmhSComkE,6848
|
|
138
138
|
_mcp_mesh/tracing/redis_metadata_publisher.py,sha256=F78E34qnI3D0tOmbHUTBsLbDst2G7Su2-0F37Rq0rcM,4652
|
|
139
139
|
_mcp_mesh/tracing/trace_context_helper.py,sha256=6tEkwjWFqMBe45zBlhacktmIpzJWTF950ph3bwL3cNc,5994
|
|
140
140
|
_mcp_mesh/tracing/utils.py,sha256=t9lJuTH7CeuzAiiAaD0WxsJMFJPdzZFR0w6-vyR9f2E,3849
|
|
141
141
|
_mcp_mesh/utils/fastmcp_schema_extractor.py,sha256=M54ffesC-56zl_fNJHj9dZxElDQaWFf1MXdSLCuFStg,17253
|
|
142
142
|
mesh/__init__.py,sha256=0zequaBtd_9NLOLsr9sNONuwWa_fT_-G4LnJ1CHTEY0,3808
|
|
143
|
-
mesh/decorators.py,sha256=
|
|
143
|
+
mesh/decorators.py,sha256=_3yVrEvGHZ5MKX_pf7Zn-vLdOH68iE7o6EIvxKcGOds,57636
|
|
144
144
|
mesh/helpers.py,sha256=c3FhSy9U4KBHEH6WH6MjCVrPMw9li5JAgBLUTIoamz4,9472
|
|
145
145
|
mesh/types.py,sha256=9TqbJSxlybLQaPVjugcKwPiIrVnJEzqAOvPRhlX1zmo,15559
|
|
146
|
-
mcp_mesh-0.
|
|
147
|
-
mcp_mesh-0.
|
|
148
|
-
mcp_mesh-0.
|
|
149
|
-
mcp_mesh-0.
|
|
146
|
+
mcp_mesh-0.7.1.dist-info/METADATA,sha256=PB20crMnSTNChuIte5_lRMnoKMY5XBWtW_KEj4L_Qqg,4972
|
|
147
|
+
mcp_mesh-0.7.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
148
|
+
mcp_mesh-0.7.1.dist-info/licenses/LICENSE,sha256=_EBQHRQThv9FPOLc5eFOUdeeRO0mYwChC7cx60dM1tM,1078
|
|
149
|
+
mcp_mesh-0.7.1.dist-info/RECORD,,
|
mesh/decorators.py
CHANGED
|
@@ -76,6 +76,49 @@ def _start_uvicorn_immediately(http_host: str, http_port: int):
|
|
|
76
76
|
app = FastAPI(title="MCP Mesh Agent (Starting)")
|
|
77
77
|
logger.debug("📦 IMMEDIATE UVICORN: Created minimal FastAPI app")
|
|
78
78
|
|
|
79
|
+
# Add trace context middleware for distributed tracing BEFORE app starts
|
|
80
|
+
# This must be done before uvicorn.run() since middleware can't be added after start
|
|
81
|
+
try:
|
|
82
|
+
import os
|
|
83
|
+
|
|
84
|
+
tracing_enabled = os.getenv(
|
|
85
|
+
"MCP_MESH_DISTRIBUTED_TRACING_ENABLED", "false"
|
|
86
|
+
).lower() in ("true", "1", "yes")
|
|
87
|
+
if tracing_enabled:
|
|
88
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
89
|
+
from starlette.requests import Request
|
|
90
|
+
|
|
91
|
+
class TraceContextMiddleware(BaseHTTPMiddleware):
|
|
92
|
+
"""Middleware to extract trace headers and set up trace context."""
|
|
93
|
+
|
|
94
|
+
async def dispatch(self, request: Request, call_next):
|
|
95
|
+
try:
|
|
96
|
+
from _mcp_mesh.tracing.trace_context_helper import (
|
|
97
|
+
TraceContextHelper,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Extract and set trace context from headers for distributed tracing
|
|
101
|
+
trace_context = await TraceContextHelper.extract_trace_context_from_request(
|
|
102
|
+
request
|
|
103
|
+
)
|
|
104
|
+
TraceContextHelper.setup_request_trace_context(
|
|
105
|
+
trace_context, logger
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
# Never fail request due to tracing issues
|
|
109
|
+
logger.warning(f"Failed to set trace context: {e}")
|
|
110
|
+
|
|
111
|
+
return await call_next(request)
|
|
112
|
+
|
|
113
|
+
app.add_middleware(TraceContextMiddleware)
|
|
114
|
+
logger.debug(
|
|
115
|
+
"📦 IMMEDIATE UVICORN: Added trace context middleware for distributed tracing"
|
|
116
|
+
)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.warning(
|
|
119
|
+
f"⚠️ IMMEDIATE UVICORN: Failed to add trace context middleware: {e}"
|
|
120
|
+
)
|
|
121
|
+
|
|
79
122
|
# Add health endpoint that can be updated by pipeline
|
|
80
123
|
# Store health check result in a shared location that can be updated
|
|
81
124
|
health_result = {"status": "starting", "message": "Agent is starting"}
|
|
File without changes
|
|
File without changes
|