mcp-mesh 0.7.21__py3-none-any.whl → 0.8.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. _mcp_mesh/__init__.py +1 -1
  2. _mcp_mesh/engine/dependency_injector.py +4 -6
  3. _mcp_mesh/engine/http_wrapper.py +69 -10
  4. _mcp_mesh/engine/mesh_llm_agent.py +4 -7
  5. _mcp_mesh/engine/mesh_llm_agent_injector.py +2 -1
  6. _mcp_mesh/engine/provider_handlers/__init__.py +14 -1
  7. _mcp_mesh/engine/provider_handlers/base_provider_handler.py +114 -8
  8. _mcp_mesh/engine/provider_handlers/claude_handler.py +15 -57
  9. _mcp_mesh/engine/provider_handlers/gemini_handler.py +181 -0
  10. _mcp_mesh/engine/provider_handlers/openai_handler.py +8 -63
  11. _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +16 -10
  12. _mcp_mesh/engine/response_parser.py +61 -15
  13. _mcp_mesh/engine/unified_mcp_proxy.py +18 -34
  14. _mcp_mesh/pipeline/__init__.py +9 -20
  15. _mcp_mesh/pipeline/api_heartbeat/__init__.py +12 -7
  16. _mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +23 -49
  17. _mcp_mesh/pipeline/api_heartbeat/rust_api_heartbeat.py +425 -0
  18. _mcp_mesh/pipeline/api_startup/api_pipeline.py +7 -9
  19. _mcp_mesh/pipeline/api_startup/api_server_setup.py +91 -70
  20. _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +22 -23
  21. _mcp_mesh/pipeline/api_startup/middleware_integration.py +32 -24
  22. _mcp_mesh/pipeline/api_startup/route_collection.py +2 -4
  23. _mcp_mesh/pipeline/mcp_heartbeat/__init__.py +5 -17
  24. _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +695 -0
  25. _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -5
  26. _mcp_mesh/pipeline/mcp_startup/configuration.py +1 -1
  27. _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +5 -6
  28. _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
  29. _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +21 -9
  30. _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +3 -8
  31. _mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -2
  32. _mcp_mesh/reload.py +1 -3
  33. _mcp_mesh/shared/__init__.py +2 -8
  34. _mcp_mesh/shared/config_resolver.py +124 -80
  35. _mcp_mesh/shared/defaults.py +89 -14
  36. _mcp_mesh/shared/fastapi_middleware_manager.py +149 -91
  37. _mcp_mesh/shared/host_resolver.py +8 -46
  38. _mcp_mesh/shared/server_discovery.py +115 -86
  39. _mcp_mesh/shared/simple_shutdown.py +44 -86
  40. _mcp_mesh/tracing/execution_tracer.py +2 -6
  41. _mcp_mesh/tracing/redis_metadata_publisher.py +24 -79
  42. _mcp_mesh/tracing/trace_context_helper.py +3 -13
  43. _mcp_mesh/tracing/utils.py +29 -15
  44. _mcp_mesh/utils/fastmcp_schema_extractor.py +2 -1
  45. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/METADATA +2 -1
  46. mcp_mesh-0.8.0b1.dist-info/RECORD +85 -0
  47. mesh/__init__.py +2 -1
  48. mesh/decorators.py +89 -5
  49. _mcp_mesh/generated/.openapi-generator/FILES +0 -50
  50. _mcp_mesh/generated/.openapi-generator/VERSION +0 -1
  51. _mcp_mesh/generated/.openapi-generator-ignore +0 -15
  52. _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -90
  53. _mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -6
  54. _mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -1088
  55. _mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -764
  56. _mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -303
  57. _mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -798
  58. _mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -21
  59. _mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -577
  60. _mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -217
  61. _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -55
  62. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -158
  63. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -126
  64. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -139
  65. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -92
  66. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -103
  67. _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -136
  68. _mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -100
  69. _mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -107
  70. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -112
  71. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -103
  72. _mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -105
  73. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -103
  74. _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -106
  75. _mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -91
  76. _mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -103
  77. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -101
  78. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -111
  79. _mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -117
  80. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -93
  81. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider_resolution_info.py +0 -106
  82. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -109
  83. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -139
  84. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -91
  85. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -101
  86. _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_resolution_info.py +0 -120
  87. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -112
  88. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -129
  89. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -153
  90. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -101
  91. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -93
  92. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -107
  93. _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -117
  94. _mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -119
  95. _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -110
  96. _mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -93
  97. _mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -92
  98. _mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -93
  99. _mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -106
  100. _mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  101. _mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -259
  102. _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -418
  103. _mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -117
  104. _mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -140
  105. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -243
  106. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -311
  107. _mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -386
  108. _mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -104
  109. _mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -396
  110. _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -116
  111. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -311
  112. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -282
  113. _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -98
  114. _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -84
  115. _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -264
  116. _mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -79
  117. _mcp_mesh/pipeline/shared/registry_connection.py +0 -80
  118. _mcp_mesh/shared/registry_client_wrapper.py +0 -515
  119. mcp_mesh-0.7.21.dist-info/RECORD +0 -152
  120. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/WHEEL +0 -0
  121. {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -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": {}}