mcp-mesh 0.7.21__py3-none-any.whl → 0.8.0b1__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.
Files changed (121) hide show
  1. _mcp_mesh/__init__.py +1 -1
  2. _mcp_mesh/engine/dependency_injector.py +4 -6
  3. _mcp_mesh/engine/http_wrapper.py +69 -10
  4. _mcp_mesh/engine/mesh_llm_agent.py +4 -7
  5. _mcp_mesh/engine/mesh_llm_agent_injector.py +2 -1
  6. _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
  7. _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
  8. _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
  9. _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
  10. _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
  11. _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
  12. _mcp_mesh/engine/response_parser.py +61 -15
  13. _mcp_mesh/engine/unified_mcp_proxy.py +18 -34
  14. _mcp_mesh/pipeline/__init__.py +9 -20
  15. _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
  16. _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
  17. _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +425 -0
  18. _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
  19. _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
  20. _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
  21. _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
  22. _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
  23. _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
  24. _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +695 -0
  25. _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
  26. _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
  27. _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +5 -6
  28. _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
  29. _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +21 -9
  30. _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
  31. _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
  32. _mcp_mesh/reload.py +1 -3
  33. _mcp_mesh/shared/__init__.py +2 -8
  34. _mcp_mesh/shared/config_resolver.py +124 -80
  35. _mcp_mesh/shared/defaults.py +89 -14
  36. _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
  37. _mcp_mesh/shared/host_resolver.py +8 -46
  38. _mcp_mesh/shared/server_discovery.py +115 -86
  39. _mcp_mesh/shared/simple_shutdown.py +44 -86
  40. _mcp_mesh/tracing/execution_tracer.py +2 -6
  41. _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
  42. _mcp_mesh/tracing/trace_context_helper.py +3 -13
  43. _mcp_mesh/tracing/utils.py +29 -15
  44. _mcp_mesh/utils/fastmcp_schema_extractor.py +2 -1
  45. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/METADATA +2 -1
  46. mcp_mesh-0.8.0b1.dist-info/RECORD +85 -0
  47. mesh/__init__.py +2 -1
  48. mesh/decorators.py +89 -5
  49. _mcp_mesh/generated/.openapi-generator/FILES +0 -50
  50. _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
  51. _mcp_mesh/generated/.openapi-generator-ignore +0 -15
  52. _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
  53. _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
  54. _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
  55. _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
  56. _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
  57. _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
  58. _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
  59. _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
  60. _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
  61. _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
  62. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
  63. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
  64. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
  65. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
  66. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
  67. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
  68. _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
  69. _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
  70. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
  71. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
  72. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
  73. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
  74. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
  75. _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
  76. _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
  77. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
  78. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
  79. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
  80. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
  81. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
  82. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
  83. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
  84. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
  85. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
  86. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
  87. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
  88. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
  89. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
  90. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
  91. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
  92. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
  93. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
  94. _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
  95. _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
  96. _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
  97. _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
  98. _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
  99. _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
  100. _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  101. _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
  102. _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
  103. _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
  104. _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
  105. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
  106. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
  107. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
  108. _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
  109. _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
  110. _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
  111. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
  112. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
  113. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
  114. _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
  115. _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
  116. _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
  117. _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
  118. _mcp_mesh/shared/registry_client_wrapper.py +0 -515
  119. mcp_mesh-0.7.21.dist-info/RECORD +0 -152
  120. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/WHEEL +0 -0
  121. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -5,12 +5,15 @@ Optimized for OpenAI models (GPT-4, GPT-4 Turbo, GPT-3.5-turbo)
5
5
  using OpenAI's native structured output capabilities.
6
6
  """
7
7
 
8
- import json
9
8
  from typing import Any, Optional
10
9
 
11
10
  from pydantic import BaseModel
12
11
 
13
- from .base_provider_handler import BaseProviderHandler
12
+ from .base_provider_handler import (
13
+ BASE_TOOL_INSTRUCTIONS,
14
+ BaseProviderHandler,
15
+ make_schema_strict,
16
+ )
14
17
 
15
18
 
16
19
  class OpenAIHandler(BaseProviderHandler):
@@ -94,8 +97,8 @@ class OpenAIHandler(BaseProviderHandler):
94
97
  schema = output_type.model_json_schema()
95
98
 
96
99
  # Transform schema for OpenAI strict mode
97
- # OpenAI requires additionalProperties: false on all object schemas
98
- schema = self._add_additional_properties_false(schema)
100
+ # OpenAI requires additionalProperties: false and all properties in required
101
+ schema = make_schema_strict(schema, add_all_required=True)
99
102
 
100
103
  # OpenAI structured output format
101
104
  # See: https://platform.openai.com/docs/guides/structured-outputs
@@ -143,14 +146,7 @@ class OpenAIHandler(BaseProviderHandler):
143
146
 
144
147
  # Add tool calling instructions if tools available
145
148
  if tool_schemas:
146
- system_content += """
147
-
148
- IMPORTANT TOOL CALLING RULES:
149
- - You have access to tools that you can call to gather information
150
- - Make ONE tool call at a time
151
- - After receiving tool results, you can make additional calls if needed
152
- - Once you have all needed information, provide your final response
153
- """
149
+ system_content += BASE_TOOL_INSTRUCTIONS
154
150
 
155
151
  # Skip JSON note for str return type (text mode)
156
152
  if output_type is str:
@@ -181,54 +177,3 @@ IMPORTANT TOOL CALLING RULES:
181
177
  "json_mode": True, # Has dedicated JSON mode via response_format
182
178
  }
183
179
 
184
- def _add_additional_properties_false(
185
- self, schema: dict[str, Any]
186
- ) -> dict[str, Any]:
187
- """
188
- Recursively add additionalProperties: false to all object schemas.
189
-
190
- OpenAI strict mode requires this for all object schemas.
191
- See: https://platform.openai.com/docs/guides/structured-outputs
192
-
193
- Args:
194
- schema: JSON schema from Pydantic model
195
-
196
- Returns:
197
- Modified schema with additionalProperties: false on all objects
198
- """
199
- import copy
200
-
201
- schema = copy.deepcopy(schema)
202
- self._add_additional_properties_recursive(schema)
203
- return schema
204
-
205
- def _add_additional_properties_recursive(self, obj: Any) -> None:
206
- """Recursively process schema for OpenAI strict mode compliance."""
207
- if isinstance(obj, dict):
208
- # If this is an object type, add additionalProperties: false
209
- # and ensure required includes all properties
210
- if obj.get("type") == "object":
211
- obj["additionalProperties"] = False
212
- # OpenAI strict mode: required must include ALL property keys
213
- if "properties" in obj:
214
- obj["required"] = list(obj["properties"].keys())
215
-
216
- # Process $defs (Pydantic uses this for nested models)
217
- if "$defs" in obj:
218
- for def_schema in obj["$defs"].values():
219
- self._add_additional_properties_recursive(def_schema)
220
-
221
- # Process properties
222
- if "properties" in obj:
223
- for prop_schema in obj["properties"].values():
224
- self._add_additional_properties_recursive(prop_schema)
225
-
226
- # Process items (for arrays)
227
- if "items" in obj:
228
- self._add_additional_properties_recursive(obj["items"])
229
-
230
- # Process anyOf, oneOf, allOf
231
- for key in ("anyOf", "oneOf", "allOf"):
232
- if key in obj:
233
- for item in obj[key]:
234
- self._add_additional_properties_recursive(item)
@@ -5,10 +5,11 @@ Manages selection and instantiation of provider handlers based on vendor name.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Dict, Optional, Type
8
+ from typing import Optional
9
9
 
10
10
  from .base_provider_handler import BaseProviderHandler
11
11
  from .claude_handler import ClaudeHandler
12
+ from .gemini_handler import GeminiHandler
12
13
  from .generic_handler import GenericHandler
13
14
  from .openai_handler import OpenAIHandler
14
15
 
@@ -39,16 +40,17 @@ class ProviderHandlerRegistry:
39
40
  """
40
41
 
41
42
  # Built-in vendor mappings
42
- _handlers: Dict[str, Type[BaseProviderHandler]] = {
43
+ _handlers: dict[str, type[BaseProviderHandler]] = {
43
44
  "anthropic": ClaudeHandler,
44
45
  "openai": OpenAIHandler,
46
+ "gemini": GeminiHandler,
45
47
  }
46
48
 
47
49
  # Cache of instantiated handlers (singleton per vendor)
48
- _instances: Dict[str, BaseProviderHandler] = {}
50
+ _instances: dict[str, BaseProviderHandler] = {}
49
51
 
50
52
  @classmethod
51
- def register(cls, vendor: str, handler_class: Type[BaseProviderHandler]) -> None:
53
+ def register(cls, vendor: str, handler_class: type[BaseProviderHandler]) -> None:
52
54
  """
53
55
  Register a custom provider handler.
54
56
 
@@ -73,7 +75,9 @@ class ProviderHandlerRegistry:
73
75
  )
74
76
 
75
77
  cls._handlers[vendor] = handler_class
76
- logger.info(f"📝 Registered provider handler: {vendor} -> {handler_class.__name__}")
78
+ logger.info(
79
+ f"📝 Registered provider handler: {vendor} -> {handler_class.__name__}"
80
+ )
77
81
 
78
82
  # Clear cached instance if it exists (force re-instantiation)
79
83
  if vendor in cls._instances:
@@ -119,9 +123,7 @@ class ProviderHandlerRegistry:
119
123
  # Get handler class (or fallback to Generic)
120
124
  if vendor in cls._handlers:
121
125
  handler_class = cls._handlers[vendor]
122
- logger.info(
123
- f"✅ Selected {handler_class.__name__} for vendor: {vendor}"
124
- )
126
+ logger.info(f"✅ Selected {handler_class.__name__} for vendor: {vendor}")
125
127
  else:
126
128
  handler_class = GenericHandler
127
129
  if vendor != "unknown":
@@ -132,14 +134,18 @@ class ProviderHandlerRegistry:
132
134
  logger.debug("Using GenericHandler for unknown vendor")
133
135
 
134
136
  # Instantiate and cache
135
- handler = handler_class() if handler_class != GenericHandler else GenericHandler(vendor)
137
+ handler = (
138
+ handler_class()
139
+ if handler_class != GenericHandler
140
+ else GenericHandler(vendor)
141
+ )
136
142
  cls._instances[vendor] = handler
137
143
 
138
144
  logger.debug(f"🆕 Instantiated handler: {handler}")
139
145
  return handler
140
146
 
141
147
  @classmethod
142
- def list_vendors(cls) -> Dict[str, str]:
148
+ def list_vendors(cls) -> dict[str, str]:
143
149
  """
144
150
  List all registered vendors and their handlers.
145
151
 
@@ -94,8 +94,9 @@ class ResponseParser:
94
94
 
95
95
  Tries multiple strategies to find JSON in mixed responses:
96
96
  1. Find ```json ... ``` code fence blocks
97
- 2. Find any JSON object {...} in the content
98
- 3. Return original content if no extraction needed
97
+ 2. Find any JSON object {...} using progressive json.loads
98
+ 3. Find any JSON array [...] using progressive json.loads
99
+ 4. Return original content if no extraction needed
99
100
 
100
101
  Args:
101
102
  content: Raw content that may contain narrative, XML, and JSON
@@ -109,25 +110,70 @@ class ResponseParser:
109
110
  extracted = json_match.group(1).strip()
110
111
  return extracted
111
112
 
112
- # Strategy 2: Try to find any JSON object {...} in content
113
- # Look for balanced braces starting with { and ending with }
113
+ # Strategy 2: Try to find JSON object using progressive json.loads
114
+ # This correctly handles braces inside string values
114
115
  brace_start = content.find("{")
115
116
  if brace_start != -1:
116
- # Find matching closing brace
117
- brace_count = 0
118
- for i in range(brace_start, len(content)):
119
- if content[i] == "{":
120
- brace_count += 1
121
- elif content[i] == "}":
122
- brace_count -= 1
123
- if brace_count == 0:
124
- # Found matching brace
125
- extracted = content[brace_start : i + 1]
126
- return extracted
117
+ result = ResponseParser._try_progressive_parse(
118
+ content, brace_start, "{", "}"
119
+ )
120
+ if result:
121
+ return result
122
+
123
+ # Strategy 3: Try to find JSON array using progressive json.loads
124
+ bracket_start = content.find("[")
125
+ if bracket_start != -1:
126
+ result = ResponseParser._try_progressive_parse(
127
+ content, bracket_start, "[", "]"
128
+ )
129
+ if result:
130
+ return result
127
131
 
128
132
  # No JSON found, return original
129
133
  return content
130
134
 
135
+ @staticmethod
136
+ def _try_progressive_parse(
137
+ content: str, start: int, open_char: str, close_char: str
138
+ ) -> str | None:
139
+ """
140
+ Try to extract valid JSON by progressively extending the end position.
141
+ This correctly handles braces/brackets inside string values.
142
+
143
+ Args:
144
+ content: The full content string
145
+ start: Starting index of the JSON
146
+ open_char: Opening character ('{' or '[')
147
+ close_char: Closing character ('}' or ']')
148
+
149
+ Returns:
150
+ Extracted JSON string or None if not found
151
+ """
152
+ # Find potential end positions based on depth counting
153
+ depth = 0
154
+ potential_ends: list[int] = []
155
+
156
+ for i in range(start, len(content)):
157
+ char = content[i]
158
+ if char == open_char:
159
+ depth += 1
160
+ elif char == close_char:
161
+ depth -= 1
162
+ if depth == 0:
163
+ potential_ends.append(i)
164
+
165
+ # Try each potential end position (shortest first for efficiency)
166
+ for end in potential_ends:
167
+ candidate = content[start : end + 1]
168
+ try:
169
+ json.loads(candidate)
170
+ return candidate
171
+ except json.JSONDecodeError:
172
+ # Not valid JSON, try next potential end
173
+ continue
174
+
175
+ return None
176
+
131
177
  @staticmethod
132
178
  def _strip_markdown_fences(content: str) -> str:
133
179
  """
@@ -16,6 +16,8 @@ from ..shared.logging_config import (
16
16
  get_trace_prefix,
17
17
  )
18
18
  from ..shared.sse_parser import SSEParser
19
+ from ..tracing.context import TraceContext
20
+ from ..tracing.utils import generate_span_id
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -178,37 +180,6 @@ class UnifiedMCPProxy:
178
180
  f"FastMCP client failed: {e}"
179
181
  ) # Convert to ImportError to trigger fallback
180
182
 
181
- def _get_trace_headers(self) -> dict[str, str]:
182
- """Extract trace headers from current context for distributed tracing.
183
-
184
- Returns:
185
- Dict of trace headers or empty dict if no trace context available
186
- """
187
- try:
188
- from ..tracing.context import TraceContext
189
-
190
- current_trace = TraceContext.get_current()
191
- if current_trace:
192
- headers = {
193
- "X-Trace-ID": current_trace.trace_id,
194
- "X-Parent-Span": current_trace.span_id, # Current span becomes parent for downstream
195
- }
196
- self.logger.info(
197
- f"🔗 TRACE_PROPAGATION: Injecting headers trace_id={current_trace.trace_id[:8]}... "
198
- f"parent_span={current_trace.span_id[:8]}..."
199
- )
200
- return headers
201
- else:
202
- self.logger.warning("🔗 TRACE_PROPAGATION: No trace context available")
203
- return {}
204
-
205
- except Exception as e:
206
- # Never fail MCP calls due to tracing issues
207
- self.logger.warning(
208
- f"🔗 TRACE_PROPAGATION: Exception getting trace context: {e}"
209
- )
210
- return {}
211
-
212
183
  def _configure_from_kwargs(self):
213
184
  """Auto-configure proxy settings from kwargs."""
214
185
  # Basic configuration
@@ -433,6 +404,19 @@ class UnifiedMCPProxy:
433
404
  # Get trace prefix if available
434
405
  tp = get_trace_prefix()
435
406
 
407
+ # Inject trace context into arguments for downstream agents
408
+ # This is the fallback mechanism for agents that can't access HTTP headers (e.g., TypeScript)
409
+ args_with_trace = dict(arguments) if arguments else {}
410
+ current_trace = TraceContext.get_current()
411
+ if current_trace:
412
+ # Use current function's span_id as parent for downstream call
413
+ # Don't generate a new span - that creates unpublished "ghost" spans that break the tree
414
+ args_with_trace["_trace_id"] = current_trace.trace_id
415
+ args_with_trace["_parent_span"] = current_trace.span_id
416
+ self.logger.debug(
417
+ f"{tp}🔗 Injecting trace context: trace_id={current_trace.trace_id[:8]}..., parent_span={current_trace.span_id[:8]}..."
418
+ )
419
+
436
420
  # Log cross-agent call - summary line
437
421
  arg_keys = list(arguments.keys()) if arguments else []
438
422
  self.logger.debug(
@@ -451,7 +435,7 @@ class UnifiedMCPProxy:
451
435
  async with client_instance as client:
452
436
 
453
437
  # Use FastMCP's call_tool which returns CallToolResult object
454
- result = await client.call_tool(name, arguments or {})
438
+ result = await client.call_tool(name, args_with_trace)
455
439
 
456
440
  # Calculate performance metrics
457
441
  end_time = time.time()
@@ -479,12 +463,12 @@ class UnifiedMCPProxy:
479
463
  self.logger.warning(
480
464
  f"FastMCP Client not available: {e}, falling back to HTTP"
481
465
  )
482
- return await self._fallback_http_call(name, arguments)
466
+ return await self._fallback_http_call(name, args_with_trace)
483
467
  except Exception as e:
484
468
  self.logger.warning(f"FastMCP Client failed: {e}, falling back to HTTP")
485
469
  # Try HTTP fallback
486
470
  try:
487
- result = await self._fallback_http_call(name, arguments)
471
+ result = await self._fallback_http_call(name, args_with_trace)
488
472
  return result
489
473
  except Exception as fallback_error:
490
474
  raise RuntimeError(
@@ -2,31 +2,22 @@
2
2
  MCP Mesh Pipeline Architecture
3
3
 
4
4
  This module provides a clean, explicit pipeline-based architecture for processing
5
- decorators and managing the mesh agent lifecycle. This replaces the scattered
6
- async processing with a clear, sequential flow that can be easily tested and debugged.
5
+ decorators and managing the mesh agent lifecycle. The Rust core handles registry
6
+ communication including heartbeats, dependency resolution, and deregistration.
7
7
 
8
8
  Key Components:
9
9
  - MeshPipeline: Main orchestrator that executes steps in sequence
10
10
  - PipelineStep: Interface for individual processing steps
11
11
  - PipelineResult: Result container with status and context
12
- - Built-in steps for common operations (collection, config, heartbeat, etc.)
12
+ - Built-in steps for common operations (collection, config, etc.)
13
13
  """
14
14
 
15
- from .mcp_heartbeat import (
16
- DependencyResolutionStep,
17
- HeartbeatSendStep,
18
- RegistryConnectionStep,
19
- )
15
+ from .mcp_heartbeat import rust_heartbeat_task
16
+ from .mcp_startup import (ConfigurationStep, DecoratorCollectionStep,
17
+ FastAPIServerSetupStep, FastMCPServerDiscoveryStep,
18
+ HeartbeatLoopStep, HeartbeatPreparationStep,
19
+ StartupPipeline)
20
20
  from .shared import MeshPipeline, PipelineResult, PipelineStatus, PipelineStep
21
- from .mcp_startup import (
22
- ConfigurationStep,
23
- DecoratorCollectionStep,
24
- FastAPIServerSetupStep,
25
- FastMCPServerDiscoveryStep,
26
- HeartbeatLoopStep,
27
- HeartbeatPreparationStep,
28
- StartupPipeline,
29
- )
30
21
 
31
22
  __all__ = [
32
23
  "MeshPipeline",
@@ -39,8 +30,6 @@ __all__ = [
39
30
  "FastMCPServerDiscoveryStep",
40
31
  "HeartbeatLoopStep",
41
32
  "HeartbeatPreparationStep",
42
- "RegistryConnectionStep",
43
- "HeartbeatSendStep",
44
- "DependencyResolutionStep",
45
33
  "StartupPipeline",
34
+ "rust_heartbeat_task",
46
35
  ]
@@ -3,14 +3,19 @@ API heartbeat pipeline for FastAPI integration.
3
3
 
4
4
  Provides periodic service registration and health monitoring
5
5
  for FastAPI applications using @mesh.route decorators.
6
+
7
+ Uses Rust core for registry communication, dependency resolution,
8
+ and deregistration.
6
9
  """
7
10
 
8
- from .api_heartbeat_pipeline import APIHeartbeatPipeline
9
- from .api_heartbeat_orchestrator import APIHeartbeatOrchestrator
10
- from .api_dependency_resolution import APIDependencyResolutionStep
11
+ from .api_lifespan_integration import (api_heartbeat_lifespan_task,
12
+ create_api_lifespan_handler,
13
+ integrate_api_heartbeat_with_fastapi)
14
+ from .rust_api_heartbeat import rust_api_heartbeat_task
11
15
 
12
16
  __all__ = [
13
- "APIHeartbeatPipeline",
14
- "APIHeartbeatOrchestrator",
15
- "APIDependencyResolutionStep",
16
- ]
17
+ "api_heartbeat_lifespan_task",
18
+ "create_api_lifespan_handler",
19
+ "integrate_api_heartbeat_with_fastapi",
20
+ "rust_api_heartbeat_task",
21
+ ]
@@ -1,8 +1,10 @@
1
1
  """
2
2
  FastAPI lifespan integration for API heartbeat pipeline.
3
3
 
4
- Handles the execution of API heartbeat pipeline as a background task
4
+ Handles the execution of API heartbeat as a background task
5
5
  during FastAPI application lifespan for @mesh.route decorator services.
6
+
7
+ Uses the Rust core for registry communication.
6
8
  """
7
9
 
8
10
  import asyncio
@@ -14,65 +16,35 @@ logger = logging.getLogger(__name__)
14
16
 
15
17
  async def api_heartbeat_lifespan_task(heartbeat_config: dict[str, Any]) -> None:
16
18
  """
17
- API heartbeat task that runs in FastAPI lifespan using pipeline architecture.
19
+ API heartbeat task that runs in FastAPI lifespan.
20
+
21
+ Uses Rust-backed heartbeat for registry communication.
18
22
 
19
23
  Args:
20
- heartbeat_config: Configuration containing service_id, interval,
24
+ heartbeat_config: Configuration containing service_id, interval,
21
25
  and context for API heartbeat execution
22
26
  """
23
- service_id = heartbeat_config["service_id"]
24
- interval = heartbeat_config["interval"] # Already validated by get_config_value in setup
25
- context = heartbeat_config["context"]
27
+ service_id = heartbeat_config.get("service_id", "unknown-api-service")
26
28
  standalone_mode = heartbeat_config.get("standalone_mode", False)
27
29
 
28
30
  # Check if running in standalone mode
29
31
  if standalone_mode:
30
32
  logger.info(
31
- f"💓 Starting API heartbeat pipeline in standalone mode for service '{service_id}' "
33
+ f"💓 API heartbeat in standalone mode for service '{service_id}' "
32
34
  f"(no registry communication)"
33
35
  )
34
- return # For now, skip heartbeat in standalone mode
35
-
36
- # Create API heartbeat orchestrator for pipeline execution
37
- from .api_heartbeat_orchestrator import APIHeartbeatOrchestrator
36
+ return
38
37
 
39
- api_heartbeat_orchestrator = APIHeartbeatOrchestrator()
38
+ # Use Rust-backed heartbeat
39
+ from .rust_api_heartbeat import rust_api_heartbeat_task
40
40
 
41
- logger.info(f"💓 Starting API heartbeat pipeline task for service '{service_id}'")
42
-
43
- try:
44
- while True:
45
- try:
46
- # Execute API heartbeat pipeline
47
- success = await api_heartbeat_orchestrator.execute_api_heartbeat(
48
- service_id, context
49
- )
50
-
51
- if not success:
52
- # Log failure but continue to next cycle (pipeline handles detailed logging)
53
- logger.debug(
54
- f"💔 API heartbeat pipeline failed for service '{service_id}' - "
55
- f"continuing to next cycle"
56
- )
57
-
58
- except Exception as e:
59
- # Log pipeline execution error but continue to next cycle for resilience
60
- logger.error(
61
- f"❌ API heartbeat pipeline execution error for service '{service_id}': {e}"
62
- )
63
- # Continue to next cycle - heartbeat should be resilient
64
-
65
- # Wait for next heartbeat interval
66
- await asyncio.sleep(interval)
67
-
68
- except asyncio.CancelledError:
69
- logger.info(f"🛑 API heartbeat pipeline task cancelled for service '{service_id}'")
70
- raise
41
+ logger.info(f"💓 Using Rust-backed heartbeat for API service '{service_id}'")
42
+ await rust_api_heartbeat_task(heartbeat_config)
71
43
 
72
44
 
73
45
  def create_api_lifespan_handler(heartbeat_config: dict[str, Any]) -> Any:
74
46
  """
75
- Create a FastAPI lifespan context manager that runs API heartbeat pipeline.
47
+ Create a FastAPI lifespan context manager that runs API heartbeat.
76
48
 
77
49
  Args:
78
50
  heartbeat_config: Configuration for API heartbeat execution
@@ -100,11 +72,13 @@ def create_api_lifespan_handler(heartbeat_config: dict[str, Any]) -> Any:
100
72
  # Cleanup: cancel heartbeat task
101
73
  logger.info(f"🛑 Shutting down FastAPI lifespan for service '{service_id}'")
102
74
  heartbeat_task.cancel()
103
-
75
+
104
76
  try:
105
77
  await heartbeat_task
106
78
  except asyncio.CancelledError:
107
- logger.info(f"✅ API heartbeat task cancelled for service '{service_id}'")
79
+ logger.info(
80
+ f"✅ API heartbeat task cancelled for service '{service_id}'"
81
+ )
108
82
 
109
83
  return api_lifespan
110
84
 
@@ -113,18 +87,18 @@ def integrate_api_heartbeat_with_fastapi(
113
87
  fastapi_app: Any, heartbeat_config: dict[str, Any]
114
88
  ) -> None:
115
89
  """
116
- Integrate API heartbeat pipeline with FastAPI lifespan events.
90
+ Integrate API heartbeat with FastAPI lifespan events.
117
91
 
118
92
  Args:
119
93
  fastapi_app: FastAPI application instance
120
94
  heartbeat_config: Configuration for heartbeat execution
121
95
  """
122
96
  service_id = heartbeat_config.get("service_id", "unknown")
123
-
97
+
124
98
  try:
125
99
  # Check if FastAPI app already has a lifespan handler
126
100
  existing_lifespan = getattr(fastapi_app, "router.lifespan_context", None)
127
-
101
+
128
102
  if existing_lifespan is not None:
129
103
  logger.warning(
130
104
  f"⚠️ FastAPI app already has lifespan handler - "
@@ -144,4 +118,4 @@ def integrate_api_heartbeat_with_fastapi(
144
118
  f"❌ Failed to integrate API heartbeat with FastAPI lifespan "
145
119
  f"for service '{service_id}': {e}"
146
120
  )
147
- raise
121
+ raise