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
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
API heartbeat send step for API heartbeat pipeline.
|
|
3
|
-
|
|
4
|
-
Sends service health status and registration data to the registry
|
|
5
|
-
for FastAPI applications using @mesh.route decorators.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
from ...engine.decorator_registry import DecoratorRegistry
|
|
12
|
-
from ..shared.base_step import PipelineStep
|
|
13
|
-
from ..shared.pipeline_types import PipelineResult
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class APIHeartbeatSendStep(PipelineStep):
|
|
19
|
-
"""
|
|
20
|
-
Send API service heartbeat to registry.
|
|
21
|
-
|
|
22
|
-
Communicates service health status and registration information
|
|
23
|
-
to the registry for monitoring and discovery purposes.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, required: bool = True):
|
|
27
|
-
super().__init__(
|
|
28
|
-
name="api-heartbeat-send",
|
|
29
|
-
required=required,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
async def execute(self, context: dict[str, Any]) -> PipelineResult:
|
|
33
|
-
"""
|
|
34
|
-
Send API service heartbeat to registry.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
context: Pipeline context containing registry_wrapper, health_status, service_id
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
PipelineResult with heartbeat_response in context
|
|
41
|
-
"""
|
|
42
|
-
self.logger.debug("Sending API service heartbeat to registry")
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Get required components from context
|
|
46
|
-
registry_wrapper = context.get("registry_wrapper")
|
|
47
|
-
health_status = context.get("health_status")
|
|
48
|
-
service_id = context.get("service_id") or context.get("agent_id", "unknown")
|
|
49
|
-
|
|
50
|
-
if not registry_wrapper:
|
|
51
|
-
error_msg = "No registry wrapper available for heartbeat"
|
|
52
|
-
self.logger.error(f"❌ {error_msg}")
|
|
53
|
-
|
|
54
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
55
|
-
|
|
56
|
-
result = PipelineResult(
|
|
57
|
-
status=PipelineStatus.FAILED, message=error_msg, context=context
|
|
58
|
-
)
|
|
59
|
-
result.add_error(error_msg)
|
|
60
|
-
return result
|
|
61
|
-
|
|
62
|
-
if not health_status:
|
|
63
|
-
error_msg = "No health status available for heartbeat"
|
|
64
|
-
self.logger.error(f"❌ {error_msg}")
|
|
65
|
-
|
|
66
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
67
|
-
|
|
68
|
-
result = PipelineResult(
|
|
69
|
-
status=PipelineStatus.FAILED, message=error_msg, context=context
|
|
70
|
-
)
|
|
71
|
-
result.add_error(error_msg)
|
|
72
|
-
return result
|
|
73
|
-
|
|
74
|
-
# Prepare heartbeat data for API service
|
|
75
|
-
heartbeat_data = self._prepare_api_heartbeat_data(
|
|
76
|
-
health_status, service_id, context
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
self.logger.debug(f"📡 Sending heartbeat for API service '{service_id}'")
|
|
80
|
-
|
|
81
|
-
# Send heartbeat to registry using the same format as test_api_service.json
|
|
82
|
-
# Import json at the beginning
|
|
83
|
-
import json
|
|
84
|
-
|
|
85
|
-
import aiohttp
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
# For API services, send directly to registry using the format that works
|
|
89
|
-
# Get registry URL
|
|
90
|
-
registry_url = context.get("registry_url", "http://localhost:8000")
|
|
91
|
-
|
|
92
|
-
# Build the API service payload using actual dependencies from @mesh.route decorators
|
|
93
|
-
display_config = context.get("display_config", {})
|
|
94
|
-
|
|
95
|
-
# Build per-route tool entries using METHOD:path as unique identifiers
|
|
96
|
-
# This allows dependency resolution to map back to specific route wrappers
|
|
97
|
-
route_wrappers = DecoratorRegistry.get_all_route_wrappers()
|
|
98
|
-
tools_list = []
|
|
99
|
-
|
|
100
|
-
for route_id, route_info in route_wrappers.items():
|
|
101
|
-
dependencies = route_info.get("dependencies", [])
|
|
102
|
-
if dependencies: # Only include routes with dependencies
|
|
103
|
-
tools_list.append(
|
|
104
|
-
{
|
|
105
|
-
"function_name": route_id, # e.g., "GET:/api/v1/benchmark-services"
|
|
106
|
-
"dependencies": [
|
|
107
|
-
{"capability": dep, "tags": []}
|
|
108
|
-
for dep in dependencies
|
|
109
|
-
],
|
|
110
|
-
}
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# Fallback to old behavior if no route wrappers registered yet
|
|
114
|
-
if not tools_list:
|
|
115
|
-
all_route_dependencies = self._extract_all_route_dependencies(
|
|
116
|
-
context
|
|
117
|
-
)
|
|
118
|
-
if all_route_dependencies:
|
|
119
|
-
tools_list.append(
|
|
120
|
-
{
|
|
121
|
-
"function_name": "api_endpoint_handler",
|
|
122
|
-
"dependencies": all_route_dependencies,
|
|
123
|
-
}
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
self.logger.debug(
|
|
127
|
-
f"Route wrappers registered: {list(route_wrappers.keys())}"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
api_service_payload = {
|
|
131
|
-
"agent_id": service_id,
|
|
132
|
-
"agent_type": "api",
|
|
133
|
-
"tools": tools_list,
|
|
134
|
-
"http_host": display_config.get("display_host", "127.0.0.1"),
|
|
135
|
-
"http_port": display_config.get("display_port", 8080),
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
self.logger.debug(
|
|
139
|
-
f"Sending API service payload to {registry_url}/heartbeat"
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
try:
|
|
143
|
-
async with aiohttp.ClientSession() as session:
|
|
144
|
-
async with session.post(
|
|
145
|
-
f"{registry_url}/heartbeat",
|
|
146
|
-
headers={"Content-Type": "application/json"},
|
|
147
|
-
data=json.dumps(api_service_payload),
|
|
148
|
-
) as response:
|
|
149
|
-
self.logger.debug(
|
|
150
|
-
f'{registry_url} "POST /heartbeat HTTP/1.1" {response.status}'
|
|
151
|
-
)
|
|
152
|
-
if response.status == 200:
|
|
153
|
-
heartbeat_response = await response.json()
|
|
154
|
-
else:
|
|
155
|
-
response_text = await response.text()
|
|
156
|
-
self.logger.error(
|
|
157
|
-
f"❌ Registry error {response.status}: {response_text}"
|
|
158
|
-
)
|
|
159
|
-
raise Exception(
|
|
160
|
-
f"Registry returned {response.status}: {response_text}"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
except Exception as http_error:
|
|
164
|
-
self.logger.error(f"❌ HTTP request failed: {http_error}")
|
|
165
|
-
raise http_error
|
|
166
|
-
|
|
167
|
-
if heartbeat_response:
|
|
168
|
-
self.logger.info(
|
|
169
|
-
f"💚 API heartbeat successful for service '{service_id}'"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
return PipelineResult(
|
|
173
|
-
message=f"API heartbeat sent for service {service_id}",
|
|
174
|
-
context={
|
|
175
|
-
"heartbeat_response": heartbeat_response,
|
|
176
|
-
"heartbeat_success": True,
|
|
177
|
-
"heartbeat_data": heartbeat_data,
|
|
178
|
-
},
|
|
179
|
-
)
|
|
180
|
-
else:
|
|
181
|
-
error_msg = f"Registry heartbeat failed for service {service_id}"
|
|
182
|
-
self.logger.warning(f"⚠️ {error_msg}")
|
|
183
|
-
|
|
184
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
185
|
-
|
|
186
|
-
result = PipelineResult(
|
|
187
|
-
status=PipelineStatus.FAILED, message=error_msg, context=context
|
|
188
|
-
)
|
|
189
|
-
result.add_error(error_msg)
|
|
190
|
-
return result
|
|
191
|
-
|
|
192
|
-
except Exception as e:
|
|
193
|
-
error_msg = f"Registry communication failed: {e}"
|
|
194
|
-
self.logger.error(f"❌ {error_msg}")
|
|
195
|
-
|
|
196
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
197
|
-
|
|
198
|
-
result = PipelineResult(
|
|
199
|
-
status=PipelineStatus.FAILED, message=error_msg, context=context
|
|
200
|
-
)
|
|
201
|
-
result.add_error(str(e))
|
|
202
|
-
return result
|
|
203
|
-
|
|
204
|
-
except Exception as e:
|
|
205
|
-
error_msg = f"API heartbeat send failed: {e}"
|
|
206
|
-
self.logger.error(f"❌ {error_msg}")
|
|
207
|
-
|
|
208
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
209
|
-
|
|
210
|
-
result = PipelineResult(
|
|
211
|
-
status=PipelineStatus.FAILED, message=error_msg, context=context
|
|
212
|
-
)
|
|
213
|
-
result.add_error(str(e))
|
|
214
|
-
return result
|
|
215
|
-
|
|
216
|
-
def _prepare_api_heartbeat_data(
|
|
217
|
-
self, health_status: Any, service_id: str, context: dict[str, Any]
|
|
218
|
-
) -> dict[str, Any]:
|
|
219
|
-
"""Prepare heartbeat data specific to API services."""
|
|
220
|
-
try:
|
|
221
|
-
# Extract FastAPI-specific information
|
|
222
|
-
app_title = context.get("app_title", "Unknown API")
|
|
223
|
-
app_version = context.get("app_version", "1.0.0")
|
|
224
|
-
routes_total = context.get("routes_total", 0)
|
|
225
|
-
routes_with_mesh = context.get("routes_with_mesh", 0)
|
|
226
|
-
|
|
227
|
-
# Get display configuration
|
|
228
|
-
display_config = context.get("display_config", {})
|
|
229
|
-
host = display_config.get("host", "0.0.0.0")
|
|
230
|
-
port = display_config.get("port", 8080)
|
|
231
|
-
|
|
232
|
-
heartbeat_data = {
|
|
233
|
-
"service_id": service_id,
|
|
234
|
-
"service_type": "api",
|
|
235
|
-
"app_title": app_title,
|
|
236
|
-
"app_version": app_version,
|
|
237
|
-
"host": host,
|
|
238
|
-
"port": port,
|
|
239
|
-
"routes": {
|
|
240
|
-
"total": routes_total,
|
|
241
|
-
"with_mesh": routes_with_mesh,
|
|
242
|
-
},
|
|
243
|
-
"health_status": (
|
|
244
|
-
health_status
|
|
245
|
-
if isinstance(health_status, dict)
|
|
246
|
-
else {
|
|
247
|
-
"status": (
|
|
248
|
-
health_status.status.value
|
|
249
|
-
if hasattr(health_status, "status")
|
|
250
|
-
and hasattr(health_status.status, "value")
|
|
251
|
-
else str(getattr(health_status, "status", "healthy"))
|
|
252
|
-
),
|
|
253
|
-
"timestamp": (
|
|
254
|
-
health_status.timestamp.isoformat()
|
|
255
|
-
if hasattr(health_status, "timestamp")
|
|
256
|
-
and hasattr(health_status.timestamp, "isoformat")
|
|
257
|
-
else str(getattr(health_status, "timestamp", ""))
|
|
258
|
-
),
|
|
259
|
-
"version": getattr(health_status, "version", "1.0.0"),
|
|
260
|
-
"metadata": getattr(health_status, "metadata", {}),
|
|
261
|
-
}
|
|
262
|
-
),
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return heartbeat_data
|
|
266
|
-
|
|
267
|
-
except Exception as e:
|
|
268
|
-
self.logger.warning(f"⚠️ Could not prepare heartbeat data: {e}")
|
|
269
|
-
return {
|
|
270
|
-
"service_id": service_id,
|
|
271
|
-
"service_type": "api",
|
|
272
|
-
"error": f"Failed to prepare heartbeat data: {e}",
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
def _extract_all_route_dependencies(
|
|
276
|
-
self, context: dict[str, Any]
|
|
277
|
-
) -> list[dict[str, Any]]:
|
|
278
|
-
"""
|
|
279
|
-
Extract all unique dependencies from @mesh.route decorators in the FastAPI app.
|
|
280
|
-
|
|
281
|
-
This method looks at the actual route dependencies that were discovered during
|
|
282
|
-
the API startup pipeline and extracts them for registry registration.
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
context: Pipeline context containing FastAPI app and route information
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
List of unique dependency objects in the format expected by registry
|
|
289
|
-
"""
|
|
290
|
-
try:
|
|
291
|
-
# Try to get dependencies from startup context (preferred method)
|
|
292
|
-
api_service_metadata = context.get("api_service_metadata", {})
|
|
293
|
-
route_capabilities = api_service_metadata.get("capabilities", [])
|
|
294
|
-
|
|
295
|
-
self.logger.debug(
|
|
296
|
-
f"api_service_metadata keys: {list(api_service_metadata.keys())}"
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
# Extract dependencies from route capabilities
|
|
300
|
-
all_dependencies = []
|
|
301
|
-
seen_capabilities = set()
|
|
302
|
-
|
|
303
|
-
for route_capability in route_capabilities:
|
|
304
|
-
route_deps = route_capability.get("dependencies", [])
|
|
305
|
-
for dep in route_deps:
|
|
306
|
-
# dep should already be a string (capability name)
|
|
307
|
-
if isinstance(dep, str) and dep not in seen_capabilities:
|
|
308
|
-
seen_capabilities.add(dep)
|
|
309
|
-
# Convert to object format for registry
|
|
310
|
-
all_dependencies.append(
|
|
311
|
-
{
|
|
312
|
-
"capability": dep,
|
|
313
|
-
"tags": [], # No tags info available at this level
|
|
314
|
-
}
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
# If we found dependencies from startup context, use them
|
|
318
|
-
if all_dependencies:
|
|
319
|
-
self.logger.debug(
|
|
320
|
-
f"Extracted {len(all_dependencies)} unique dependencies from API startup: "
|
|
321
|
-
f"{[dep['capability'] for dep in all_dependencies]}"
|
|
322
|
-
)
|
|
323
|
-
return all_dependencies
|
|
324
|
-
|
|
325
|
-
# Fallback: try to extract directly from FastAPI app routes
|
|
326
|
-
fastapi_app = context.get("fastapi_app")
|
|
327
|
-
if fastapi_app:
|
|
328
|
-
return self._extract_dependencies_from_routes(fastapi_app)
|
|
329
|
-
|
|
330
|
-
# Final fallback: empty dependencies
|
|
331
|
-
self.logger.warning(
|
|
332
|
-
"⚠️ No route dependencies found in context or FastAPI app"
|
|
333
|
-
)
|
|
334
|
-
return []
|
|
335
|
-
|
|
336
|
-
except Exception as e:
|
|
337
|
-
self.logger.error(f"❌ Failed to extract route dependencies: {e}")
|
|
338
|
-
return []
|
|
339
|
-
|
|
340
|
-
def _extract_dependencies_from_routes(
|
|
341
|
-
self, fastapi_app: Any
|
|
342
|
-
) -> list[dict[str, Any]]:
|
|
343
|
-
"""
|
|
344
|
-
Fallback method to extract dependencies directly from FastAPI route metadata.
|
|
345
|
-
|
|
346
|
-
Args:
|
|
347
|
-
fastapi_app: FastAPI application instance
|
|
348
|
-
|
|
349
|
-
Returns:
|
|
350
|
-
List of unique dependency objects
|
|
351
|
-
"""
|
|
352
|
-
try:
|
|
353
|
-
all_dependencies = []
|
|
354
|
-
seen_capabilities = set()
|
|
355
|
-
|
|
356
|
-
routes = getattr(fastapi_app, "routes", [])
|
|
357
|
-
for route in routes:
|
|
358
|
-
endpoint = getattr(route, "endpoint", None)
|
|
359
|
-
if endpoint and hasattr(endpoint, "_mesh_route_metadata"):
|
|
360
|
-
metadata = endpoint._mesh_route_metadata
|
|
361
|
-
route_deps = metadata.get("dependencies", [])
|
|
362
|
-
|
|
363
|
-
for dep in route_deps:
|
|
364
|
-
if isinstance(dep, dict):
|
|
365
|
-
capability = dep.get("capability")
|
|
366
|
-
if capability and capability not in seen_capabilities:
|
|
367
|
-
seen_capabilities.add(capability)
|
|
368
|
-
all_dependencies.append(
|
|
369
|
-
{
|
|
370
|
-
"capability": capability,
|
|
371
|
-
"tags": dep.get("tags", []),
|
|
372
|
-
}
|
|
373
|
-
)
|
|
374
|
-
elif isinstance(dep, str) and dep not in seen_capabilities:
|
|
375
|
-
seen_capabilities.add(dep)
|
|
376
|
-
all_dependencies.append({"capability": dep, "tags": []})
|
|
377
|
-
|
|
378
|
-
self.logger.debug(
|
|
379
|
-
f"Extracted {len(all_dependencies)} unique dependencies from FastAPI routes: "
|
|
380
|
-
f"{[dep['capability'] for dep in all_dependencies]}"
|
|
381
|
-
)
|
|
382
|
-
return all_dependencies
|
|
383
|
-
|
|
384
|
-
except Exception as e:
|
|
385
|
-
self.logger.error(f"❌ Failed to extract dependencies from routes: {e}")
|
|
386
|
-
return []
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
API registry connection step for API heartbeat pipeline.
|
|
3
|
-
|
|
4
|
-
Prepares registry communication for FastAPI service heartbeat operations.
|
|
5
|
-
Simpler than MCP registry connection since API services don't require
|
|
6
|
-
complex dependency resolution.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from ..shared.base_step import PipelineStep
|
|
13
|
-
from ..shared.pipeline_types import PipelineResult
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class APIRegistryConnectionStep(PipelineStep):
|
|
19
|
-
"""
|
|
20
|
-
Prepare registry connection for API service heartbeat.
|
|
21
|
-
|
|
22
|
-
Ensures registry client is available and properly configured for
|
|
23
|
-
FastAPI service registration and health monitoring.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, required: bool = True):
|
|
27
|
-
super().__init__(
|
|
28
|
-
name="api-registry-connection",
|
|
29
|
-
required=required,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
async def execute(self, context: dict[str, Any]) -> PipelineResult:
|
|
33
|
-
"""
|
|
34
|
-
Prepare registry connection for API heartbeat operations.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
context: Pipeline context containing agent_config and registration_data
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
PipelineResult with registry_wrapper in context
|
|
41
|
-
"""
|
|
42
|
-
self.logger.trace("🔗 Preparing API registry connection for heartbeat")
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Check if registry_wrapper already exists in context
|
|
46
|
-
registry_wrapper = context.get("registry_wrapper")
|
|
47
|
-
|
|
48
|
-
if registry_wrapper is not None:
|
|
49
|
-
self.logger.trace("✅ Registry wrapper already available in context")
|
|
50
|
-
return PipelineResult(
|
|
51
|
-
message="Registry connection already established",
|
|
52
|
-
context={"registry_wrapper": registry_wrapper}
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
# Get registry configuration from context and environment
|
|
56
|
-
agent_config = context.get("agent_config", {})
|
|
57
|
-
registration_data = context.get("registration_data", {})
|
|
58
|
-
|
|
59
|
-
# Use proper config resolver to respect environment variables first
|
|
60
|
-
from ...shared.config_resolver import ValidationRule, get_config_value
|
|
61
|
-
|
|
62
|
-
registry_url = get_config_value(
|
|
63
|
-
"MCP_MESH_REGISTRY_URL",
|
|
64
|
-
override=(
|
|
65
|
-
agent_config.get("registry_url")
|
|
66
|
-
or registration_data.get("registry_url")
|
|
67
|
-
),
|
|
68
|
-
default="http://localhost:8000",
|
|
69
|
-
rule=ValidationRule.URL_RULE,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
self.logger.trace(f"🔍 Using registry URL: {registry_url}")
|
|
73
|
-
|
|
74
|
-
# Create registry client wrapper
|
|
75
|
-
from ...generated.mcp_mesh_registry_client.api_client import ApiClient
|
|
76
|
-
from ...generated.mcp_mesh_registry_client.configuration import Configuration
|
|
77
|
-
from ...shared.registry_client_wrapper import RegistryClientWrapper
|
|
78
|
-
|
|
79
|
-
config = Configuration(host=registry_url)
|
|
80
|
-
api_client = ApiClient(configuration=config)
|
|
81
|
-
registry_wrapper = RegistryClientWrapper(api_client)
|
|
82
|
-
|
|
83
|
-
self.logger.trace(f"🔗 API registry connection prepared: {registry_url}")
|
|
84
|
-
|
|
85
|
-
return PipelineResult(
|
|
86
|
-
message=f"Registry connection prepared for {registry_url}",
|
|
87
|
-
context={
|
|
88
|
-
"registry_wrapper": registry_wrapper,
|
|
89
|
-
"registry_url": registry_url,
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
except Exception as e:
|
|
94
|
-
error_msg = f"Failed to prepare API registry connection: {e}"
|
|
95
|
-
self.logger.error(f"❌ {error_msg}")
|
|
96
|
-
|
|
97
|
-
from ..shared.pipeline_types import PipelineStatus
|
|
98
|
-
result = PipelineResult(
|
|
99
|
-
status=PipelineStatus.FAILED,
|
|
100
|
-
message=error_msg,
|
|
101
|
-
context=context
|
|
102
|
-
)
|
|
103
|
-
result.add_error(str(e))
|
|
104
|
-
return result
|