mcp-mesh 0.7.21__py3-none-any.whl → 0.8.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.
Files changed (124) hide show
  1. _mcp_mesh/__init__.py +1 -1
  2. _mcp_mesh/engine/dependency_injector.py +13 -15
  3. _mcp_mesh/engine/http_wrapper.py +69 -10
  4. _mcp_mesh/engine/mesh_llm_agent.py +29 -10
  5. _mcp_mesh/engine/mesh_llm_agent_injector.py +77 -41
  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/signature_analyzer.py +58 -68
  14. _mcp_mesh/engine/unified_mcp_proxy.py +19 -35
  15. _mcp_mesh/pipeline/__init__.py +9 -20
  16. _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
  17. _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
  18. _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +429 -0
  19. _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
  20. _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
  21. _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
  22. _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
  23. _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
  24. _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
  25. _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +710 -0
  26. _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
  27. _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
  28. _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +31 -8
  29. _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
  30. _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +23 -11
  31. _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
  32. _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
  33. _mcp_mesh/reload.py +1 -3
  34. _mcp_mesh/shared/__init__.py +2 -8
  35. _mcp_mesh/shared/config_resolver.py +124 -80
  36. _mcp_mesh/shared/defaults.py +89 -14
  37. _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
  38. _mcp_mesh/shared/host_resolver.py +8 -46
  39. _mcp_mesh/shared/server_discovery.py +115 -86
  40. _mcp_mesh/shared/simple_shutdown.py +44 -86
  41. _mcp_mesh/tracing/execution_tracer.py +2 -6
  42. _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
  43. _mcp_mesh/tracing/trace_context_helper.py +3 -13
  44. _mcp_mesh/tracing/utils.py +29 -15
  45. _mcp_mesh/utils/fastmcp_schema_extractor.py +5 -4
  46. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/METADATA +7 -5
  47. mcp_mesh-0.8.0.dist-info/RECORD +85 -0
  48. mesh/__init__.py +12 -1
  49. mesh/decorators.py +248 -33
  50. mesh/helpers.py +52 -0
  51. mesh/types.py +40 -13
  52. _mcp_mesh/generated/.openapi-generator/FILES +0 -50
  53. _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
  54. _mcp_mesh/generated/.openapi-generator-ignore +0 -15
  55. _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
  56. _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
  57. _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
  58. _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
  59. _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
  60. _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
  61. _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
  62. _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
  63. _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
  64. _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
  65. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
  66. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
  67. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
  68. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
  69. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
  70. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
  71. _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
  72. _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
  73. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
  74. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
  75. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
  76. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
  77. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
  78. _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
  79. _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
  80. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
  81. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
  82. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
  83. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
  84. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
  85. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
  86. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
  87. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
  88. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
  89. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
  90. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
  91. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
  92. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
  93. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
  94. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
  95. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
  96. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
  97. _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
  98. _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
  99. _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
  100. _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
  101. _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
  102. _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
  103. _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  104. _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
  105. _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
  106. _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
  107. _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
  108. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
  109. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
  110. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
  111. _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
  112. _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
  113. _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
  114. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
  115. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
  116. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
  117. _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
  118. _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
  119. _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
  120. _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
  121. _mcp_mesh/shared/registry_client_wrapper.py +0 -515
  122. mcp_mesh-0.7.21.dist-info/RECORD +0 -152
  123. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/WHEEL +0 -0
  124. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -11,12 +11,12 @@ from ..shared import PipelineResult, PipelineStatus, PipelineStep
11
11
  class APIServerSetupStep(PipelineStep):
12
12
  """
13
13
  Minimal API server setup for FastAPI integration.
14
-
14
+
15
15
  This step prepares the binding configuration and service registration
16
16
  metadata for the user's existing FastAPI application. It does NOT create
17
17
  or modify the FastAPI app - it only prepares the configuration needed
18
18
  to run the app with uvicorn and register it with the mesh registry.
19
-
19
+
20
20
  Our job is ONLY dependency injection - the user owns their FastAPI app.
21
21
  """
22
22
 
@@ -37,7 +37,7 @@ class APIServerSetupStep(PipelineStep):
37
37
  # Verify we have FastAPI apps to work with
38
38
  fastapi_apps = context.get("fastapi_apps", {})
39
39
  integration_results = context.get("integration_results", {})
40
-
40
+
41
41
  if not fastapi_apps:
42
42
  result.status = PipelineStatus.FAILED
43
43
  result.message = "No FastAPI applications found"
@@ -60,35 +60,45 @@ class APIServerSetupStep(PipelineStep):
60
60
  primary_app_id = list(fastapi_apps.keys())[0]
61
61
  primary_app_info = fastapi_apps[primary_app_id]
62
62
  primary_app = primary_app_info["instance"]
63
-
63
+
64
64
  self.logger.info(
65
65
  f"🎯 Using FastAPI app: '{primary_app_info['title']}' as primary app"
66
66
  )
67
67
 
68
68
  # Prepare display configuration for registry (NOT binding configuration)
69
69
  display_config = self._prepare_display_config()
70
-
70
+
71
71
  # Prepare service registration metadata
72
72
  service_metadata = self._prepare_service_metadata(
73
- primary_app_info, integration_results.get(primary_app_id, {}), display_config
73
+ primary_app_info,
74
+ integration_results.get(primary_app_id, {}),
75
+ display_config,
74
76
  )
75
-
77
+
76
78
  # Prepare heartbeat configuration
77
79
  heartbeat_config = self._prepare_heartbeat_config(
78
80
  primary_app_info, display_config, service_metadata
79
81
  )
80
-
82
+
81
83
  # Store configuration in context
82
84
  result.add_context("primary_fastapi_app", primary_app)
83
- result.add_context("fastapi_app", primary_app) # For heartbeat compatibility
85
+ result.add_context(
86
+ "fastapi_app", primary_app
87
+ ) # For heartbeat compatibility
84
88
  result.add_context("api_display_config", display_config)
85
- result.add_context("display_config", display_config) # For heartbeat compatibility
89
+ result.add_context(
90
+ "display_config", display_config
91
+ ) # For heartbeat compatibility
86
92
  result.add_context("api_service_metadata", service_metadata)
87
- result.add_context("service_type", "api") # Important for registry registration
93
+ result.add_context(
94
+ "service_type", "api"
95
+ ) # Important for registry registration
88
96
  result.add_context("heartbeat_config", heartbeat_config)
89
-
97
+
90
98
  # Update result message
91
- integrated_routes = integration_results.get(primary_app_id, {}).get("integrated_count", 0)
99
+ integrated_routes = integration_results.get(primary_app_id, {}).get(
100
+ "integrated_count", 0
101
+ )
92
102
  result.message = (
93
103
  f"API server config prepared for '{primary_app_info['title']}' "
94
104
  f"with {integrated_routes} dependency-injected routes"
@@ -110,65 +120,68 @@ class APIServerSetupStep(PipelineStep):
110
120
  def _prepare_display_config(self) -> Dict[str, Any]:
111
121
  """
112
122
  Prepare display configuration for service registration.
113
-
123
+
114
124
  This is ONLY for registry display purposes since FastAPI services
115
125
  are consumers (nobody needs to communicate TO them in mesh network).
116
126
  Users configure their actual uvicorn host/port separately.
117
127
  """
118
128
  # Get external host for display (what others would see this service as)
119
129
  external_host = HostResolver.get_external_host()
120
-
130
+
121
131
  # Get port for display - users can override via env var
122
132
  display_port = get_config_value(
123
- "MCP_MESH_HTTP_PORT",
133
+ "MCP_MESH_HTTP_PORT",
124
134
  default=8080, # FastAPI convention
125
135
  rule=ValidationRule.PORT_RULE,
126
136
  )
127
-
137
+
128
138
  # Also check if user provided host override
129
139
  display_host = get_config_value(
130
140
  "MCP_MESH_HTTP_HOST",
131
141
  default=external_host,
132
142
  rule=ValidationRule.STRING_RULE,
133
143
  )
134
-
144
+
135
145
  display_config = {
136
146
  "display_host": display_host,
137
147
  "display_port": display_port,
138
148
  "external_host": external_host,
139
149
  }
140
-
150
+
141
151
  self.logger.debug(
142
152
  f"📍 Display config: {display_host}:{display_port} "
143
153
  f"(for registry display only - user controls actual uvicorn binding)"
144
154
  )
145
-
155
+
146
156
  return display_config
147
157
 
148
158
  def _prepare_service_metadata(
149
- self, app_info: Dict[str, Any], integration_results: Dict[str, Any], display_config: Dict[str, Any]
159
+ self,
160
+ app_info: Dict[str, Any],
161
+ integration_results: Dict[str, Any],
162
+ display_config: Dict[str, Any],
150
163
  ) -> Dict[str, Any]:
151
164
  """
152
165
  Prepare service registration metadata for mesh registry.
153
-
166
+
154
167
  This metadata tells the mesh registry about our API service
155
168
  and what capabilities it provides (routes, not MCP tools).
156
169
  """
157
170
  # Extract route information for registry
158
171
  route_capabilities = []
159
172
  route_details = integration_results.get("route_details", {})
160
-
173
+
161
174
  for route_name, details in route_details.items():
162
175
  if details.get("status") == "integrated":
163
176
  # Create capability entry for each dependency-injected route
164
177
  capability_info = {
165
178
  "name": route_name,
166
- "type": "api_route",
179
+ "type": "api_route",
167
180
  "dependencies": details.get("dependencies", []),
168
181
  "dependency_count": details.get("dependency_count", 0),
169
182
  }
170
183
  route_capabilities.append(capability_info)
171
-
184
+
172
185
  # Build service metadata
173
186
  service_metadata = {
174
187
  "service_name": app_info.get("title", "FastAPI Service"),
@@ -184,43 +197,46 @@ class APIServerSetupStep(PipelineStep):
184
197
  "display_port": display_config["display_port"],
185
198
  "external_host": display_config["external_host"],
186
199
  }
187
-
200
+
188
201
  self.logger.debug(
189
202
  f"📋 Service metadata: {service_metadata['service_name']} "
190
203
  f"({len(route_capabilities)} capabilities)"
191
204
  )
192
-
205
+
193
206
  return service_metadata
194
207
 
195
208
  def _prepare_heartbeat_config(
196
- self, app_info: Dict[str, Any], display_config: Dict[str, Any], service_metadata: Dict[str, Any]
209
+ self,
210
+ app_info: Dict[str, Any],
211
+ display_config: Dict[str, Any],
212
+ service_metadata: Dict[str, Any],
197
213
  ) -> Dict[str, Any]:
198
214
  """
199
215
  Prepare heartbeat configuration for API service.
200
-
216
+
201
217
  This configuration will be used to start the heartbeat pipeline
202
218
  for periodic service registration and health monitoring.
203
219
  """
204
220
  # Check if we already have a service ID in the decorator registry
205
221
  # If so, and it's in API format, reuse it to avoid ID changes
206
222
  service_id = self._get_or_generate_api_service_id(app_info)
207
-
223
+
208
224
  # Get heartbeat interval using centralized defaults (consistent with MCP heartbeat)
209
225
  from ...shared.defaults import MeshDefaults
210
-
226
+
211
227
  heartbeat_interval = get_config_value(
212
228
  "MCP_MESH_HEALTH_INTERVAL",
213
229
  default=MeshDefaults.HEALTH_INTERVAL,
214
230
  rule=ValidationRule.NONZERO_RULE,
215
231
  )
216
-
232
+
217
233
  # Check if standalone mode (no registry communication)
218
234
  standalone_mode = get_config_value(
219
235
  "MCP_MESH_STANDALONE",
220
236
  default=False,
221
237
  rule=ValidationRule.TRUTHY_RULE,
222
238
  )
223
-
239
+
224
240
  heartbeat_config = {
225
241
  "service_id": service_id,
226
242
  "interval": heartbeat_interval,
@@ -228,18 +244,17 @@ class APIServerSetupStep(PipelineStep):
228
244
  "context": {
229
245
  # Context will be populated during heartbeat execution
230
246
  # with current pipeline context including fastapi_app, display_config, etc.
231
- }
247
+ },
232
248
  }
233
-
249
+
234
250
  # Store the generated service ID back to decorator registry for telemetry
235
251
  try:
236
252
  from ...engine.decorator_registry import DecoratorRegistry
237
-
238
- DecoratorRegistry.update_agent_config({
239
- "agent_id": service_id,
240
- "name": app_info.get("title", "api-service")
241
- })
242
-
253
+
254
+ DecoratorRegistry.update_agent_config(
255
+ {"agent_id": service_id, "name": app_info.get("title", "api-service")}
256
+ )
257
+
243
258
  self.logger.debug(
244
259
  f"🔧 Stored API service ID '{service_id}' in decorator registry for telemetry"
245
260
  )
@@ -247,56 +262,58 @@ class APIServerSetupStep(PipelineStep):
247
262
  self.logger.warning(
248
263
  f"⚠️ Failed to store API service ID in decorator registry: {e}"
249
264
  )
250
-
265
+
251
266
  self.logger.info(
252
267
  f"💓 API heartbeat config created: service_id='{service_id}', "
253
268
  f"interval={heartbeat_interval}s, standalone={standalone_mode}"
254
269
  )
255
-
270
+
256
271
  return heartbeat_config
257
272
 
258
- def _generate_api_service_id(self, app_info: Optional[Dict[str, Any]] = None) -> str:
273
+ def _generate_api_service_id(
274
+ self, app_info: Optional[Dict[str, Any]] = None
275
+ ) -> str:
259
276
  """
260
277
  Generate API service ID using same priority logic as MCP agents.
261
-
278
+
262
279
  Priority order:
263
- 1. MCP_MESH_API_NAME environment variable
280
+ 1. MCP_MESH_API_NAME environment variable
264
281
  2. MCP_MESH_AGENT_NAME environment variable (fallback)
265
282
  3. Default to "api-{uuid8}"
266
-
283
+
267
284
  Args:
268
285
  app_info: FastAPI app information (optional, not used in simplified logic)
269
-
286
+
270
287
  Returns:
271
288
  Generated service ID with UUID suffix
272
289
  """
273
290
  import uuid
274
-
291
+
275
292
  # Check for API-specific environment variable first
276
293
  api_name = get_config_value(
277
294
  "MCP_MESH_API_NAME",
278
295
  default=None,
279
296
  rule=ValidationRule.STRING_RULE,
280
297
  )
281
-
298
+
282
299
  # Fallback to general agent name env var
283
300
  if not api_name:
284
301
  api_name = get_config_value(
285
- "MCP_MESH_AGENT_NAME",
302
+ "MCP_MESH_AGENT_NAME",
286
303
  default=None,
287
304
  rule=ValidationRule.STRING_RULE,
288
305
  )
289
-
306
+
290
307
  # Clean the service name if provided
291
308
  if api_name:
292
309
  cleaned_name = api_name.lower().replace(" ", "-").replace("_", "-")
293
310
  cleaned_name = "-".join(part for part in cleaned_name.split("-") if part)
294
311
  else:
295
312
  cleaned_name = ""
296
-
297
- # Generate UUID suffix
313
+
314
+ # Generate UUID suffix
298
315
  uuid_suffix = str(uuid.uuid4())[:8]
299
-
316
+
300
317
  # Apply naming logic
301
318
  if not cleaned_name:
302
319
  # No name provided: default to "api-{uuid8}"
@@ -307,39 +324,41 @@ class APIServerSetupStep(PipelineStep):
307
324
  else:
308
325
  # Name doesn't contain "api": use "{name}-api-{uuid8}"
309
326
  service_id = f"{cleaned_name}-api-{uuid_suffix}"
310
-
327
+
311
328
  self.logger.debug(
312
329
  f"Generated API service ID: '{service_id}' from env name: '{api_name}'"
313
330
  )
314
-
331
+
315
332
  return service_id
316
333
 
317
- def _get_or_generate_api_service_id(self, app_info: Optional[Dict[str, Any]] = None) -> str:
334
+ def _get_or_generate_api_service_id(
335
+ self, app_info: Optional[Dict[str, Any]] = None
336
+ ) -> str:
318
337
  """
319
338
  Get existing service ID from decorator registry or generate a new one.
320
-
321
- Since both the fallback and API pipeline now use identical logic based on
339
+
340
+ Since both the fallback and API pipeline now use identical logic based on
322
341
  environment variables, we can simply reuse any existing API service ID.
323
-
342
+
324
343
  Args:
325
344
  app_info: FastAPI app information (optional, not used in simplified logic)
326
-
345
+
327
346
  Returns:
328
347
  Service ID (existing or newly generated)
329
348
  """
330
349
  try:
331
350
  from ...engine.decorator_registry import DecoratorRegistry
332
-
351
+
333
352
  # Get current cached config to see if we already have an ID
334
353
  current_config = DecoratorRegistry.get_resolved_agent_config()
335
354
  existing_id = current_config.get("agent_id", "")
336
-
355
+
337
356
  # Check if existing ID looks like an API service ID
338
357
  is_api_format = (
339
- existing_id.startswith("api-") or # api-{uuid}
340
- "-api-" in existing_id # {name}-api-{uuid}
358
+ existing_id.startswith("api-") # api-{uuid}
359
+ or "-api-" in existing_id # {name}-api-{uuid}
341
360
  )
342
-
361
+
343
362
  if existing_id and is_api_format:
344
363
  # Reuse existing API service ID (fallback and pipeline use same logic now)
345
364
  self.logger.info(
@@ -353,9 +372,11 @@ class APIServerSetupStep(PipelineStep):
353
372
  f"🆕 Generated new API service ID: '{new_id}' (no existing API format ID found)"
354
373
  )
355
374
  return new_id
356
-
375
+
357
376
  except Exception as e:
358
- self.logger.warning(f"⚠️ Error checking existing service ID, generating new one: {e}")
377
+ self.logger.warning(
378
+ f"⚠️ Error checking existing service ID, generating new one: {e}"
379
+ )
359
380
  return self._generate_api_service_id(app_info)
360
381
 
361
382
  def _is_http_enabled(self) -> bool:
@@ -364,4 +385,4 @@ class APIServerSetupStep(PipelineStep):
364
385
  "MCP_MESH_HTTP_ENABLED",
365
386
  default=True,
366
387
  rule=ValidationRule.TRUTHY_RULE,
367
- )
388
+ )
@@ -1,17 +1,17 @@
1
1
  import logging
2
2
  from typing import Any, Dict, List
3
3
 
4
- from ..shared import PipelineResult, PipelineStatus, PipelineStep
5
4
  from ...shared.server_discovery import ServerDiscoveryUtil
5
+ from ..shared import PipelineResult, PipelineStatus, PipelineStep
6
6
 
7
7
 
8
8
  class FastAPIAppDiscoveryStep(PipelineStep):
9
9
  """
10
10
  Discovers existing FastAPI application instances in the user's code.
11
-
11
+
12
12
  This step scans the Python runtime to find FastAPI applications that
13
13
  have been instantiated by the user, without modifying them in any way.
14
-
14
+
15
15
  The goal is minimal intervention - we only discover what exists,
16
16
  we don't create or modify anything.
17
17
  """
@@ -32,7 +32,7 @@ class FastAPIAppDiscoveryStep(PipelineStep):
32
32
  try:
33
33
  # Get route decorators from context (from RouteCollectionStep)
34
34
  mesh_routes = context.get("mesh_routes", {})
35
-
35
+
36
36
  if not mesh_routes:
37
37
  result.status = PipelineStatus.SKIPPED
38
38
  result.message = "No @mesh.route decorators found"
@@ -41,7 +41,7 @@ class FastAPIAppDiscoveryStep(PipelineStep):
41
41
 
42
42
  # Discover FastAPI instances using shared utility
43
43
  fastapi_apps = ServerDiscoveryUtil.discover_fastapi_instances()
44
-
44
+
45
45
  if not fastapi_apps:
46
46
  # This is not necessarily an error - user might be using FastAPI differently
47
47
  result.status = PipelineStatus.FAILED
@@ -56,12 +56,12 @@ class FastAPIAppDiscoveryStep(PipelineStep):
56
56
 
57
57
  # Analyze which routes belong to which apps
58
58
  route_mapping = self._map_routes_to_apps(fastapi_apps, mesh_routes)
59
-
59
+
60
60
  # Store discovery results in context
61
61
  result.add_context("fastapi_apps", fastapi_apps)
62
62
  result.add_context("route_mapping", route_mapping)
63
63
  result.add_context("discovered_app_count", len(fastapi_apps))
64
-
64
+
65
65
  # Update result message
66
66
  route_count = sum(len(routes) for routes in route_mapping.values())
67
67
  result.message = (
@@ -90,50 +90,49 @@ class FastAPIAppDiscoveryStep(PipelineStep):
90
90
 
91
91
  return result
92
92
 
93
-
94
93
  def _map_routes_to_apps(
95
- self,
96
- fastapi_apps: Dict[str, Dict[str, Any]],
97
- mesh_routes: Dict[str, Any]
94
+ self, fastapi_apps: Dict[str, Dict[str, Any]], mesh_routes: Dict[str, Any]
98
95
  ) -> Dict[str, Dict[str, Any]]:
99
96
  """
100
97
  Map @mesh.route decorated functions to their FastAPI applications.
101
-
98
+
102
99
  Args:
103
100
  fastapi_apps: Discovered FastAPI applications
104
101
  mesh_routes: @mesh.route decorated functions from DecoratorRegistry
105
-
102
+
106
103
  Returns:
107
104
  Dict mapping app_id -> {route_name -> route_info} for routes that have @mesh.route
108
105
  """
109
106
  route_mapping = {}
110
-
107
+
111
108
  for app_id, app_info in fastapi_apps.items():
112
109
  app_routes = {}
113
-
110
+
114
111
  for route_info in app_info["routes"]:
115
112
  endpoint_name = route_info["endpoint_name"]
116
-
113
+
117
114
  # Check if this route handler has @mesh.route decorator
118
115
  if endpoint_name in mesh_routes:
119
116
  mesh_route_data = mesh_routes[endpoint_name]
120
-
117
+
121
118
  # Combine FastAPI route info with @mesh.route metadata
122
119
  combined_info = {
123
120
  **route_info, # FastAPI route info
124
121
  "mesh_metadata": mesh_route_data.metadata, # @mesh.route metadata
125
- "dependencies": mesh_route_data.metadata.get("dependencies", []),
122
+ "dependencies": mesh_route_data.metadata.get(
123
+ "dependencies", []
124
+ ),
126
125
  "mesh_decorator": mesh_route_data, # Full DecoratedFunction object
127
126
  }
128
-
127
+
129
128
  app_routes[endpoint_name] = combined_info
130
-
129
+
131
130
  self.logger.debug(
132
131
  f"Mapped route '{endpoint_name}' to app '{app_info['title']}' "
133
132
  f"with {len(combined_info['dependencies'])} dependencies"
134
133
  )
135
-
134
+
136
135
  if app_routes:
137
136
  route_mapping[app_id] = app_routes
138
-
139
- return route_mapping
137
+
138
+ return route_mapping
@@ -2,7 +2,7 @@
2
2
  Tracing Middleware Integration for FastAPI Applications.
3
3
 
4
4
  This module provides automatic injection of tracing middleware into discovered
5
- FastAPI applications, ensuring unified telemetry collection across both MCP
5
+ FastAPI applications, ensuring unified telemetry collection across both MCP
6
6
  agents and FastAPI apps without requiring user intervention.
7
7
  """
8
8
 
@@ -17,10 +17,10 @@ logger = logging.getLogger(__name__)
17
17
  class TracingMiddlewareIntegrationStep(PipelineStep):
18
18
  """
19
19
  Programmatically adds tracing middleware to discovered FastAPI applications.
20
-
21
- This ensures consistent telemetry collection across both MCP agents
20
+
21
+ This ensures consistent telemetry collection across both MCP agents
22
22
  (via HTTP wrapper middleware) and FastAPI apps (via injected middleware).
23
-
23
+
24
24
  The middleware handles:
25
25
  - Extracting trace headers (X-Trace-ID, X-Parent-Span) from requests
26
26
  - Setting up trace context for the request lifecycle
@@ -36,36 +36,40 @@ class TracingMiddlewareIntegrationStep(PipelineStep):
36
36
 
37
37
  async def execute(self, context: dict[str, Any]) -> PipelineResult:
38
38
  """Add tracing middleware to discovered FastAPI applications."""
39
- self.logger.debug("🔍 TRACING: Starting middleware integration for FastAPI apps...")
39
+ self.logger.debug(
40
+ "🔍 TRACING: Starting middleware integration for FastAPI apps..."
41
+ )
40
42
 
41
43
  result = PipelineResult(message="Tracing middleware integration completed")
42
44
 
43
45
  try:
44
46
  fastapi_apps = context.get("fastapi_apps", {})
45
-
47
+
46
48
  if not fastapi_apps:
47
49
  result.status = PipelineStatus.SKIPPED
48
- result.message = "No FastAPI applications found for middleware injection"
50
+ result.message = (
51
+ "No FastAPI applications found for middleware injection"
52
+ )
49
53
  self.logger.debug("🔍 TRACING: No FastAPI apps to add middleware to")
50
54
  return result
51
55
 
52
56
  # Add middleware to each discovered FastAPI app
53
57
  middleware_added = 0
54
58
  skipped_count = 0
55
-
59
+
56
60
  for app_id, app_info in fastapi_apps.items():
57
61
  fastapi_app = app_info["instance"]
58
62
  app_title = app_info.get("title", "Unknown App")
59
-
63
+
60
64
  self.logger.debug(
61
65
  f"🔍 TRACING: Checking app '{app_title}' ({app_id}) for middleware injection"
62
66
  )
63
-
67
+
64
68
  # Check if middleware already exists to avoid duplicates
65
69
  if not self._has_tracing_middleware(fastapi_app):
66
70
  self._add_tracing_middleware(fastapi_app, app_title)
67
71
  middleware_added += 1
68
-
72
+
69
73
  self.logger.info(
70
74
  f"🔍 TRACING: Added tracing middleware to FastAPI app '{app_title}'"
71
75
  )
@@ -99,29 +103,32 @@ class TracingMiddlewareIntegrationStep(PipelineStep):
99
103
  def _has_tracing_middleware(self, app) -> bool:
100
104
  """
101
105
  Check if FastAPI app already has MCP Mesh tracing middleware.
102
-
106
+
103
107
  Args:
104
108
  app: FastAPI application instance
105
-
109
+
106
110
  Returns:
107
111
  True if tracing middleware already exists, False otherwise
108
112
  """
109
113
  try:
110
114
  # Check user_middleware stack for our specific middleware classes
111
- if hasattr(app, 'user_middleware'):
115
+ if hasattr(app, "user_middleware"):
112
116
  for middleware in app.user_middleware:
113
- if hasattr(middleware, 'cls'):
117
+ if hasattr(middleware, "cls"):
114
118
  middleware_name = middleware.cls.__name__
115
119
  # Check for both old and new middleware names
116
- if middleware_name in ('MCPMeshTracingMiddleware', 'FastAPITracingMiddleware'):
120
+ if middleware_name in (
121
+ "MCPMeshTracingMiddleware",
122
+ "FastAPITracingMiddleware",
123
+ ):
117
124
  self.logger.debug(
118
125
  f"🔍 TRACING: Found existing {middleware_name} in app"
119
126
  )
120
127
  return True
121
-
128
+
122
129
  self.logger.debug("🔍 TRACING: No existing tracing middleware found")
123
130
  return False
124
-
131
+
125
132
  except Exception as e:
126
133
  # If we can't check middleware stack, assume it doesn't exist
127
134
  self.logger.debug(f"🔍 TRACING: Error checking middleware stack: {e}")
@@ -130,24 +137,25 @@ class TracingMiddlewareIntegrationStep(PipelineStep):
130
137
  def _add_tracing_middleware(self, app, app_title: str) -> None:
131
138
  """
132
139
  Add dedicated FastAPI tracing middleware to FastAPI app.
133
-
140
+
134
141
  Args:
135
142
  app: FastAPI application instance
136
143
  app_title: Human-readable app title for logging
137
144
  """
138
145
  try:
139
- from ....tracing.fastapi_tracing_middleware import FastAPITracingMiddleware
140
-
146
+ from ....tracing.fastapi_tracing_middleware import \
147
+ FastAPITracingMiddleware
148
+
141
149
  # Add the dedicated FastAPI tracing middleware
142
150
  app.add_middleware(FastAPITracingMiddleware, logger_instance=self.logger)
143
-
151
+
144
152
  self.logger.debug(
145
153
  f"🔍 TRACING: Successfully added FastAPITracingMiddleware to '{app_title}'"
146
154
  )
147
-
155
+
148
156
  except Exception as e:
149
157
  # Log error but don't fail the entire pipeline
150
158
  self.logger.error(
151
159
  f"🔍 TRACING: Failed to add middleware to '{app_title}': {e}"
152
160
  )
153
- raise # Re-raise so pipeline can handle the error appropriately
161
+ raise # Re-raise so pipeline can handle the error appropriately
@@ -37,9 +37,7 @@ class RouteCollectionStep(PipelineStep):
37
37
  # Update result message
38
38
  result.message = f"Collected {len(mesh_routes)} routes"
39
39
 
40
- self.logger.info(
41
- f"📦 Collected decorators: {len(mesh_routes)} @mesh.route"
42
- )
40
+ self.logger.info(f"📦 Collected decorators: {len(mesh_routes)} @mesh.route")
43
41
 
44
42
  # Validate we have routes to process
45
43
  if len(mesh_routes) == 0:
@@ -53,4 +51,4 @@ class RouteCollectionStep(PipelineStep):
53
51
  result.add_error(str(e))
54
52
  self.logger.error(f"❌ Route collection failed: {e}")
55
53
 
56
- return result
54
+ return result