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,515 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Registry Client Wrapper - Clean interface for generated OpenAPI client.
|
|
3
|
-
|
|
4
|
-
Provides a type-safe, convenient wrapper around the generated OpenAPI client
|
|
5
|
-
that handles conversion between simple Python dicts and Pydantic models.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
from datetime import UTC, datetime
|
|
10
|
-
from typing import Any, Optional
|
|
11
|
-
|
|
12
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.api.agents_api import AgentsApi
|
|
13
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.api_client import ApiClient
|
|
14
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.models.mesh_agent_registration import (
|
|
15
|
-
MeshAgentRegistration,
|
|
16
|
-
)
|
|
17
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.models.mesh_tool_dependency_registration import (
|
|
18
|
-
MeshToolDependencyRegistration,
|
|
19
|
-
)
|
|
20
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.models.mesh_tool_registration import (
|
|
21
|
-
MeshToolRegistration,
|
|
22
|
-
)
|
|
23
|
-
from _mcp_mesh.shared.fast_heartbeat_status import (
|
|
24
|
-
FastHeartbeatStatus,
|
|
25
|
-
FastHeartbeatStatusUtil,
|
|
26
|
-
)
|
|
27
|
-
from _mcp_mesh.shared.support_types import HealthStatus
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class RegistryClientWrapper:
|
|
31
|
-
"""
|
|
32
|
-
Wrapper around the generated OpenAPI client for clean, type-safe registry operations.
|
|
33
|
-
|
|
34
|
-
Provides convenience methods that convert between simple Python dicts and
|
|
35
|
-
generated Pydantic models, while maintaining full type safety.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, api_client: ApiClient) -> None:
|
|
39
|
-
self.api_client = api_client
|
|
40
|
-
self.agents_api = AgentsApi(api_client)
|
|
41
|
-
self.logger = logging.getLogger(__name__)
|
|
42
|
-
|
|
43
|
-
async def send_heartbeat_with_dependency_resolution(
|
|
44
|
-
self, health_status: HealthStatus
|
|
45
|
-
) -> Optional[dict[str, Any]]:
|
|
46
|
-
"""
|
|
47
|
-
Send heartbeat and get dependency resolution updates.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
health_status: Current health status of the agent
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
Registry response with dependencies_resolved or None if failed
|
|
54
|
-
"""
|
|
55
|
-
try:
|
|
56
|
-
# Build heartbeat registration from health status
|
|
57
|
-
agent_registration = self._build_heartbeat_registration(health_status)
|
|
58
|
-
|
|
59
|
-
# Debug: Log full registration payload
|
|
60
|
-
import json
|
|
61
|
-
|
|
62
|
-
# Convert agent_registration to dict for logging
|
|
63
|
-
if hasattr(agent_registration, "model_dump"):
|
|
64
|
-
registration_dict = agent_registration.model_dump(
|
|
65
|
-
mode="json", exclude_none=True
|
|
66
|
-
)
|
|
67
|
-
else:
|
|
68
|
-
registration_dict = (
|
|
69
|
-
agent_registration.__dict__
|
|
70
|
-
if hasattr(agent_registration, "__dict__")
|
|
71
|
-
else str(agent_registration)
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
registration_json = json.dumps(registration_dict, indent=2, default=str)
|
|
75
|
-
self.logger.trace(
|
|
76
|
-
f"🔍 Full heartbeat registration payload:\n{registration_json}"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Call generated client
|
|
80
|
-
response = self.agents_api.send_heartbeat(agent_registration)
|
|
81
|
-
|
|
82
|
-
# Convert response to dict
|
|
83
|
-
response_dict = self._response_to_dict(response)
|
|
84
|
-
|
|
85
|
-
return response_dict
|
|
86
|
-
|
|
87
|
-
except Exception as e:
|
|
88
|
-
self.logger.error(
|
|
89
|
-
f"Failed to send heartbeat for {health_status.agent_name}: {e}"
|
|
90
|
-
)
|
|
91
|
-
return None
|
|
92
|
-
|
|
93
|
-
def parse_tool_dependencies(self, response: dict[str, Any]) -> dict[str, Any]:
|
|
94
|
-
"""
|
|
95
|
-
Parse dependency resolution from registry response with kwargs support.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
response: Registry response containing dependencies_resolved
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Dict mapping tool names to their resolved dependencies (including kwargs)
|
|
102
|
-
"""
|
|
103
|
-
try:
|
|
104
|
-
# Extract dependencies_resolved from response
|
|
105
|
-
dependencies_resolved = None
|
|
106
|
-
if "dependencies_resolved" in response:
|
|
107
|
-
dependencies_resolved = response["dependencies_resolved"]
|
|
108
|
-
elif (
|
|
109
|
-
"metadata" in response
|
|
110
|
-
and "dependencies_resolved" in response["metadata"]
|
|
111
|
-
):
|
|
112
|
-
dependencies_resolved = response["metadata"]["dependencies_resolved"]
|
|
113
|
-
else:
|
|
114
|
-
return {}
|
|
115
|
-
|
|
116
|
-
# Process each dependency to extract kwargs if present
|
|
117
|
-
parsed_dependencies = {}
|
|
118
|
-
|
|
119
|
-
for function_name, dependency_list in dependencies_resolved.items():
|
|
120
|
-
if not isinstance(dependency_list, list):
|
|
121
|
-
continue
|
|
122
|
-
|
|
123
|
-
parsed_dependencies[function_name] = []
|
|
124
|
-
|
|
125
|
-
for dep_resolution in dependency_list:
|
|
126
|
-
if not isinstance(dep_resolution, dict):
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
# Standard dependency fields
|
|
130
|
-
parsed_dep = {
|
|
131
|
-
"capability": dep_resolution.get("capability", ""),
|
|
132
|
-
"endpoint": dep_resolution.get("endpoint", ""),
|
|
133
|
-
"function_name": dep_resolution.get("function_name", ""),
|
|
134
|
-
"status": dep_resolution.get("status", ""),
|
|
135
|
-
"agent_id": dep_resolution.get("agent_id", ""),
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# NEW: Extract kwargs if present (from database JSON field)
|
|
139
|
-
if "kwargs" in dep_resolution:
|
|
140
|
-
try:
|
|
141
|
-
# kwargs might be JSON string from database
|
|
142
|
-
kwargs_data = dep_resolution["kwargs"]
|
|
143
|
-
if isinstance(kwargs_data, str):
|
|
144
|
-
import json
|
|
145
|
-
|
|
146
|
-
kwargs_data = (
|
|
147
|
-
json.loads(kwargs_data) if kwargs_data else {}
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
parsed_dep["kwargs"] = kwargs_data
|
|
151
|
-
self.logger.trace(
|
|
152
|
-
f"🔧 Parsed kwargs for {dep_resolution.get('capability')}: {kwargs_data}"
|
|
153
|
-
)
|
|
154
|
-
except (json.JSONDecodeError, TypeError) as e:
|
|
155
|
-
self.logger.warning(
|
|
156
|
-
f"Failed to parse kwargs for {dep_resolution.get('capability')}: {e}"
|
|
157
|
-
)
|
|
158
|
-
parsed_dep["kwargs"] = {}
|
|
159
|
-
else:
|
|
160
|
-
parsed_dep["kwargs"] = {}
|
|
161
|
-
|
|
162
|
-
parsed_dependencies[function_name].append(parsed_dep)
|
|
163
|
-
|
|
164
|
-
return parsed_dependencies
|
|
165
|
-
|
|
166
|
-
except Exception as e:
|
|
167
|
-
self.logger.error(f"Failed to parse tool dependencies: {e}")
|
|
168
|
-
return {}
|
|
169
|
-
|
|
170
|
-
async def check_fast_heartbeat(self, agent_id: str) -> FastHeartbeatStatus:
|
|
171
|
-
"""
|
|
172
|
-
Perform fast heartbeat check using HEAD request.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
agent_id: Unique agent identifier
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
FastHeartbeatStatus indicating required action
|
|
179
|
-
"""
|
|
180
|
-
try:
|
|
181
|
-
self.logger.trace(
|
|
182
|
-
f"🚀 Performing fast heartbeat check for agent '{agent_id}'"
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Call generated client fast heartbeat check with HTTP info to get status code
|
|
186
|
-
http_response = self.agents_api.fast_heartbeat_check_with_http_info(
|
|
187
|
-
agent_id
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Extract the actual HTTP status code from the response
|
|
191
|
-
status_code = http_response.status_code
|
|
192
|
-
self.logger.trace(
|
|
193
|
-
f"Fast heartbeat HEAD request for agent '{agent_id}' returned HTTP {status_code}"
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
# Convert HTTP status to semantic status
|
|
197
|
-
status = FastHeartbeatStatusUtil.from_http_code(status_code)
|
|
198
|
-
|
|
199
|
-
self.logger.trace(
|
|
200
|
-
f"✅ Fast heartbeat check completed for agent '{agent_id}': {status.value}"
|
|
201
|
-
)
|
|
202
|
-
return status
|
|
203
|
-
|
|
204
|
-
except ValueError as e:
|
|
205
|
-
# HTTP status code not supported
|
|
206
|
-
self.logger.warning(
|
|
207
|
-
f"Unsupported HTTP status in fast heartbeat for agent '{agent_id}': {e}"
|
|
208
|
-
)
|
|
209
|
-
return FastHeartbeatStatus.NETWORK_ERROR
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
# Check if this is an HTTP error with a specific status code
|
|
213
|
-
error_str = str(e)
|
|
214
|
-
|
|
215
|
-
# Handle 410 Gone specifically (agent unknown)
|
|
216
|
-
if "(410)" in error_str or "Gone" in error_str:
|
|
217
|
-
self.logger.trace(
|
|
218
|
-
f"🔍 Fast heartbeat: Agent '{agent_id}' unknown (410 Gone) - re-registration needed"
|
|
219
|
-
)
|
|
220
|
-
return FastHeartbeatStatus.AGENT_UNKNOWN
|
|
221
|
-
|
|
222
|
-
# Handle 503 Service Unavailable specifically (registry error)
|
|
223
|
-
if "(503)" in error_str or "Service Unavailable" in error_str:
|
|
224
|
-
self.logger.warning(
|
|
225
|
-
f"⚠️ Fast heartbeat: Registry error for agent '{agent_id}' (503) - skipping for resilience"
|
|
226
|
-
)
|
|
227
|
-
return FastHeartbeatStatus.REGISTRY_ERROR
|
|
228
|
-
|
|
229
|
-
# Handle 202 Accepted specifically (topology changed)
|
|
230
|
-
if "(202)" in error_str or "Accepted" in error_str:
|
|
231
|
-
self.logger.info(
|
|
232
|
-
f"🔄 Fast heartbeat: Topology changed for agent '{agent_id}' (202) - full refresh needed"
|
|
233
|
-
)
|
|
234
|
-
return FastHeartbeatStatus.TOPOLOGY_CHANGED
|
|
235
|
-
|
|
236
|
-
# All other errors treated as network errors
|
|
237
|
-
self.logger.warning(
|
|
238
|
-
f"Fast heartbeat check failed for agent '{agent_id}': {e}"
|
|
239
|
-
)
|
|
240
|
-
return FastHeartbeatStatusUtil.from_exception(e)
|
|
241
|
-
|
|
242
|
-
async def unregister_agent(self, agent_id: str) -> bool:
|
|
243
|
-
"""
|
|
244
|
-
Gracefully unregister agent from registry.
|
|
245
|
-
|
|
246
|
-
Args:
|
|
247
|
-
agent_id: Agent identifier to unregister
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
True if successful, False if failed
|
|
251
|
-
"""
|
|
252
|
-
try:
|
|
253
|
-
self.logger.info(f"🏁 Gracefully unregistering agent '{agent_id}'")
|
|
254
|
-
|
|
255
|
-
# Call generated client unregister method
|
|
256
|
-
response = self.agents_api.unregister_agent_with_http_info(agent_id)
|
|
257
|
-
|
|
258
|
-
success = response.status_code == 204
|
|
259
|
-
if success:
|
|
260
|
-
self.logger.info(f"✅ Agent '{agent_id}' unregistered successfully")
|
|
261
|
-
else:
|
|
262
|
-
self.logger.warning(
|
|
263
|
-
f"⚠️ Agent '{agent_id}' unregister returned unexpected status: {response.status_code}"
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
return success
|
|
267
|
-
|
|
268
|
-
except Exception as e:
|
|
269
|
-
self.logger.error(f"❌ Failed to unregister agent '{agent_id}': {e}")
|
|
270
|
-
return False
|
|
271
|
-
|
|
272
|
-
def _build_agent_registration(
|
|
273
|
-
self, agent_id: str, metadata: dict[str, Any]
|
|
274
|
-
) -> MeshAgentRegistration:
|
|
275
|
-
"""Build MeshAgentRegistration from agent metadata."""
|
|
276
|
-
|
|
277
|
-
# Build tools array
|
|
278
|
-
tools = []
|
|
279
|
-
for tool_data in metadata.get("tools", []):
|
|
280
|
-
# Convert dependencies
|
|
281
|
-
dep_registrations = []
|
|
282
|
-
for dep in tool_data.get("dependencies", []):
|
|
283
|
-
if isinstance(dep, dict):
|
|
284
|
-
dep_reg = MeshToolDependencyRegistration(
|
|
285
|
-
capability=dep["capability"],
|
|
286
|
-
tags=dep.get("tags", []),
|
|
287
|
-
version=dep.get("version", ""),
|
|
288
|
-
namespace=dep.get("namespace", "default"),
|
|
289
|
-
)
|
|
290
|
-
dep_registrations.append(dep_reg)
|
|
291
|
-
|
|
292
|
-
# Extract kwargs from tool_data (non-standard fields)
|
|
293
|
-
standard_fields = {
|
|
294
|
-
"capability",
|
|
295
|
-
"function_name",
|
|
296
|
-
"tags",
|
|
297
|
-
"version",
|
|
298
|
-
"dependencies",
|
|
299
|
-
"description",
|
|
300
|
-
}
|
|
301
|
-
kwargs_data = {
|
|
302
|
-
k: v for k, v in tool_data.items() if k not in standard_fields
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
# Create tool registration with kwargs support
|
|
306
|
-
tool_reg = MeshToolRegistration(
|
|
307
|
-
function_name=tool_data["function_name"],
|
|
308
|
-
capability=tool_data.get("capability"),
|
|
309
|
-
tags=tool_data.get("tags", []),
|
|
310
|
-
version=tool_data.get("version", "1.0.0"),
|
|
311
|
-
dependencies=dep_registrations,
|
|
312
|
-
description=tool_data.get("description"),
|
|
313
|
-
kwargs=kwargs_data if kwargs_data else None,
|
|
314
|
-
)
|
|
315
|
-
tools.append(tool_reg)
|
|
316
|
-
|
|
317
|
-
# Create agent registration
|
|
318
|
-
return MeshAgentRegistration(
|
|
319
|
-
agent_id=agent_id,
|
|
320
|
-
agent_type="mcp_agent",
|
|
321
|
-
name=metadata.get("name", agent_id),
|
|
322
|
-
version=metadata.get("version", "1.0.0"),
|
|
323
|
-
http_host=metadata.get("http_host", "0.0.0.0"),
|
|
324
|
-
http_port=metadata.get("http_port", 0),
|
|
325
|
-
timestamp=datetime.now(UTC),
|
|
326
|
-
namespace=metadata.get("namespace", "default"),
|
|
327
|
-
tools=tools,
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
def _build_heartbeat_registration(
|
|
331
|
-
self, health_status: HealthStatus
|
|
332
|
-
) -> MeshAgentRegistration:
|
|
333
|
-
"""Build MeshAgentRegistration from health status for heartbeat."""
|
|
334
|
-
|
|
335
|
-
# Import here to avoid circular imports
|
|
336
|
-
from _mcp_mesh.engine.decorator_registry import DecoratorRegistry
|
|
337
|
-
from _mcp_mesh.utils.fastmcp_schema_extractor import FastMCPSchemaExtractor
|
|
338
|
-
|
|
339
|
-
# Get current tools from registry
|
|
340
|
-
mesh_tools = DecoratorRegistry.get_mesh_tools()
|
|
341
|
-
|
|
342
|
-
# Build tools array with current metadata
|
|
343
|
-
tools = []
|
|
344
|
-
for func_name, decorated_func in mesh_tools.items():
|
|
345
|
-
metadata = decorated_func.metadata
|
|
346
|
-
|
|
347
|
-
# Convert dependencies
|
|
348
|
-
dep_registrations = []
|
|
349
|
-
for dep in metadata.get("dependencies", []):
|
|
350
|
-
if isinstance(dep, dict):
|
|
351
|
-
dep_reg = MeshToolDependencyRegistration(
|
|
352
|
-
capability=dep["capability"],
|
|
353
|
-
tags=dep.get("tags", []),
|
|
354
|
-
version=dep.get("version", ""),
|
|
355
|
-
namespace=dep.get("namespace", "default"),
|
|
356
|
-
)
|
|
357
|
-
dep_registrations.append(dep_reg)
|
|
358
|
-
elif isinstance(dep, str) and dep:
|
|
359
|
-
dep_reg = MeshToolDependencyRegistration(
|
|
360
|
-
capability=dep,
|
|
361
|
-
tags=[],
|
|
362
|
-
version="",
|
|
363
|
-
namespace="default",
|
|
364
|
-
)
|
|
365
|
-
dep_registrations.append(dep_reg)
|
|
366
|
-
|
|
367
|
-
# Extract kwargs from metadata (non-standard fields)
|
|
368
|
-
standard_fields = {
|
|
369
|
-
"capability",
|
|
370
|
-
"function_name",
|
|
371
|
-
"tags",
|
|
372
|
-
"version",
|
|
373
|
-
"dependencies",
|
|
374
|
-
"description",
|
|
375
|
-
}
|
|
376
|
-
kwargs_data = {
|
|
377
|
-
k: v for k, v in metadata.items() if k not in standard_fields
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
# Extract inputSchema from FastMCP tool (Phase 2: Schema Collection)
|
|
381
|
-
# First try to get FastMCP server info from DecoratorRegistry
|
|
382
|
-
fastmcp_servers = DecoratorRegistry.get_fastmcp_server_info()
|
|
383
|
-
input_schema = None
|
|
384
|
-
|
|
385
|
-
if fastmcp_servers:
|
|
386
|
-
# Try comprehensive extraction using server context
|
|
387
|
-
input_schema = FastMCPSchemaExtractor.extract_from_fastmcp_servers(
|
|
388
|
-
decorated_func.function, fastmcp_servers
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
# Fallback to direct attribute check if server lookup didn't work
|
|
392
|
-
if input_schema is None:
|
|
393
|
-
input_schema = FastMCPSchemaExtractor.extract_input_schema(
|
|
394
|
-
decorated_func.function
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
# Extract llm_filter from @mesh.llm decorator (Phase 3: LLM Integration)
|
|
398
|
-
llm_agents = DecoratorRegistry.get_mesh_llm_agents()
|
|
399
|
-
llm_filter_data = None
|
|
400
|
-
|
|
401
|
-
for llm_agent_id, llm_metadata in llm_agents.items():
|
|
402
|
-
# Match by function name (decorated_func.function is the wrapper, need to check original)
|
|
403
|
-
if llm_metadata.function.__name__ == func_name:
|
|
404
|
-
# Found matching LLM agent - extract filter config
|
|
405
|
-
raw_filter = llm_metadata.config.get("filter")
|
|
406
|
-
filter_mode = llm_metadata.config.get("filter_mode", "all")
|
|
407
|
-
|
|
408
|
-
# Normalize filter to array format
|
|
409
|
-
if raw_filter is None:
|
|
410
|
-
normalized_filter = []
|
|
411
|
-
elif isinstance(raw_filter, list):
|
|
412
|
-
normalized_filter = raw_filter
|
|
413
|
-
elif isinstance(raw_filter, dict):
|
|
414
|
-
# Single dict filter like {'capability': 'date_service'}
|
|
415
|
-
normalized_filter = [raw_filter]
|
|
416
|
-
elif isinstance(raw_filter, str):
|
|
417
|
-
normalized_filter = [raw_filter] if raw_filter else []
|
|
418
|
-
else:
|
|
419
|
-
normalized_filter = []
|
|
420
|
-
|
|
421
|
-
llm_filter_data = {
|
|
422
|
-
"filter": normalized_filter,
|
|
423
|
-
"filter_mode": filter_mode,
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
self.logger.trace(
|
|
427
|
-
f"🤖 Extracted llm_filter for {func_name}: {len(normalized_filter)} filters, mode={filter_mode}"
|
|
428
|
-
)
|
|
429
|
-
break
|
|
430
|
-
|
|
431
|
-
# Extract llm_provider from @mesh.llm decorator (v0.6.1: LLM Mesh Delegation)
|
|
432
|
-
llm_provider_data = None
|
|
433
|
-
|
|
434
|
-
for llm_agent_id, llm_metadata in llm_agents.items():
|
|
435
|
-
if llm_metadata.function.__name__ == func_name:
|
|
436
|
-
# Check if provider is a dict (mesh delegation mode)
|
|
437
|
-
provider = llm_metadata.config.get("provider")
|
|
438
|
-
if isinstance(provider, dict):
|
|
439
|
-
# Import generated client model
|
|
440
|
-
from _mcp_mesh.generated.mcp_mesh_registry_client.models.llm_provider import (
|
|
441
|
-
LLMProvider,
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
# Convert dict to LLMProvider model
|
|
445
|
-
llm_provider_data = LLMProvider(
|
|
446
|
-
capability=provider.get("capability", "llm"),
|
|
447
|
-
tags=provider.get("tags", []),
|
|
448
|
-
version=provider.get("version", ""),
|
|
449
|
-
namespace=provider.get("namespace", "default"),
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
self.logger.trace(
|
|
453
|
-
f"🔌 Extracted llm_provider for {func_name}: {llm_provider_data.model_dump()}"
|
|
454
|
-
)
|
|
455
|
-
break
|
|
456
|
-
|
|
457
|
-
# Create tool registration with llm_filter as separate top-level field (not in kwargs)
|
|
458
|
-
tool_reg = MeshToolRegistration(
|
|
459
|
-
function_name=func_name,
|
|
460
|
-
capability=metadata.get("capability"),
|
|
461
|
-
tags=metadata.get("tags", []),
|
|
462
|
-
version=metadata.get("version", "1.0.0"),
|
|
463
|
-
dependencies=dep_registrations,
|
|
464
|
-
description=metadata.get("description"),
|
|
465
|
-
llm_filter=llm_filter_data, # Pass llm_filter as top-level parameter
|
|
466
|
-
llm_provider=llm_provider_data, # Pass llm_provider as top-level parameter (v0.6.1)
|
|
467
|
-
input_schema=input_schema, # Pass inputSchema as top-level parameter (not in kwargs)
|
|
468
|
-
kwargs=kwargs_data if kwargs_data else None,
|
|
469
|
-
)
|
|
470
|
-
tools.append(tool_reg)
|
|
471
|
-
|
|
472
|
-
# Extract host/port from health status metadata
|
|
473
|
-
agent_metadata = health_status.metadata or {}
|
|
474
|
-
|
|
475
|
-
# Use external endpoint information for registry advertisement (not binding address)
|
|
476
|
-
external_host = agent_metadata.get("external_host")
|
|
477
|
-
external_port = agent_metadata.get("external_port")
|
|
478
|
-
external_endpoint = agent_metadata.get("external_endpoint")
|
|
479
|
-
|
|
480
|
-
# Parse external endpoint if provided
|
|
481
|
-
if external_endpoint:
|
|
482
|
-
from urllib.parse import urlparse
|
|
483
|
-
|
|
484
|
-
parsed = urlparse(external_endpoint)
|
|
485
|
-
http_host = parsed.hostname or external_host or "localhost"
|
|
486
|
-
http_port = (
|
|
487
|
-
parsed.port or external_port or agent_metadata.get("http_port", 8080)
|
|
488
|
-
)
|
|
489
|
-
else:
|
|
490
|
-
http_host = external_host or agent_metadata.get("http_host", "localhost")
|
|
491
|
-
http_port = external_port or agent_metadata.get("http_port", 8080)
|
|
492
|
-
|
|
493
|
-
# Fallback to localhost if we somehow get 0.0.0.0 (binding address)
|
|
494
|
-
if http_host == "0.0.0.0":
|
|
495
|
-
http_host = "localhost"
|
|
496
|
-
|
|
497
|
-
return MeshAgentRegistration(
|
|
498
|
-
agent_id=health_status.agent_name,
|
|
499
|
-
agent_type="mcp_agent",
|
|
500
|
-
name=health_status.agent_name,
|
|
501
|
-
version=health_status.version,
|
|
502
|
-
http_host=http_host,
|
|
503
|
-
http_port=http_port,
|
|
504
|
-
timestamp=health_status.timestamp,
|
|
505
|
-
namespace=agent_metadata.get("namespace", "default"),
|
|
506
|
-
tools=tools,
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
def _response_to_dict(self, response) -> dict[str, Any]:
|
|
510
|
-
"""Convert Pydantic response model to dict."""
|
|
511
|
-
if hasattr(response, "model_dump"):
|
|
512
|
-
return response.model_dump(mode="json", exclude_none=True)
|
|
513
|
-
else:
|
|
514
|
-
# Fallback for non-Pydantic responses
|
|
515
|
-
return {"status": "success", "dependencies_resolved": {}}
|