mcp-mesh 0.5.5__py3-none-any.whl → 0.5.7__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.5.5"
34
+ __version__ = "0.5.7"
35
35
 
36
36
  # Store reference to runtime processor if initialized
37
37
  _runtime_processor = None
@@ -2,7 +2,7 @@
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
5
+ Focused purely on dependency injection - telemetry/tracing is handled at
6
6
  the HTTP middleware layer for unified approach across MCP agents and FastAPI apps.
7
7
  """
8
8
 
@@ -57,17 +57,17 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
57
57
  logger.warning(
58
58
  f"Single parameter '{param_name}' in function '{func_name}' found, "
59
59
  f"injecting {dependencies[0] if dependencies else 'dependency'} proxy "
60
- f"(consider typing as McpMeshAgent for clarity)"
60
+ f"(consider typing as McpMeshAgent or McpAgent for clarity)"
61
61
  )
62
62
  return [0] # Inject into the single parameter
63
63
 
64
- # Multiple parameters rule: only inject into McpMeshAgent typed parameters
64
+ # Multiple parameters rule: only inject into McpMeshAgent or McpAgent typed parameters
65
65
  if param_count > 1:
66
66
  if not mesh_positions:
67
67
  logger.warning(
68
68
  f"⚠️ Function '{func_name}' has {param_count} parameters but none are "
69
- f"typed as McpMeshAgent. Skipping injection of {len(dependencies)} dependencies. "
70
- f"Consider typing dependency parameters as McpMeshAgent."
69
+ f"typed as McpMeshAgent or McpAgent. Skipping injection of {len(dependencies)} dependencies. "
70
+ f"Consider typing dependency parameters as McpMeshAgent or McpAgent."
71
71
  )
72
72
  return []
73
73
 
@@ -77,7 +77,7 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
77
77
  excess_deps = dependencies[len(mesh_positions) :]
78
78
  logger.warning(
79
79
  f"Function '{func_name}' has {len(dependencies)} dependencies "
80
- f"but only {len(mesh_positions)} McpMeshAgent parameters. "
80
+ f"but only {len(mesh_positions)} McpMeshAgent/McpAgent parameters. "
81
81
  f"Dependencies {excess_deps} will not be injected."
82
82
  )
83
83
  else:
@@ -85,7 +85,7 @@ def analyze_injection_strategy(func: Callable, dependencies: list[str]) -> list[
85
85
  params[pos].name for pos in mesh_positions[len(dependencies) :]
86
86
  ]
87
87
  logger.warning(
88
- f"Function '{func_name}' has {len(mesh_positions)} McpMeshAgent parameters "
88
+ f"Function '{func_name}' has {len(mesh_positions)} McpMeshAgent/McpAgent parameters "
89
89
  f"but only {len(dependencies)} dependencies declared. "
90
90
  f"Parameters {excess_params} will remain None."
91
91
  )
@@ -118,12 +118,17 @@ class DependencyInjector:
118
118
  self._lock = asyncio.Lock()
119
119
 
120
120
  async def register_dependency(self, name: str, instance: Any) -> None:
121
- """Register a new dependency or update existing one."""
121
+ """Register a new dependency or update existing one.
122
+
123
+ Args:
124
+ name: Composite key in format "function_id:dep_N" or legacy capability name
125
+ instance: Proxy instance to register
126
+ """
122
127
  async with self._lock:
123
128
  logger.info(f"📦 Registering dependency: {name}")
124
129
  self._dependencies[name] = instance
125
130
 
126
- # Notify all functions that depend on this
131
+ # Notify all functions that depend on this (using composite keys)
127
132
  if name in self._dependency_mapping:
128
133
  for func_id in self._dependency_mapping[name]:
129
134
  if func_id in self._function_registry:
@@ -132,10 +137,28 @@ class DependencyInjector:
132
137
  f"🔄 UPDATING dependency '{name}' for {func_id} -> {func} at {hex(id(func))}"
133
138
  )
134
139
  if hasattr(func, "_mesh_update_dependency"):
135
- func._mesh_update_dependency(name, instance)
140
+ # Extract dep_index from composite key (format: "function_id:dep_N")
141
+ if ":dep_" in name:
142
+ dep_index_str = name.split(":dep_")[-1]
143
+ try:
144
+ dep_index = int(dep_index_str)
145
+ func._mesh_update_dependency(dep_index, instance)
146
+ except ValueError:
147
+ logger.warning(
148
+ f"⚠️ Invalid dep_index in key '{name}', skipping update"
149
+ )
150
+ else:
151
+ # Legacy format (shouldn't happen with new code)
152
+ logger.warning(
153
+ f"⚠️ Legacy dependency key format '{name}' not supported in array-based injection"
154
+ )
136
155
 
137
156
  async def unregister_dependency(self, name: str) -> None:
138
- """Remove a dependency (e.g., service went down)."""
157
+ """Remove a dependency (e.g., service went down).
158
+
159
+ Args:
160
+ name: Composite key in format "function_id:dep_N" or legacy capability name
161
+ """
139
162
  async with self._lock:
140
163
  logger.info(f"🗑️ INJECTOR: Unregistering dependency: {name}")
141
164
  if name in self._dependencies:
@@ -156,7 +179,21 @@ class DependencyInjector:
156
179
  logger.info(
157
180
  f"🗑️ INJECTOR: Removing {name} from function {func_id}"
158
181
  )
159
- func._mesh_update_dependency(name, None)
182
+ # Extract dep_index from composite key
183
+ if ":dep_" in name:
184
+ dep_index_str = name.split(":dep_")[-1]
185
+ try:
186
+ dep_index = int(dep_index_str)
187
+ func._mesh_update_dependency(dep_index, None)
188
+ except ValueError:
189
+ logger.warning(
190
+ f"⚠️ Invalid dep_index in key '{name}', skipping removal"
191
+ )
192
+ else:
193
+ # Legacy format
194
+ logger.warning(
195
+ f"⚠️ Legacy dependency key format '{name}' not supported in array-based injection"
196
+ )
160
197
  else:
161
198
  logger.warning(
162
199
  f"🗑️ INJECTOR: Function {func_id} has no _mesh_update_dependency method"
@@ -266,15 +303,16 @@ class DependencyInjector:
266
303
  # Get parameter type information for proxy selection
267
304
  parameter_types = get_agent_parameter_types(func)
268
305
 
269
- # Track which dependencies this function needs
270
- for dep in dependencies:
271
- if dep not in self._dependency_mapping:
272
- self._dependency_mapping[dep] = set()
273
- self._dependency_mapping[dep].add(func_id)
306
+ # Track which dependencies this function needs (using composite keys)
307
+ for dep_index, dep in enumerate(dependencies):
308
+ dep_key = f"{func_id}:dep_{dep_index}"
309
+ if dep_key not in self._dependency_mapping:
310
+ self._dependency_mapping[dep_key] = set()
311
+ self._dependency_mapping[dep_key].add(func_id)
274
312
 
275
- # Store current dependency values on the function itself
313
+ # Store current dependency values as array (indexed by position)
276
314
  if not hasattr(func, "_mesh_injected_deps"):
277
- func._mesh_injected_deps = {}
315
+ func._mesh_injected_deps = [None] * len(dependencies)
278
316
 
279
317
  # Store original implementation if not already stored
280
318
  if not hasattr(func, "_mesh_original_func"):
@@ -292,34 +330,45 @@ class DependencyInjector:
292
330
 
293
331
  # Check if we need async wrapper for minimal case
294
332
  if inspect.iscoroutinefunction(func):
333
+
295
334
  @functools.wraps(func)
296
335
  async def minimal_wrapper(*args, **kwargs):
297
336
  # Use ExecutionTracer for functions without dependencies (v0.4.0 style)
298
337
  from ..tracing.execution_tracer import ExecutionTracer
299
- wrapper_logger.debug(f"🔧 DI: Executing async function {func.__name__} (no dependencies)")
300
-
338
+
339
+ wrapper_logger.debug(
340
+ f"🔧 DI: Executing async function {func.__name__} (no dependencies)"
341
+ )
342
+
301
343
  # For async functions without dependencies, use the async tracer
302
344
  return await ExecutionTracer.trace_function_execution_async(
303
345
  func, args, kwargs, [], [], 0, wrapper_logger
304
346
  )
347
+
305
348
  else:
349
+
306
350
  @functools.wraps(func)
307
351
  def minimal_wrapper(*args, **kwargs):
308
352
  # Use ExecutionTracer for functions without dependencies (v0.4.0 style)
309
353
  from ..tracing.execution_tracer import ExecutionTracer
310
- wrapper_logger.debug(f"🔧 DI: Executing sync function {func.__name__} (no dependencies)")
311
-
354
+
355
+ wrapper_logger.debug(
356
+ f"🔧 DI: Executing sync function {func.__name__} (no dependencies)"
357
+ )
358
+
312
359
  # Use original function tracer for functions without dependencies
313
- return ExecutionTracer.trace_original_function(func, args, kwargs, wrapper_logger)
360
+ return ExecutionTracer.trace_original_function(
361
+ func, args, kwargs, wrapper_logger
362
+ )
314
363
 
315
- # Add minimal metadata for compatibility
316
- minimal_wrapper._mesh_injected_deps = {}
364
+ # Add minimal metadata for compatibility (use array for consistency)
365
+ minimal_wrapper._mesh_injected_deps = [None] * len(dependencies)
317
366
  minimal_wrapper._mesh_dependencies = dependencies
318
367
  minimal_wrapper._mesh_positions = mesh_positions
319
368
  minimal_wrapper._mesh_parameter_types = get_agent_parameter_types(func)
320
369
  minimal_wrapper._mesh_original_func = func
321
370
 
322
- def update_dependency(name: str, instance: Any | None) -> None:
371
+ def update_dependency(dep_index: int, instance: Any | None) -> None:
323
372
  """No-op update for functions without injection positions."""
324
373
  pass
325
374
 
@@ -363,7 +412,7 @@ class DependencyInjector:
363
412
  wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: params={params}")
364
413
  wrapper_logger.debug(f"🔧 DEPENDENCY_WRAPPER: original kwargs={kwargs}")
365
414
 
366
- # Inject dependencies as kwargs
415
+ # Inject dependencies as kwargs (using array-based lookup)
367
416
  injected_count = 0
368
417
  for dep_index, param_position in enumerate(mesh_positions):
369
418
  if dep_index < len(dependencies):
@@ -379,24 +428,28 @@ class DependencyInjector:
379
428
  param_name not in final_kwargs
380
429
  or final_kwargs.get(param_name) is None
381
430
  ):
382
- # Get the dependency from wrapper's storage
383
- dependency = dependency_wrapper._mesh_injected_deps.get(
384
- dep_name
385
- )
431
+ # Get the dependency from wrapper's array storage (by index)
432
+ dependency = None
433
+ if dep_index < len(dependency_wrapper._mesh_injected_deps):
434
+ dependency = dependency_wrapper._mesh_injected_deps[
435
+ dep_index
436
+ ]
386
437
  wrapper_logger.debug(
387
- f"🔧 DEPENDENCY_WRAPPER: From wrapper storage: {dependency}"
438
+ f"🔧 DEPENDENCY_WRAPPER: From wrapper storage[{dep_index}]: {dependency}"
388
439
  )
389
440
 
390
441
  if dependency is None:
391
- dependency = self.get_dependency(dep_name)
442
+ # Fallback to global storage with composite key
443
+ dep_key = f"{func.__module__}.{func.__qualname__}:dep_{dep_index}"
444
+ dependency = self.get_dependency(dep_key)
392
445
  wrapper_logger.debug(
393
- f"🔧 DEPENDENCY_WRAPPER: From global storage: {dependency}"
446
+ f"🔧 DEPENDENCY_WRAPPER: From global storage[{dep_key}]: {dependency}"
394
447
  )
395
448
 
396
449
  final_kwargs[param_name] = dependency
397
450
  injected_count += 1
398
451
  wrapper_logger.debug(
399
- f"🔧 DEPENDENCY_WRAPPER: Injected {dep_name} as {param_name}"
452
+ f"🔧 DEPENDENCY_WRAPPER: Injected {dep_name} from index {dep_index} as {param_name}"
400
453
  )
401
454
  else:
402
455
  wrapper_logger.debug(
@@ -413,7 +466,7 @@ class DependencyInjector:
413
466
  # ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
414
467
  # Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
415
468
  from ..tracing.execution_tracer import ExecutionTracer
416
-
469
+
417
470
  original_func = func._mesh_original_func
418
471
 
419
472
  wrapper_logger.debug(
@@ -460,7 +513,7 @@ class DependencyInjector:
460
513
  params = list(sig.parameters.keys())
461
514
  final_kwargs = kwargs.copy()
462
515
 
463
- # Inject dependencies as kwargs
516
+ # Inject dependencies as kwargs (using array-based lookup)
464
517
  injected_count = 0
465
518
  for dep_index, param_position in enumerate(mesh_positions):
466
519
  if dep_index < len(dependencies):
@@ -472,13 +525,17 @@ class DependencyInjector:
472
525
  param_name not in final_kwargs
473
526
  or final_kwargs.get(param_name) is None
474
527
  ):
475
- # Get the dependency from wrapper's storage
476
- dependency = dependency_wrapper._mesh_injected_deps.get(
477
- dep_name
478
- )
528
+ # Get the dependency from wrapper's array storage (by index)
529
+ dependency = None
530
+ if dep_index < len(dependency_wrapper._mesh_injected_deps):
531
+ dependency = dependency_wrapper._mesh_injected_deps[
532
+ dep_index
533
+ ]
479
534
 
480
535
  if dependency is None:
481
- dependency = self.get_dependency(dep_name)
536
+ # Fallback to global storage with composite key
537
+ dep_key = f"{func.__module__}.{func.__qualname__}:dep_{dep_index}"
538
+ dependency = self.get_dependency(dep_key)
482
539
 
483
540
  final_kwargs[param_name] = dependency
484
541
  injected_count += 1
@@ -486,7 +543,7 @@ class DependencyInjector:
486
543
  # ===== EXECUTE WITH DEPENDENCY INJECTION AND TRACING =====
487
544
  # Use ExecutionTracer for comprehensive execution logging (v0.4.0 style)
488
545
  from ..tracing.execution_tracer import ExecutionTracer
489
-
546
+
490
547
  wrapper_logger.debug(
491
548
  f"🔧 DI: Executing sync function {func._mesh_original_func.__name__} with {injected_count} injected dependencies"
492
549
  )
@@ -502,20 +559,28 @@ class DependencyInjector:
502
559
  wrapper_logger,
503
560
  )
504
561
 
505
- # Store dependency state on wrapper
506
- dependency_wrapper._mesh_injected_deps = {}
507
-
508
- # Add update method to wrapper
509
- def update_dependency(name: str, instance: Any | None) -> None:
510
- """Called when a dependency changes."""
511
- if instance is None:
512
- dependency_wrapper._mesh_injected_deps.pop(name, None)
513
- wrapper_logger.debug(f"Removed {name} from {func_id}")
562
+ # Store dependency state on wrapper as array (indexed by position)
563
+ dependency_wrapper._mesh_injected_deps = [None] * len(dependencies)
564
+
565
+ # Add update method to wrapper (now uses index-based updates)
566
+ def update_dependency(dep_index: int, instance: Any | None) -> None:
567
+ """Called when a dependency changes (index-based for duplicate capability support)."""
568
+ if dep_index < len(dependency_wrapper._mesh_injected_deps):
569
+ dependency_wrapper._mesh_injected_deps[dep_index] = instance
570
+ if instance is None:
571
+ wrapper_logger.debug(
572
+ f"Removed dependency at index {dep_index} from {func_id}"
573
+ )
574
+ else:
575
+ wrapper_logger.debug(
576
+ f"Updated dependency at index {dep_index} for {func_id}"
577
+ )
578
+ wrapper_logger.debug(
579
+ f"🔗 Wrapper pointer receiving dependency: {dependency_wrapper} at {hex(id(dependency_wrapper))}"
580
+ )
514
581
  else:
515
- dependency_wrapper._mesh_injected_deps[name] = instance
516
- wrapper_logger.debug(f"Updated {name} for {func_id}")
517
- wrapper_logger.debug(
518
- f"🔗 Wrapper pointer receiving dependency: {dependency_wrapper} at {hex(id(dependency_wrapper))}"
582
+ wrapper_logger.warning(
583
+ f"⚠️ Attempted to update dependency at index {dep_index} but wrapper only has {len(dependency_wrapper._mesh_injected_deps)} dependencies"
519
584
  )
520
585
 
521
586
  # Store update method on wrapper
@@ -151,13 +151,13 @@ def get_mesh_agent_positions(func: Any) -> list[int]:
151
151
 
152
152
  def get_mesh_agent_parameter_names(func: Any) -> list[str]:
153
153
  """
154
- Get names of McpMeshAgent parameters in function signature.
154
+ Get names of McpMeshAgent/McpAgent parameters in function signature.
155
155
 
156
156
  Args:
157
157
  func: Function to analyze
158
158
 
159
159
  Returns:
160
- List of parameter names that are McpMeshAgent types
160
+ List of parameter names that are McpMeshAgent or McpAgent types
161
161
  """
162
162
  try:
163
163
  type_hints = get_type_hints(func)
@@ -168,22 +168,31 @@ def get_mesh_agent_parameter_names(func: Any) -> list[str]:
168
168
  if param_name in type_hints:
169
169
  param_type = type_hints[param_name]
170
170
 
171
- # Check if it's McpMeshAgent type (handle different import paths and Union types)
171
+ # Check if it's McpAgent or McpMeshAgent type (handle different import paths and Union types)
172
172
  is_mesh_agent = False
173
173
 
174
- # Direct McpMeshAgent type
175
- if param_type == McpMeshAgent or (
176
- hasattr(param_type, "__origin__")
177
- and param_type.__origin__ is type(McpMeshAgent)
174
+ # Direct McpAgent or McpMeshAgent type
175
+ if (
176
+ param_type in (McpAgent, McpMeshAgent)
177
+ or (
178
+ hasattr(param_type, "__name__")
179
+ and param_type.__name__ in ("McpAgent", "McpMeshAgent")
180
+ )
181
+ or (
182
+ hasattr(param_type, "__origin__")
183
+ and param_type.__origin__
184
+ in (type(McpAgent), type(McpMeshAgent))
185
+ )
178
186
  ):
179
187
  is_mesh_agent = True
180
188
 
181
- # Union type (e.g., McpMeshAgent | None)
189
+ # Union type (e.g., McpAgent | None, McpMeshAgent | None)
182
190
  elif hasattr(param_type, "__args__"):
183
- # Check if any arg in the union is McpMeshAgent
191
+ # Check if any arg in the union is McpAgent or McpMeshAgent
184
192
  for arg in param_type.__args__:
185
- if arg == McpMeshAgent or (
186
- hasattr(arg, "__name__") and arg.__name__ == "McpMeshAgent"
193
+ if arg in (McpAgent, McpMeshAgent) or (
194
+ hasattr(arg, "__name__")
195
+ and arg.__name__ in ("McpAgent", "McpMeshAgent")
187
196
  ):
188
197
  is_mesh_agent = True
189
198
  break
@@ -199,7 +208,7 @@ def get_mesh_agent_parameter_names(func: Any) -> list[str]:
199
208
 
200
209
  def validate_mesh_dependencies(func: Any, dependencies: list[dict]) -> tuple[bool, str]:
201
210
  """
202
- Validate that the number of dependencies matches McpMeshAgent parameters.
211
+ Validate that the number of dependencies matches McpMeshAgent/McpAgent parameters.
203
212
 
204
213
  Args:
205
214
  func: Function to validate
@@ -212,9 +221,9 @@ def validate_mesh_dependencies(func: Any, dependencies: list[dict]) -> tuple[boo
212
221
 
213
222
  if len(dependencies) != len(mesh_positions):
214
223
  return False, (
215
- f"Function {func.__name__} has {len(mesh_positions)} McpMeshAgent parameters "
224
+ f"Function {func.__name__} has {len(mesh_positions)} McpMeshAgent/McpAgent parameters "
216
225
  f"but {len(dependencies)} dependencies declared. "
217
- f"Each McpMeshAgent parameter needs a corresponding dependency."
226
+ f"Each McpMeshAgent/McpAgent parameter needs a corresponding dependency."
218
227
  )
219
228
 
220
229
  return True, ""