mcp-mesh 0.6.3__py3-none-any.whl → 0.7.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 CHANGED
@@ -31,7 +31,7 @@ from .engine.decorator_registry import (
31
31
  get_decorator_stats,
32
32
  )
33
33
 
34
- __version__ = "0.6.3"
34
+ __version__ = "0.7.0"
35
35
 
36
36
  # Store reference to runtime processor if initialized
37
37
  _runtime_processor = None
@@ -38,7 +38,7 @@ class LLMAgentMetadata:
38
38
 
39
39
  function: Callable
40
40
  config: dict[str, Any] # LLM configuration (provider, model, filter, etc.)
41
- output_type: Optional[type] # Pydantic model type from return annotation
41
+ output_type: type | None # Pydantic model type from return annotation
42
42
  param_name: str # Name of MeshLlmAgent parameter
43
43
  function_id: str # Unique function ID for registry
44
44
  registered_at: datetime
@@ -70,16 +70,55 @@ class DecoratorRegistry:
70
70
  _custom_decorators: dict[str, dict[str, DecoratedFunction]] = {}
71
71
 
72
72
  # Immediate uvicorn server storage (for preventing shutdown state)
73
- _immediate_uvicorn_server: Optional[dict[str, Any]] = None
73
+ _immediate_uvicorn_server: dict[str, Any] | None = None
74
74
 
75
75
  # FastMCP lifespan storage (for proper integration with FastAPI)
76
- _fastmcp_lifespan: Optional[Any] = None
76
+ _fastmcp_lifespan: Any | None = None
77
77
 
78
78
  # FastMCP HTTP app storage (the same app instance whose lifespan was extracted)
79
- _fastmcp_http_app: Optional[Any] = None
79
+ _fastmcp_http_app: Any | None = None
80
80
 
81
81
  # FastMCP server info storage (for schema extraction during heartbeat)
82
- _fastmcp_server_info: Optional[dict[str, Any]] = None
82
+ _fastmcp_server_info: dict[str, Any] | None = None
83
+
84
+ # Route-to-wrapper mapping for @mesh.route dependency injection
85
+ # Key: "METHOD:path" (e.g., "GET:/api/v1/benchmark-services")
86
+ # Value: {"wrapper": Callable, "dependencies": list[str]}
87
+ _route_wrapper_registry: dict[str, dict[str, Any]] = {}
88
+
89
+ @classmethod
90
+ def register_route_wrapper(
91
+ cls, method: str, path: str, wrapper: Callable, dependencies: list[str]
92
+ ) -> None:
93
+ """
94
+ Register a route's wrapper function for dependency injection.
95
+
96
+ Args:
97
+ method: HTTP method (e.g., "GET", "POST")
98
+ path: Route path (e.g., "/api/v1/benchmark-services")
99
+ wrapper: The injection wrapper function
100
+ dependencies: List of dependency capability names
101
+ """
102
+ route_id = f"{method}:{path}"
103
+ cls._route_wrapper_registry[route_id] = {
104
+ "wrapper": wrapper,
105
+ "dependencies": dependencies,
106
+ "method": method,
107
+ "path": path,
108
+ }
109
+ logger.debug(
110
+ f"📝 Registered route wrapper: {route_id} with {len(dependencies)} dependencies"
111
+ )
112
+
113
+ @classmethod
114
+ def get_route_wrapper(cls, route_id: str) -> dict[str, Any] | None:
115
+ """Get route wrapper info by route ID (METHOD:path)."""
116
+ return cls._route_wrapper_registry.get(route_id)
117
+
118
+ @classmethod
119
+ def get_all_route_wrappers(cls) -> dict[str, dict[str, Any]]:
120
+ """Get all registered route wrappers."""
121
+ return cls._route_wrapper_registry.copy()
83
122
 
84
123
  @classmethod
85
124
  def register_mesh_agent(cls, func: Callable, metadata: dict[str, Any]) -> None:
@@ -154,7 +193,7 @@ class DecoratorRegistry:
154
193
  cls,
155
194
  func: Callable,
156
195
  config: dict[str, Any],
157
- output_type: Optional[type],
196
+ output_type: type | None,
158
197
  param_name: str,
159
198
  function_id: str,
160
199
  ) -> None:
@@ -355,7 +394,7 @@ class DecoratorRegistry:
355
394
  return stats
356
395
 
357
396
  # Cache for resolved agent configuration to avoid repeated work
358
- _cached_agent_config: Optional[dict[str, Any]] = None
397
+ _cached_agent_config: dict[str, Any] | None = None
359
398
 
360
399
  @classmethod
361
400
  def update_agent_config(cls, updates: dict[str, Any]) -> None:
@@ -610,7 +649,7 @@ class DecoratorRegistry:
610
649
  )
611
650
 
612
651
  @classmethod
613
- def get_immediate_uvicorn_server(cls) -> Optional[dict[str, Any]]:
652
+ def get_immediate_uvicorn_server(cls) -> dict[str, Any] | None:
614
653
  """
615
654
  Get stored immediate uvicorn server reference.
616
655
 
@@ -659,7 +698,7 @@ class DecoratorRegistry:
659
698
  logger.debug("🔄 REGISTRY: Stored FastMCP lifespan for FastAPI integration")
660
699
 
661
700
  @classmethod
662
- def get_fastmcp_lifespan(cls) -> Optional[Any]:
701
+ def get_fastmcp_lifespan(cls) -> Any | None:
663
702
  """
664
703
  Get stored FastMCP lifespan.
665
704
 
@@ -686,7 +725,7 @@ class DecoratorRegistry:
686
725
  logger.debug("🔄 REGISTRY: Stored FastMCP HTTP app for mounting")
687
726
 
688
727
  @classmethod
689
- def get_fastmcp_http_app(cls) -> Optional[Any]:
728
+ def get_fastmcp_http_app(cls) -> Any | None:
690
729
  """
691
730
  Get stored FastMCP HTTP app.
692
731
 
@@ -715,7 +754,7 @@ class DecoratorRegistry:
715
754
  )
716
755
 
717
756
  @classmethod
718
- def get_fastmcp_server_info(cls) -> Optional[dict[str, Any]]:
757
+ def get_fastmcp_server_info(cls) -> dict[str, Any] | None:
719
758
  """
720
759
  Get stored FastMCP server info.
721
760
 
@@ -132,7 +132,7 @@ class HttpMcpWrapper:
132
132
 
133
133
  # Phase 3: Metadata caching
134
134
  self._metadata_cache: dict[str, Any] = {}
135
- self._cache_timestamp: Optional[datetime] = None
135
+ self._cache_timestamp: datetime | None = None
136
136
  self._cache_ttl: timedelta = timedelta(minutes=5) # Cache for 5 minutes
137
137
 
138
138
  # Phase 5: Session storage and pod info
@@ -254,7 +254,7 @@ class HttpMcpWrapper:
254
254
  self._cache_timestamp = datetime.now()
255
255
  logger.debug(f"📋 Metadata cache updated with {len(metadata)} entries")
256
256
 
257
- def get_cached_metadata(self) -> Optional[dict[str, Any]]:
257
+ def get_cached_metadata(self) -> dict[str, Any] | None:
258
258
  """Get cached metadata if available and valid."""
259
259
  if self._is_cache_valid():
260
260
  logger.debug("✅ Returning cached metadata")
@@ -391,6 +391,14 @@ class HttpMcpWrapper:
391
391
  try:
392
392
  from ..tracing.trace_context_helper import TraceContextHelper
393
393
 
394
+ # DEBUG: Log incoming headers for trace propagation debugging
395
+ trace_id_header = request.headers.get("X-Trace-ID")
396
+ parent_span_header = request.headers.get("X-Parent-Span")
397
+ self.logger.info(
398
+ f"🔍 INCOMING_HEADERS: X-Trace-ID={trace_id_header}, "
399
+ f"X-Parent-Span={parent_span_header}, path={request.url.path}"
400
+ )
401
+
394
402
  # Use helper class for trace context extraction and setup
395
403
  trace_context = (
396
404
  await TraceContextHelper.extract_trace_context_from_request(
@@ -8,7 +8,7 @@ import asyncio
8
8
  import json
9
9
  import logging
10
10
  from pathlib import Path
11
- from typing import Any, Dict, List, Optional, Union
11
+ from typing import Any, Dict, List, Literal, Optional, Union
12
12
 
13
13
  from pydantic import BaseModel
14
14
 
@@ -41,6 +41,9 @@ except ImportError:
41
41
 
42
42
  logger = logging.getLogger(__name__)
43
43
 
44
+ # Sentinel value to distinguish "context not provided" from "explicitly None/empty"
45
+ _CONTEXT_NOT_PROVIDED = object()
46
+
44
47
 
45
48
  class MeshLlmAgent:
46
49
  """
@@ -241,7 +244,56 @@ IMPORTANT TOOL CALLING RULES:
241
244
  f"Expected MeshContextModel, dict, or None."
242
245
  )
243
246
 
244
- def _render_system_prompt(self) -> str:
247
+ def _resolve_context(
248
+ self,
249
+ runtime_context: Union[dict, None, object],
250
+ context_mode: Literal["replace", "append", "prepend"],
251
+ ) -> dict:
252
+ """
253
+ Resolve effective context for template rendering.
254
+
255
+ Merges auto-populated context (from decorator's context_param) with
256
+ runtime context passed to __call__(), based on the context_mode.
257
+
258
+ Args:
259
+ runtime_context: Context passed at call time, or _CONTEXT_NOT_PROVIDED
260
+ context_mode: How to merge contexts - "replace", "append", or "prepend"
261
+
262
+ Returns:
263
+ Resolved context dictionary for template rendering
264
+
265
+ Behavior:
266
+ - If runtime_context is _CONTEXT_NOT_PROVIDED: use auto-populated context
267
+ - If context_mode is "replace": use runtime_context entirely
268
+ - If context_mode is "append": auto_context | runtime_context (runtime wins)
269
+ - If context_mode is "prepend": runtime_context | auto_context (auto wins)
270
+
271
+ Note:
272
+ Empty dict {} with "replace" mode explicitly clears context.
273
+ Empty dict {} with "append"/"prepend" is a no-op (keeps auto context).
274
+ """
275
+ # Get auto-populated context from decorator
276
+ auto_context = self._prepare_context(self._context_value)
277
+
278
+ # If no runtime context provided, use auto-populated context unchanged
279
+ if runtime_context is _CONTEXT_NOT_PROVIDED:
280
+ return auto_context
281
+
282
+ # Prepare runtime context (handles MeshContextModel, dict, None)
283
+ runtime_dict = self._prepare_context(runtime_context)
284
+
285
+ # Apply context_mode
286
+ if context_mode == "replace":
287
+ # Replace entirely with runtime context (even if empty)
288
+ return runtime_dict
289
+ elif context_mode == "prepend":
290
+ # Runtime first, auto overwrites (auto wins on conflicts)
291
+ return {**runtime_dict, **auto_context}
292
+ else: # "append" (default)
293
+ # Auto first, runtime overwrites (runtime wins on conflicts)
294
+ return {**auto_context, **runtime_dict}
295
+
296
+ def _render_system_prompt(self, effective_context: Optional[dict] = None) -> str:
245
297
  """
246
298
  Render system prompt from template or return literal.
247
299
 
@@ -249,6 +301,10 @@ IMPORTANT TOOL CALLING RULES:
249
301
  If system_prompt was set via set_system_prompt(), uses that override.
250
302
  Otherwise, uses config.system_prompt as literal.
251
303
 
304
+ Args:
305
+ effective_context: Optional pre-resolved context dict for template rendering.
306
+ If None, uses auto-populated _context_value.
307
+
252
308
  Returns:
253
309
  Rendered system prompt string
254
310
 
@@ -261,7 +317,12 @@ IMPORTANT TOOL CALLING RULES:
261
317
 
262
318
  # If template provided, render it
263
319
  if self._template is not None:
264
- context = self._prepare_context(self._context_value)
320
+ # Use provided effective_context or fall back to auto-populated context
321
+ context = (
322
+ effective_context
323
+ if effective_context is not None
324
+ else self._prepare_context(self._context_value)
325
+ )
265
326
  try:
266
327
  rendered = self._template.render(**context)
267
328
  logger.debug(
@@ -412,7 +473,12 @@ IMPORTANT TOOL CALLING RULES:
412
473
  raise RuntimeError(f"Mesh LLM provider invocation failed: {e}") from e
413
474
 
414
475
  async def __call__(
415
- self, message: Union[str, list[dict[str, Any]]], **kwargs
476
+ self,
477
+ message: Union[str, list[dict[str, Any]]],
478
+ *,
479
+ context: Union[dict, None, object] = _CONTEXT_NOT_PROVIDED,
480
+ context_mode: Literal["replace", "append", "prepend"] = "append",
481
+ **kwargs,
416
482
  ) -> Any:
417
483
  """
418
484
  Execute automatic agentic loop and return typed response.
@@ -422,6 +488,13 @@ IMPORTANT TOOL CALLING RULES:
422
488
  - str: Single user message (will be wrapped in messages array)
423
489
  - List[Dict[str, Any]]: Full conversation history with messages
424
490
  in format [{"role": "user|assistant|system", "content": "..."}]
491
+ context: Optional runtime context for system prompt template rendering.
492
+ Can be dict, MeshContextModel, or None. If not provided,
493
+ uses the auto-populated context from decorator's context_param.
494
+ context_mode: How to merge runtime context with auto-populated context:
495
+ - "append" (default): auto_context | runtime_context (runtime wins on conflicts)
496
+ - "prepend": runtime_context | auto_context (auto wins on conflicts)
497
+ - "replace": use runtime_context entirely (ignores auto-populated)
425
498
  **kwargs: Additional arguments passed to LLM
426
499
 
427
500
  Returns:
@@ -431,6 +504,22 @@ IMPORTANT TOOL CALLING RULES:
431
504
  MaxIterationsError: If max iterations exceeded
432
505
  ToolExecutionError: If tool execution fails
433
506
  ValidationError: If response doesn't match output_type schema
507
+
508
+ Examples:
509
+ # Use auto-populated context (default behavior)
510
+ result = await llm("What is the answer?")
511
+
512
+ # Append extra context (runtime wins on key conflicts)
513
+ result = await llm("What is the answer?", context={"extra": "info"})
514
+
515
+ # Prepend context (auto wins on key conflicts)
516
+ result = await llm("What is the answer?", context={"extra": "info"}, context_mode="prepend")
517
+
518
+ # Replace context entirely
519
+ result = await llm("What is the answer?", context={"only": "this"}, context_mode="replace")
520
+
521
+ # Explicitly clear context
522
+ result = await llm("What is the answer?", context={}, context_mode="replace")
434
523
  """
435
524
  self._iteration_count = 0
436
525
 
@@ -440,8 +529,11 @@ IMPORTANT TOOL CALLING RULES:
440
529
  "litellm is required for MeshLlmAgent. Install with: pip install litellm"
441
530
  )
442
531
 
443
- # Render base system prompt (from template or literal)
444
- base_system_prompt = self._render_system_prompt()
532
+ # Resolve effective context (merge auto-populated with runtime context)
533
+ effective_context = self._resolve_context(context, context_mode)
534
+
535
+ # Render base system prompt (from template or literal) with effective context
536
+ base_system_prompt = self._render_system_prompt(effective_context)
445
537
 
446
538
  # Phase 2: Use provider handler to format system prompt
447
539
  # This allows vendor-specific optimizations (e.g., OpenAI skips JSON instructions)
@@ -29,7 +29,7 @@ class UnifiedMCPProxy:
29
29
  """
30
30
 
31
31
  def __init__(
32
- self, endpoint: str, function_name: str, kwargs_config: Optional[dict] = None
32
+ self, endpoint: str, function_name: str, kwargs_config: dict | None = None
33
33
  ):
34
34
  """Initialize Unified MCP Proxy.
35
35
 
@@ -134,12 +134,20 @@ class UnifiedMCPProxy:
134
134
  "X-Trace-ID": current_trace.trace_id,
135
135
  "X-Parent-Span": current_trace.span_id, # Current span becomes parent for downstream
136
136
  }
137
+ self.logger.info(
138
+ f"🔗 TRACE_PROPAGATION: Injecting headers trace_id={current_trace.trace_id[:8]}... "
139
+ f"parent_span={current_trace.span_id[:8]}..."
140
+ )
137
141
  return headers
138
142
  else:
143
+ self.logger.warning("🔗 TRACE_PROPAGATION: No trace context available")
139
144
  return {}
140
145
 
141
146
  except Exception as e:
142
147
  # Never fail MCP calls due to tracing issues
148
+ self.logger.warning(
149
+ f"🔗 TRACE_PROPAGATION: Exception getting trace context: {e}"
150
+ )
143
151
  return {}
144
152
 
145
153
  def _configure_from_kwargs(self):
@@ -826,7 +834,7 @@ class EnhancedUnifiedMCPProxy(UnifiedMCPProxy):
826
834
  """
827
835
 
828
836
  def __init__(
829
- self, endpoint: str, function_name: str, kwargs_config: Optional[dict] = None
837
+ self, endpoint: str, function_name: str, kwargs_config: dict | None = None
830
838
  ):
831
839
  """Initialize Enhanced Unified MCP Proxy."""
832
840
  super().__init__(endpoint, function_name, kwargs_config)
@@ -54,8 +54,8 @@ class APIDependencyResolutionStep(PipelineStep):
54
54
  result.message = (
55
55
  "No heartbeat response or registry wrapper - completed successfully"
56
56
  )
57
- self.logger.info(
58
- "ℹ️ No heartbeat response to process - this is normal for API services"
57
+ self.logger.debug(
58
+ "No heartbeat response to process - this is normal for API services"
59
59
  )
60
60
  return result
61
61
 
@@ -85,20 +85,7 @@ class APIDependencyResolutionStep(PipelineStep):
85
85
  # Log function registry status for debugging
86
86
  injector = get_global_injector()
87
87
  function_count = len(injector._function_registry)
88
- self.logger.debug(
89
- f"🔍 Function registry contains {function_count} functions:"
90
- )
91
- for func_id, wrapper_func in injector._function_registry.items():
92
- original_func = getattr(wrapper_func, "_mesh_original_func", None)
93
- func_name = original_func.__name__ if original_func else "unknown"
94
- dependencies = getattr(wrapper_func, "_mesh_dependencies", [])
95
- self.logger.debug(
96
- f" 📋 {func_id} -> {func_name} (deps: {dependencies})"
97
- )
98
-
99
- self.logger.debug(
100
- "🔗 API dependency resolution step completed using hash-based change detection"
101
- )
88
+ self.logger.debug(f"Function registry contains {function_count} functions")
102
89
 
103
90
  except Exception as e:
104
91
  result.status = PipelineStatus.FAILED
@@ -206,32 +193,33 @@ class APIDependencyResolutionStep(PipelineStep):
206
193
 
207
194
  if _last_api_dependency_hash is None:
208
195
  if function_count > 0:
209
- self.logger.info(
210
- f"🔄 Initial API dependency state detected: {function_count} functions, {total_deps} dependencies"
196
+ self.logger.debug(
197
+ f"Initial API dependency state detected: {function_count} functions, {total_deps} dependencies"
211
198
  )
212
199
  else:
213
- self.logger.info(
214
- "🔄 Initial API dependency state detected: no dependencies"
200
+ self.logger.debug(
201
+ "Initial API dependency state detected: no dependencies"
215
202
  )
216
203
  else:
217
- self.logger.info(
218
- f"🔄 API dependency state changed (hash: {_last_api_dependency_hash} → {current_hash})"
204
+ self.logger.debug(
205
+ f"API dependency state changed (hash: {_last_api_dependency_hash} → {current_hash})"
219
206
  )
220
207
  if function_count > 0:
221
- self.logger.info(
222
- f"🔄 Updating API dependencies for {function_count} functions ({total_deps} total dependencies)"
208
+ self.logger.debug(
209
+ f"Updating API dependencies for {function_count} functions ({total_deps} total dependencies)"
223
210
  )
224
211
  else:
225
- self.logger.info(
226
- "🔄 Registry reports no API dependencies - unwiring all existing dependencies"
212
+ self.logger.debug(
213
+ "Registry reports no API dependencies - unwiring all existing dependencies"
227
214
  )
228
215
 
229
216
  # Import here to avoid circular imports
230
217
  from ...engine.dependency_injector import get_global_injector
231
- from ...engine.full_mcp_proxy import (EnhancedFullMCPProxy,
232
- FullMCPProxy)
233
- from ...engine.mcp_client_proxy import (EnhancedMCPClientProxy,
234
- MCPClientProxy)
218
+ from ...engine.full_mcp_proxy import EnhancedFullMCPProxy, FullMCPProxy
219
+ from ...engine.mcp_client_proxy import (
220
+ EnhancedMCPClientProxy,
221
+ MCPClientProxy,
222
+ )
235
223
 
236
224
  injector = get_global_injector()
237
225
 
@@ -267,16 +255,32 @@ class APIDependencyResolutionStep(PipelineStep):
267
255
  for dep_key in keys_to_remove:
268
256
  await injector.unregister_dependency(dep_key)
269
257
  unwired_count += 1
270
- self.logger.info(
271
- f"🗑️ Unwired API dependency '{dep_key}' (no longer reported by registry)"
258
+ self.logger.debug(
259
+ f"Unwired API dependency '{dep_key}' (no longer reported by registry)"
272
260
  )
273
261
 
274
262
  # Step 3: Apply all dependency updates using positional indexing
275
263
  updated_count = 0
276
264
  for function_name, dependency_list in current_state.items():
265
+ # Check if function_name is a route path (METHOD:path format)
266
+ # Route paths contain "/" and look like "GET:/api/v1/benchmark-services"
267
+ is_route_path = "/" in function_name
268
+
277
269
  # Map tool name to func_id (using mapping from Step 1)
270
+ # For route paths, use the route_id directly as it won't be in tool_name_to_func_id
278
271
  func_id = tool_name_to_func_id.get(function_name, function_name)
279
272
 
273
+ # Get route wrapper if this is a route path
274
+ route_wrapper_info = None
275
+ if is_route_path:
276
+ route_wrapper_info = DecoratorRegistry.get_route_wrapper(
277
+ function_name
278
+ )
279
+ if not route_wrapper_info:
280
+ self.logger.warning(
281
+ f"No route wrapper found for '{function_name}' - dependency injection may fail"
282
+ )
283
+
280
284
  for dep_index, dep_info in enumerate(dependency_list):
281
285
  status = dep_info["status"]
282
286
  endpoint = dep_info["endpoint"]
@@ -288,33 +292,21 @@ class APIDependencyResolutionStep(PipelineStep):
288
292
  # Import here to avoid circular imports
289
293
  import os
290
294
 
291
- from ...engine.self_dependency_proxy import \
292
- SelfDependencyProxy
293
- from ...engine.unified_mcp_proxy import \
294
- EnhancedUnifiedMCPProxy
295
+ from ...engine.self_dependency_proxy import SelfDependencyProxy
296
+ from ...engine.unified_mcp_proxy import EnhancedUnifiedMCPProxy
295
297
 
296
298
  # Get current agent ID for self-dependency detection
297
299
  current_agent_id = None
298
300
  try:
299
- from ...engine.decorator_registry import \
300
- DecoratorRegistry
301
+ from ...engine.decorator_registry import DecoratorRegistry
301
302
 
302
303
  config = DecoratorRegistry.get_resolved_agent_config()
303
304
  current_agent_id = config["agent_id"]
304
- self.logger.debug(
305
- f"🔍 Current API service ID from DecoratorRegistry: '{current_agent_id}'"
306
- )
307
- except Exception as e:
305
+ except Exception:
308
306
  # For API services, try environment variable fallback
309
307
  current_agent_id = os.getenv("MCP_MESH_AGENT_ID")
310
- self.logger.debug(
311
- f"🔍 Current API service ID from environment: '{current_agent_id}' (fallback due to: {e})"
312
- )
313
308
 
314
309
  target_agent_id = dep_info.get("agent_id")
315
- self.logger.debug(
316
- f"🔍 Target agent ID from registry: '{target_agent_id}'"
317
- )
318
310
 
319
311
  # Determine if this is a self-dependency (less common for API services)
320
312
  is_self_dependency = (
@@ -323,12 +315,6 @@ class APIDependencyResolutionStep(PipelineStep):
323
315
  and current_agent_id == target_agent_id
324
316
  )
325
317
 
326
- self.logger.debug(
327
- f"🔍 Self-dependency check for '{capability}': "
328
- f"current='{current_agent_id}' vs target='{target_agent_id}' "
329
- f"→ {'SELF' if is_self_dependency else 'CROSS'}-dependency"
330
- )
331
-
332
318
  if is_self_dependency:
333
319
  # Create self-dependency proxy with WRAPPER function (not original)
334
320
  # The wrapper has dependency injection logic, so calling it ensures
@@ -336,16 +322,13 @@ class APIDependencyResolutionStep(PipelineStep):
336
322
  wrapper_func = None
337
323
  if dep_function_name in mesh_tools:
338
324
  wrapper_func = mesh_tools[dep_function_name].function
339
- self.logger.debug(
340
- f"🔍 Found wrapper for '{dep_function_name}' in DecoratorRegistry"
341
- )
342
325
 
343
326
  if wrapper_func:
344
327
  new_proxy = SelfDependencyProxy(
345
328
  wrapper_func, dep_function_name
346
329
  )
347
- self.logger.info(
348
- f"🔄 API SELF-DEPENDENCY: Using wrapper for '{capability}' "
330
+ self.logger.debug(
331
+ f"API SELF-DEPENDENCY: Using wrapper for '{capability}' "
349
332
  f"(local call with full DI support)"
350
333
  )
351
334
  else:
@@ -380,57 +363,57 @@ class APIDependencyResolutionStep(PipelineStep):
380
363
  kwargs_config=kwargs_config,
381
364
  )
382
365
 
383
- # Register with composite key using func_id (not tool name) to match injector lookup
384
- dep_key = f"{func_id}:dep_{dep_index}"
385
- self.logger.debug(
386
- f"🔄 Before update: registering {dep_key} = {type(new_proxy).__name__}"
387
- )
388
- await injector.register_dependency(dep_key, new_proxy)
389
- updated_count += 1
390
-
391
- # Log which functions will be affected
392
- affected_functions = injector._dependency_mapping.get(
393
- dep_key, set()
394
- )
395
- self.logger.debug(
396
- f"🎯 Functions affected by '{capability}' at position {dep_index}: {list(affected_functions)}"
397
- )
366
+ # For route paths, directly update the wrapper's dependencies
367
+ # This bypasses the injector key-based lookup which doesn't work for routes
368
+ if route_wrapper_info:
369
+ wrapper = route_wrapper_info.get("wrapper")
370
+ if wrapper and hasattr(wrapper, "_mesh_update_dependency"):
371
+ wrapper._mesh_update_dependency(dep_index, new_proxy)
372
+ updated_count += 1
373
+ self.logger.debug(
374
+ f"Updated route dependency '{capability}' at position {dep_index} "
375
+ f"→ {endpoint}/{dep_function_name} for route '{function_name}'"
376
+ )
377
+ else:
378
+ self.logger.warning(
379
+ f"Route wrapper for '{function_name}' doesn't have _mesh_update_dependency method"
380
+ )
381
+ else:
382
+ # Fallback: Register with composite key using func_id for MCP tools
383
+ dep_key = f"{func_id}:dep_{dep_index}"
384
+ await injector.register_dependency(dep_key, new_proxy)
385
+ updated_count += 1
386
+
387
+ # Log which functions will be affected
388
+ affected_functions = injector._dependency_mapping.get(
389
+ dep_key, set()
390
+ )
391
+ self.logger.debug(
392
+ f"Functions affected by '{capability}' at position {dep_index}: {list(affected_functions)}"
393
+ )
398
394
 
399
- self.logger.info(
400
- f"🔄 Updated API dependency '{capability}' at position {dep_index} → {endpoint}/{dep_function_name} "
401
- f"(proxy: EnhancedUnifiedMCPProxy - consistent with MCP pipeline)"
402
- )
403
- self.logger.debug(
404
- f"🔗 Registered dependency '{capability}' at position {dep_index} with key '{dep_key}' (func_id: {func_id})"
405
- )
395
+ self.logger.debug(
396
+ f"Updated API dependency '{capability}' at position {dep_index} → {endpoint}/{dep_function_name}"
397
+ )
398
+ self.logger.debug(
399
+ f"Registered dependency '{capability}' at position {dep_index} with key '{dep_key}' (func_id: {func_id})"
400
+ )
406
401
  else:
407
402
  if status != "available":
408
403
  self.logger.debug(
409
- f"⚠️ API dependency '{capability}' at position {dep_index} not available: {status}"
404
+ f"API dependency '{capability}' at position {dep_index} not available: {status}"
410
405
  )
411
406
  else:
412
407
  self.logger.warning(
413
- f"⚠️ Cannot update API dependency '{capability}' at position {dep_index}: missing endpoint or function_name"
408
+ f"Cannot update API dependency '{capability}' at position {dep_index}: missing endpoint or function_name"
414
409
  )
415
410
 
416
411
  # Store new hash for next comparison (use global variable)
417
412
  _last_api_dependency_hash = current_hash
418
413
 
419
- if unwired_count > 0 and updated_count > 0:
420
- self.logger.info(
421
- f" Successfully unwired {unwired_count} and updated {updated_count} API dependencies (state hash: {current_hash})"
422
- )
423
- elif unwired_count > 0:
424
- self.logger.info(
425
- f"✅ Successfully unwired {unwired_count} API dependencies (state hash: {current_hash})"
426
- )
427
- elif updated_count > 0:
428
- self.logger.info(
429
- f"✅ Successfully updated {updated_count} API dependencies (state hash: {current_hash})"
430
- )
431
- else:
432
- self.logger.info(
433
- f"✅ API dependency state synchronized (state hash: {current_hash})"
414
+ if unwired_count > 0 or updated_count > 0:
415
+ self.logger.debug(
416
+ f"API dependency sync: unwired={unwired_count}, updated={updated_count} (hash: {current_hash})"
434
417
  )
435
418
 
436
419
  except Exception as e:
@@ -482,8 +465,7 @@ class APIDependencyResolutionStep(PipelineStep):
482
465
  Proxy instance
483
466
  """
484
467
  from ...engine.full_mcp_proxy import EnhancedFullMCPProxy, FullMCPProxy
485
- from ...engine.mcp_client_proxy import (EnhancedMCPClientProxy,
486
- MCPClientProxy)
468
+ from ...engine.mcp_client_proxy import EnhancedMCPClientProxy, MCPClientProxy
487
469
 
488
470
  if proxy_type == "FullMCPProxy":
489
471
  # Use enhanced proxy if kwargs available