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.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/dependency_injector.py +4 -6
- _mcp_mesh/engine/http_wrapper.py +69 -10
- _mcp_mesh/engine/mesh_llm_agent.py +4 -7
- _mcp_mesh/engine/mesh_llm_agent_injector.py +2 -1
- _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
- _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
- _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
- _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
- _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
- _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
- _mcp_mesh/engine/response_parser.py +61 -15
- _mcp_mesh/engine/unified_mcp_proxy.py +18 -34
- _mcp_mesh/pipeline/__init__.py +9 -20
- _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
- _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
- _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +425 -0
- _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
- _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
- _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
- _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
- _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
- _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +695 -0
- _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
- _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +5 -6
- _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +21 -9
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
- _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
- _mcp_mesh/reload.py +1 -3
- _mcp_mesh/shared/__init__.py +2 -8
- _mcp_mesh/shared/config_resolver.py +124 -80
- _mcp_mesh/shared/defaults.py +89 -14
- _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
- _mcp_mesh/shared/host_resolver.py +8 -46
- _mcp_mesh/shared/server_discovery.py +115 -86
- _mcp_mesh/shared/simple_shutdown.py +44 -86
- _mcp_mesh/tracing/execution_tracer.py +2 -6
- _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
- _mcp_mesh/tracing/trace_context_helper.py +3 -13
- _mcp_mesh/tracing/utils.py +29 -15
- _mcp_mesh/utils/fastmcp_schema_extractor.py +2 -1
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/METADATA +2 -1
- mcp_mesh-0.8.0b1.dist-info/RECORD +85 -0
- mesh/__init__.py +2 -1
- mesh/decorators.py +89 -5
- _mcp_mesh/generated/.openapi-generator/FILES +0 -50
- _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
- _mcp_mesh/generated/.openapi-generator-ignore +0 -15
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
- _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
- _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
- _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
- _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
- _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
- _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
- _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
- _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
- _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
- _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
- _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
- _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
- _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
- _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
- _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
- _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
- _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
- _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
- _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
- _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
- _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
- _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
- _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
- _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
- _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
- _mcp_mesh/shared/registry_client_wrapper.py +0 -515
- mcp_mesh-0.7.21.dist-info/RECORD +0 -152
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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-")
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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,
|
|
115
|
+
if hasattr(app, "user_middleware"):
|
|
112
116
|
for middleware in app.user_middleware:
|
|
113
|
-
if hasattr(middleware,
|
|
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 (
|
|
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
|
|
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
|