mcp-mesh 0.5.1__py3-none-any.whl → 0.5.3__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 +53 -99
- _mcp_mesh/engine/http_wrapper.py +21 -9
- _mcp_mesh/engine/unified_mcp_proxy.py +135 -83
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +8 -5
- _mcp_mesh/pipeline/api_startup/middleware_integration.py +153 -0
- _mcp_mesh/shared/fastapi_middleware_manager.py +371 -0
- _mcp_mesh/tracing/agent_context_helper.py +13 -8
- _mcp_mesh/tracing/context.py +10 -4
- _mcp_mesh/tracing/execution_tracer.py +26 -43
- _mcp_mesh/tracing/fastapi_tracing_middleware.py +204 -0
- _mcp_mesh/tracing/redis_metadata_publisher.py +14 -34
- _mcp_mesh/tracing/trace_context_helper.py +14 -19
- _mcp_mesh/tracing/utils.py +137 -0
- {mcp_mesh-0.5.1.dist-info → mcp_mesh-0.5.3.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.1.dist-info → mcp_mesh-0.5.3.dist-info}/RECORD +19 -15
- mesh/decorators.py +35 -0
- {mcp_mesh-0.5.1.dist-info → mcp_mesh-0.5.3.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.1.dist-info → mcp_mesh-0.5.3.dist-info}/licenses/LICENSE +0 -0
_mcp_mesh/__init__.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Dynamic dependency injection system for MCP Mesh.
|
|
3
3
|
|
|
4
4
|
Handles both initial injection and runtime updates when topology changes.
|
|
5
|
+
Focused purely on dependency injection - telemetry/tracing is handled at
|
|
6
|
+
the HTTP middleware layer for unified approach across MCP agents and FastAPI apps.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
9
|
import asyncio
|
|
@@ -292,41 +294,23 @@ class DependencyInjector:
|
|
|
292
294
|
if inspect.iscoroutinefunction(func):
|
|
293
295
|
@functools.wraps(func)
|
|
294
296
|
async def minimal_wrapper(*args, **kwargs):
|
|
295
|
-
#
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
except ImportError:
|
|
304
|
-
# Fallback if tracing is unavailable - never fail user function
|
|
305
|
-
wrapper_logger.debug("🔇 Tracing unavailable, executing without telemetry")
|
|
306
|
-
return await func(*args, **kwargs)
|
|
307
|
-
except Exception as e:
|
|
308
|
-
# Never fail user function due to tracing errors
|
|
309
|
-
wrapper_logger.warning(f"⚠️ Telemetry failed, executing without: {e}")
|
|
310
|
-
return await func(*args, **kwargs)
|
|
297
|
+
# Use ExecutionTracer for functions without dependencies (v0.4.0 style)
|
|
298
|
+
from ..tracing.execution_tracer import ExecutionTracer
|
|
299
|
+
wrapper_logger.debug(f"🔧 DI: Executing async function {func.__name__} (no dependencies)")
|
|
300
|
+
|
|
301
|
+
# For async functions without dependencies, use the async tracer
|
|
302
|
+
return await ExecutionTracer.trace_function_execution_async(
|
|
303
|
+
func, args, kwargs, [], [], 0, wrapper_logger
|
|
304
|
+
)
|
|
311
305
|
else:
|
|
312
306
|
@functools.wraps(func)
|
|
313
307
|
def minimal_wrapper(*args, **kwargs):
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
func, args, kwargs, wrapper_logger
|
|
321
|
-
)
|
|
322
|
-
except ImportError:
|
|
323
|
-
# Fallback if tracing is unavailable - never fail user function
|
|
324
|
-
wrapper_logger.debug("🔇 Tracing unavailable, executing without telemetry")
|
|
325
|
-
return func(*args, **kwargs)
|
|
326
|
-
except Exception as e:
|
|
327
|
-
# Never fail user function due to tracing errors
|
|
328
|
-
wrapper_logger.warning(f"⚠️ Telemetry failed, executing without: {e}")
|
|
329
|
-
return func(*args, **kwargs)
|
|
308
|
+
# Use ExecutionTracer for functions without dependencies (v0.4.0 style)
|
|
309
|
+
from ..tracing.execution_tracer import ExecutionTracer
|
|
310
|
+
wrapper_logger.debug(f"🔧 DI: Executing sync function {func.__name__} (no dependencies)")
|
|
311
|
+
|
|
312
|
+
# Use original function tracer for functions without dependencies
|
|
313
|
+
return ExecutionTracer.trace_original_function(func, args, kwargs, wrapper_logger)
|
|
330
314
|
|
|
331
315
|
# Add minimal metadata for compatibility
|
|
332
316
|
minimal_wrapper._mesh_injected_deps = {}
|
|
@@ -426,54 +410,29 @@ class DependencyInjector:
|
|
|
426
410
|
f"🔧 DEPENDENCY_WRAPPER: final_kwargs={final_kwargs}"
|
|
427
411
|
)
|
|
428
412
|
|
|
429
|
-
# ===== EXECUTE WITH
|
|
430
|
-
#
|
|
413
|
+
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
414
|
+
# Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
|
|
415
|
+
from ..tracing.execution_tracer import ExecutionTracer
|
|
416
|
+
|
|
431
417
|
original_func = func._mesh_original_func
|
|
432
418
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
wrapper_logger,
|
|
448
|
-
)
|
|
449
|
-
else:
|
|
450
|
-
# For sync functions, call directly
|
|
451
|
-
result = ExecutionTracer.trace_function_execution(
|
|
452
|
-
original_func,
|
|
453
|
-
args,
|
|
454
|
-
final_kwargs,
|
|
455
|
-
dependencies,
|
|
456
|
-
mesh_positions,
|
|
457
|
-
injected_count,
|
|
458
|
-
wrapper_logger,
|
|
459
|
-
)
|
|
460
|
-
except ImportError:
|
|
461
|
-
# Fallback if tracing is unavailable - never fail user function
|
|
462
|
-
wrapper_logger.debug("🔇 Tracing unavailable, executing without telemetry")
|
|
463
|
-
if inspect.iscoroutinefunction(original_func):
|
|
464
|
-
result = await original_func(*args, **final_kwargs)
|
|
465
|
-
else:
|
|
466
|
-
result = original_func(*args, **final_kwargs)
|
|
467
|
-
except Exception as e:
|
|
468
|
-
# Never fail user function due to tracing errors
|
|
469
|
-
wrapper_logger.warning(f"⚠️ Telemetry failed, executing without: {e}")
|
|
470
|
-
if inspect.iscoroutinefunction(original_func):
|
|
471
|
-
result = await original_func(*args, **final_kwargs)
|
|
472
|
-
else:
|
|
473
|
-
result = original_func(*args, **final_kwargs)
|
|
419
|
+
wrapper_logger.debug(
|
|
420
|
+
f"🔧 DI: Executing async function {original_func.__name__} with {injected_count} injected dependencies"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Use ExecutionTracer's async method for clean tracing
|
|
424
|
+
result = await ExecutionTracer.trace_function_execution_async(
|
|
425
|
+
original_func,
|
|
426
|
+
args,
|
|
427
|
+
final_kwargs,
|
|
428
|
+
dependencies,
|
|
429
|
+
mesh_positions,
|
|
430
|
+
injected_count,
|
|
431
|
+
wrapper_logger,
|
|
432
|
+
)
|
|
474
433
|
|
|
475
434
|
wrapper_logger.debug(
|
|
476
|
-
f"🔧
|
|
435
|
+
f"🔧 DI: Function {original_func.__name__} returned: {type(result)}"
|
|
477
436
|
)
|
|
478
437
|
return result
|
|
479
438
|
|
|
@@ -524,29 +483,24 @@ class DependencyInjector:
|
|
|
524
483
|
final_kwargs[param_name] = dependency
|
|
525
484
|
injected_count += 1
|
|
526
485
|
|
|
527
|
-
# ===== EXECUTE WITH
|
|
528
|
-
#
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
return func._mesh_original_func(*args, **final_kwargs)
|
|
546
|
-
except Exception as e:
|
|
547
|
-
# Never fail user function due to tracing errors
|
|
548
|
-
wrapper_logger.warning(f"⚠️ Telemetry failed, executing without: {e}")
|
|
549
|
-
return func._mesh_original_func(*args, **final_kwargs)
|
|
486
|
+
# ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
|
|
487
|
+
# Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
|
|
488
|
+
from ..tracing.execution_tracer import ExecutionTracer
|
|
489
|
+
|
|
490
|
+
wrapper_logger.debug(
|
|
491
|
+
f"🔧 DI: Executing sync function {func._mesh_original_func.__name__} with {injected_count} injected dependencies"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# Use ExecutionTracer for clean execution tracing
|
|
495
|
+
return ExecutionTracer.trace_function_execution(
|
|
496
|
+
func._mesh_original_func,
|
|
497
|
+
args,
|
|
498
|
+
final_kwargs,
|
|
499
|
+
dependencies,
|
|
500
|
+
mesh_positions,
|
|
501
|
+
injected_count,
|
|
502
|
+
wrapper_logger,
|
|
503
|
+
)
|
|
550
504
|
|
|
551
505
|
# Store dependency state on wrapper
|
|
552
506
|
dependency_wrapper._mesh_injected_deps = {}
|
_mcp_mesh/engine/http_wrapper.py
CHANGED
|
@@ -395,11 +395,10 @@ class HttpMcpWrapper:
|
|
|
395
395
|
from starlette.responses import Response
|
|
396
396
|
|
|
397
397
|
class MCPSessionRoutingMiddleware(BaseHTTPMiddleware):
|
|
398
|
-
"""
|
|
398
|
+
"""Clean session routing middleware for MCP requests (v0.4.0 style).
|
|
399
399
|
|
|
400
|
-
Handles session affinity
|
|
401
|
-
|
|
402
|
-
DI function wrapper for unified coverage of both MCP and API calls.
|
|
400
|
+
Handles session affinity and basic trace context setup only.
|
|
401
|
+
Function execution tracing is handled by ExecutionTracer in DependencyInjector.
|
|
403
402
|
"""
|
|
404
403
|
def __init__(self, app, http_wrapper):
|
|
405
404
|
super().__init__(app)
|
|
@@ -407,10 +406,23 @@ class HttpMcpWrapper:
|
|
|
407
406
|
self.logger = logger
|
|
408
407
|
|
|
409
408
|
async def dispatch(self, request: Request, call_next):
|
|
410
|
-
#
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
409
|
+
# Extract and set trace context from headers for distributed tracing
|
|
410
|
+
try:
|
|
411
|
+
from ..tracing.trace_context_helper import TraceContextHelper
|
|
412
|
+
|
|
413
|
+
# Use helper class for trace context extraction and setup
|
|
414
|
+
trace_context = (
|
|
415
|
+
await TraceContextHelper.extract_trace_context_from_request(
|
|
416
|
+
request
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
TraceContextHelper.setup_request_trace_context(
|
|
420
|
+
trace_context, self.logger
|
|
421
|
+
)
|
|
422
|
+
except Exception as e:
|
|
423
|
+
# Never fail request due to tracing issues
|
|
424
|
+
self.logger.warning(f"Failed to set trace context: {e}")
|
|
425
|
+
pass
|
|
414
426
|
|
|
415
427
|
# Extract session ID from request
|
|
416
428
|
session_id = await self.http_wrapper._extract_session_id(request)
|
|
@@ -443,7 +455,7 @@ class HttpMcpWrapper:
|
|
|
443
455
|
|
|
444
456
|
# Add the middleware to FastMCP app
|
|
445
457
|
self._mcp_app.add_middleware(MCPSessionRoutingMiddleware, http_wrapper=self)
|
|
446
|
-
logger.info("✅
|
|
458
|
+
logger.info("✅ Clean session routing middleware added to FastMCP app (v0.4.0 style)")
|
|
447
459
|
|
|
448
460
|
async def _extract_session_id(self, request) -> str:
|
|
449
461
|
"""Extract session ID from request headers or body."""
|
|
@@ -56,6 +56,67 @@ class UnifiedMCPProxy:
|
|
|
56
56
|
f"🔧 UnifiedMCPProxy initialized with kwargs: {self.kwargs_config}"
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
+
def _create_fastmcp_client(self, endpoint: str):
|
|
60
|
+
"""Create FastMCP client with automatic trace header injection.
|
|
61
|
+
|
|
62
|
+
This method automatically detects trace context and adds distributed tracing
|
|
63
|
+
headers when available, while maintaining full backward compatibility.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
endpoint: MCP endpoint URL
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
FastMCP Client instance with or without trace headers
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
from fastmcp import Client
|
|
73
|
+
from fastmcp.client.transports import StreamableHttpTransport
|
|
74
|
+
|
|
75
|
+
# Try to get current trace context for header injection
|
|
76
|
+
trace_headers = self._get_trace_headers()
|
|
77
|
+
|
|
78
|
+
if trace_headers:
|
|
79
|
+
# Create client with trace headers for distributed tracing
|
|
80
|
+
transport = StreamableHttpTransport(url=endpoint, headers=trace_headers)
|
|
81
|
+
return Client(transport)
|
|
82
|
+
else:
|
|
83
|
+
# Create standard client when no trace context available
|
|
84
|
+
return Client(endpoint)
|
|
85
|
+
|
|
86
|
+
except ImportError:
|
|
87
|
+
# If StreamableHttpTransport not available, fall back to standard client
|
|
88
|
+
from fastmcp import Client
|
|
89
|
+
|
|
90
|
+
return Client(endpoint)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
# Any other error, fall back to standard client
|
|
93
|
+
from fastmcp import Client
|
|
94
|
+
|
|
95
|
+
return Client(endpoint)
|
|
96
|
+
|
|
97
|
+
def _get_trace_headers(self) -> dict[str, str]:
|
|
98
|
+
"""Extract trace headers from current context for distributed tracing.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dict of trace headers or empty dict if no trace context available
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
from ..tracing.context import TraceContext
|
|
105
|
+
|
|
106
|
+
current_trace = TraceContext.get_current()
|
|
107
|
+
if current_trace:
|
|
108
|
+
headers = {
|
|
109
|
+
"X-Trace-ID": current_trace.trace_id,
|
|
110
|
+
"X-Parent-Span": current_trace.span_id, # Current span becomes parent for downstream
|
|
111
|
+
}
|
|
112
|
+
return headers
|
|
113
|
+
else:
|
|
114
|
+
return {}
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
# Never fail MCP calls due to tracing issues
|
|
118
|
+
return {}
|
|
119
|
+
|
|
59
120
|
def _configure_from_kwargs(self):
|
|
60
121
|
"""Auto-configure proxy settings from kwargs."""
|
|
61
122
|
# Basic configuration
|
|
@@ -213,74 +274,63 @@ class UnifiedMCPProxy:
|
|
|
213
274
|
return await self.call_tool_with_tracing(self.function_name, kwargs)
|
|
214
275
|
|
|
215
276
|
async def call_tool_with_tracing(self, name: str, arguments: dict = None) -> Any:
|
|
216
|
-
"""Call a tool with
|
|
217
|
-
# Check if telemetry is enabled
|
|
218
|
-
if not self.telemetry_enabled:
|
|
219
|
-
self.logger.debug(f"🔇 Telemetry disabled, calling tool directly: {name}")
|
|
220
|
-
return await self.call_tool(name, arguments)
|
|
221
|
-
|
|
222
|
-
# Import tracing dependencies
|
|
277
|
+
"""Call a tool with clean ExecutionTracer integration (v0.4.0 style)."""
|
|
278
|
+
# Check if telemetry is enabled - use same check as ExecutionTracer for consistency
|
|
223
279
|
from ..tracing.execution_tracer import ExecutionTracer
|
|
280
|
+
from ..tracing.utils import is_tracing_enabled
|
|
224
281
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
try:
|
|
229
|
-
# Start execution tracing with proxy metadata
|
|
230
|
-
tracer.start_execution(
|
|
231
|
-
args=(),
|
|
232
|
-
kwargs=arguments or {},
|
|
233
|
-
dependencies=[self.endpoint],
|
|
234
|
-
mesh_positions=[],
|
|
235
|
-
injected_count=1, # This is an injected dependency call
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
# Add proxy-specific metadata
|
|
239
|
-
tracer.execution_metadata.update(
|
|
240
|
-
{
|
|
241
|
-
"call_type": "unified_mcp_proxy",
|
|
242
|
-
"endpoint": self.endpoint,
|
|
243
|
-
"function_name": name,
|
|
244
|
-
"proxy_type": "fastmcp_with_fallback",
|
|
245
|
-
"streaming_capable": self.streaming_capable,
|
|
246
|
-
"timeout": self.timeout,
|
|
247
|
-
"retry_count": self.retry_count,
|
|
248
|
-
}
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
# Add enhanced agent context metadata if enabled
|
|
252
|
-
if self.collect_agent_context:
|
|
253
|
-
try:
|
|
254
|
-
agent_context = self._collect_agent_context_metadata(
|
|
255
|
-
name, arguments
|
|
256
|
-
)
|
|
257
|
-
tracer.execution_metadata.update(agent_context)
|
|
258
|
-
except Exception as e:
|
|
259
|
-
self.logger.debug(f"Failed to collect agent context metadata: {e}")
|
|
260
|
-
# Add minimal fallback metadata
|
|
261
|
-
tracer.execution_metadata.update(
|
|
262
|
-
{
|
|
263
|
-
"client_agent_id": "unknown",
|
|
264
|
-
"target_agent_endpoint": self.endpoint,
|
|
265
|
-
"proxy_instance_id": id(self),
|
|
266
|
-
}
|
|
267
|
-
)
|
|
282
|
+
if not self.telemetry_enabled or not is_tracing_enabled():
|
|
283
|
+
return await self.call_tool(name, arguments)
|
|
268
284
|
|
|
269
|
-
|
|
270
|
-
|
|
285
|
+
# Create wrapper function for ExecutionTracer compatibility
|
|
286
|
+
async def proxy_call_wrapper(*args, **kwargs):
|
|
287
|
+
# Add proxy-specific metadata to execution context if tracer is available
|
|
288
|
+
try:
|
|
289
|
+
from ..tracing.context import TraceContext
|
|
290
|
+
|
|
291
|
+
current_trace = TraceContext.get_current()
|
|
292
|
+
if current_trace and hasattr(current_trace, "execution_metadata"):
|
|
293
|
+
# Add proxy metadata to current trace
|
|
294
|
+
proxy_metadata = {
|
|
295
|
+
"call_type": "unified_mcp_proxy",
|
|
296
|
+
"endpoint": self.endpoint,
|
|
297
|
+
"proxy_type": "fastmcp_with_fallback",
|
|
298
|
+
"streaming_capable": self.streaming_capable,
|
|
299
|
+
"timeout": self.timeout,
|
|
300
|
+
"retry_count": self.retry_count,
|
|
301
|
+
}
|
|
271
302
|
|
|
272
|
-
|
|
273
|
-
|
|
303
|
+
# Add enhanced agent context if enabled
|
|
304
|
+
if self.collect_agent_context:
|
|
305
|
+
try:
|
|
306
|
+
agent_context = self._collect_agent_context_metadata(
|
|
307
|
+
name, arguments
|
|
308
|
+
)
|
|
309
|
+
proxy_metadata.update(agent_context)
|
|
310
|
+
except Exception as e:
|
|
311
|
+
self.logger.debug(
|
|
312
|
+
f"Failed to collect agent context metadata: {e}"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Update current execution metadata
|
|
316
|
+
if hasattr(current_trace, "execution_metadata"):
|
|
317
|
+
current_trace.execution_metadata.update(proxy_metadata)
|
|
274
318
|
|
|
275
|
-
|
|
276
|
-
|
|
319
|
+
except Exception as e:
|
|
320
|
+
self.logger.debug(f"Failed to add proxy metadata: {e}")
|
|
277
321
|
|
|
278
|
-
|
|
279
|
-
# End tracing with error
|
|
280
|
-
tracer.end_execution(error=str(e), success=False)
|
|
322
|
+
return await self.call_tool(name, arguments)
|
|
281
323
|
|
|
282
|
-
|
|
283
|
-
|
|
324
|
+
# Use ExecutionTracer's static async method for clean integration
|
|
325
|
+
return await ExecutionTracer.trace_function_execution_async(
|
|
326
|
+
proxy_call_wrapper,
|
|
327
|
+
args=(),
|
|
328
|
+
kwargs={}, # arguments are handled inside the wrapper
|
|
329
|
+
dependencies=[self.endpoint],
|
|
330
|
+
mesh_positions=[],
|
|
331
|
+
injected_count=1,
|
|
332
|
+
logger_instance=self.logger,
|
|
333
|
+
)
|
|
284
334
|
|
|
285
335
|
async def call_tool(self, name: str, arguments: dict = None) -> Any:
|
|
286
336
|
"""Call a tool using FastMCP client with HTTP transport.
|
|
@@ -292,15 +342,13 @@ class UnifiedMCPProxy:
|
|
|
292
342
|
start_time = time.time()
|
|
293
343
|
|
|
294
344
|
try:
|
|
295
|
-
from fastmcp import Client
|
|
296
|
-
|
|
297
345
|
# Use correct FastMCP client endpoint - agents expose MCP on /mcp
|
|
298
346
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
299
347
|
self.logger.debug(f"🔄 Trying FastMCP client with endpoint: {mcp_endpoint}")
|
|
300
348
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
349
|
+
# Create client with automatic trace header injection
|
|
350
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
351
|
+
async with client_instance as client:
|
|
304
352
|
|
|
305
353
|
# Use FastMCP's call_tool which returns CallToolResult object
|
|
306
354
|
result = await client.call_tool(name, arguments or {})
|
|
@@ -651,46 +699,51 @@ class UnifiedMCPProxy:
|
|
|
651
699
|
# MCP Protocol Methods - using FastMCP client's superior implementation
|
|
652
700
|
async def list_tools(self) -> list:
|
|
653
701
|
"""List available tools from remote agent."""
|
|
654
|
-
from fastmcp import Client
|
|
655
|
-
|
|
656
702
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
657
|
-
|
|
703
|
+
|
|
704
|
+
# Create client with automatic trace header injection
|
|
705
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
706
|
+
async with client_instance as client:
|
|
658
707
|
result = await client.list_tools()
|
|
659
708
|
return result.tools if hasattr(result, "tools") else result
|
|
660
709
|
|
|
661
710
|
async def list_resources(self) -> list:
|
|
662
711
|
"""List available resources from remote agent."""
|
|
663
|
-
from fastmcp import Client
|
|
664
|
-
|
|
665
712
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
666
|
-
|
|
713
|
+
|
|
714
|
+
# Create client with automatic trace header injection
|
|
715
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
716
|
+
async with client_instance as client:
|
|
667
717
|
result = await client.list_resources()
|
|
668
718
|
return result.resources if hasattr(result, "resources") else result
|
|
669
719
|
|
|
670
720
|
async def read_resource(self, uri: str) -> Any:
|
|
671
721
|
"""Read resource contents from remote agent."""
|
|
672
|
-
from fastmcp import Client
|
|
673
|
-
|
|
674
722
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
675
|
-
|
|
723
|
+
|
|
724
|
+
# Create client with automatic trace header injection
|
|
725
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
726
|
+
async with client_instance as client:
|
|
676
727
|
result = await client.read_resource(uri)
|
|
677
728
|
return result.contents if hasattr(result, "contents") else result
|
|
678
729
|
|
|
679
730
|
async def list_prompts(self) -> list:
|
|
680
731
|
"""List available prompts from remote agent."""
|
|
681
|
-
from fastmcp import Client
|
|
682
|
-
|
|
683
732
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
684
|
-
|
|
733
|
+
|
|
734
|
+
# Create client with automatic trace header injection
|
|
735
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
736
|
+
async with client_instance as client:
|
|
685
737
|
result = await client.list_prompts()
|
|
686
738
|
return result.prompts if hasattr(result, "prompts") else result
|
|
687
739
|
|
|
688
740
|
async def get_prompt(self, name: str, arguments: dict = None) -> Any:
|
|
689
741
|
"""Get prompt template from remote agent."""
|
|
690
|
-
from fastmcp import Client
|
|
691
|
-
|
|
692
742
|
mcp_endpoint = f"{self.endpoint}/mcp"
|
|
693
|
-
|
|
743
|
+
|
|
744
|
+
# Create client with automatic trace header injection
|
|
745
|
+
client_instance = self._create_fastmcp_client(mcp_endpoint)
|
|
746
|
+
async with client_instance as client:
|
|
694
747
|
result = await client.get_prompt(name, arguments or {})
|
|
695
748
|
return result
|
|
696
749
|
|
|
@@ -700,8 +753,7 @@ class UnifiedMCPProxy:
|
|
|
700
753
|
|
|
701
754
|
FastMCP client handles session management internally.
|
|
702
755
|
"""
|
|
703
|
-
|
|
704
|
-
|
|
756
|
+
|
|
705
757
|
# Generate session ID for compatibility
|
|
706
758
|
session_id = f"session:{uuid.uuid4().hex[:16]}"
|
|
707
759
|
self.logger.debug(f"📝 Created session ID: {session_id}")
|
|
@@ -11,6 +11,7 @@ import logging
|
|
|
11
11
|
from ..shared.mesh_pipeline import MeshPipeline
|
|
12
12
|
from .api_server_setup import APIServerSetupStep
|
|
13
13
|
from .fastapi_discovery import FastAPIAppDiscoveryStep
|
|
14
|
+
from .middleware_integration import TracingMiddlewareIntegrationStep
|
|
14
15
|
from .route_collection import RouteCollectionStep
|
|
15
16
|
from .route_integration import RouteIntegrationStep
|
|
16
17
|
|
|
@@ -25,7 +26,8 @@ class APIPipeline(MeshPipeline):
|
|
|
25
26
|
1. Route collection (@mesh.route decorators)
|
|
26
27
|
2. FastAPI app discovery (find user's FastAPI instances)
|
|
27
28
|
3. Route integration (apply dependency injection)
|
|
28
|
-
4.
|
|
29
|
+
4. Tracing middleware integration (add telemetry to FastAPI apps)
|
|
30
|
+
5. API server setup (service registration metadata)
|
|
29
31
|
|
|
30
32
|
Unlike MCP agents, API services are consumers so we focus on:
|
|
31
33
|
- Dependency injection into route handlers
|
|
@@ -41,10 +43,11 @@ class APIPipeline(MeshPipeline):
|
|
|
41
43
|
"""Setup the API pipeline steps."""
|
|
42
44
|
# Essential API integration steps
|
|
43
45
|
steps = [
|
|
44
|
-
RouteCollectionStep(),
|
|
45
|
-
FastAPIAppDiscoveryStep(),
|
|
46
|
-
RouteIntegrationStep(),
|
|
47
|
-
|
|
46
|
+
RouteCollectionStep(), # Collect @mesh.route decorators
|
|
47
|
+
FastAPIAppDiscoveryStep(), # Find user's FastAPI app instances
|
|
48
|
+
RouteIntegrationStep(), # Apply dependency injection to routes
|
|
49
|
+
TracingMiddlewareIntegrationStep(), # Add tracing middleware to FastAPI apps
|
|
50
|
+
APIServerSetupStep(), # Prepare service registration metadata
|
|
48
51
|
# Note: Heartbeat integration will be added in next phase
|
|
49
52
|
# Note: User controls FastAPI server startup (uvicorn/gunicorn)
|
|
50
53
|
]
|