mcp-mesh 0.5.7__py3-none-any.whl → 0.6.1__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/base_injector.py +171 -0
- _mcp_mesh/engine/decorator_registry.py +162 -35
- _mcp_mesh/engine/dependency_injector.py +105 -19
- _mcp_mesh/engine/http_wrapper.py +5 -22
- _mcp_mesh/engine/llm_config.py +45 -0
- _mcp_mesh/engine/llm_errors.py +115 -0
- _mcp_mesh/engine/mesh_llm_agent.py +626 -0
- _mcp_mesh/engine/mesh_llm_agent_injector.py +617 -0
- _mcp_mesh/engine/provider_handlers/__init__.py +20 -0
- _mcp_mesh/engine/provider_handlers/base_provider_handler.py +122 -0
- _mcp_mesh/engine/provider_handlers/claude_handler.py +138 -0
- _mcp_mesh/engine/provider_handlers/generic_handler.py +156 -0
- _mcp_mesh/engine/provider_handlers/openai_handler.py +163 -0
- _mcp_mesh/engine/provider_handlers/provider_handler_registry.py +167 -0
- _mcp_mesh/engine/response_parser.py +205 -0
- _mcp_mesh/engine/signature_analyzer.py +229 -99
- _mcp_mesh/engine/tool_executor.py +169 -0
- _mcp_mesh/engine/tool_schema_builder.py +126 -0
- _mcp_mesh/engine/unified_mcp_proxy.py +14 -12
- _mcp_mesh/generated/.openapi-generator/FILES +7 -0
- _mcp_mesh/generated/.openapi-generator-ignore +0 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +7 -16
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +7 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +11 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +108 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +95 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +111 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +141 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +93 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +103 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +1 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +35 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +11 -1
- _mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +112 -0
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +9 -72
- _mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +3 -3
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +35 -10
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +7 -4
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +260 -0
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +118 -35
- _mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +8 -1
- _mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +111 -5
- _mcp_mesh/pipeline/mcp_startup/server_discovery.py +77 -48
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +2 -2
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +2 -2
- _mcp_mesh/shared/health_check_cache.py +246 -0
- _mcp_mesh/shared/registry_client_wrapper.py +87 -4
- _mcp_mesh/utils/fastmcp_schema_extractor.py +476 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.1.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.1.dist-info}/RECORD +57 -32
- mesh/__init__.py +18 -4
- mesh/decorators.py +439 -31
- mesh/helpers.py +259 -0
- mesh/types.py +197 -97
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.1.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.1.dist-info}/licenses/LICENSE +0 -0
_mcp_mesh/__init__.py
CHANGED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base injector class with shared wrapper creation logic.
|
|
3
|
+
|
|
4
|
+
Provides common functionality for DependencyInjector and MeshLlmAgentInjector.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import inspect
|
|
9
|
+
import logging
|
|
10
|
+
import weakref
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseInjector:
|
|
18
|
+
"""
|
|
19
|
+
Base class for injection systems.
|
|
20
|
+
|
|
21
|
+
Provides shared functionality for creating and managing function wrappers
|
|
22
|
+
that support dynamic injection with two-phase updates.
|
|
23
|
+
|
|
24
|
+
Two-Phase Injection Pattern:
|
|
25
|
+
1. Phase 1 (decorator time): Create wrapper with initial state (None)
|
|
26
|
+
2. Phase 2 (runtime): Update wrapper with actual instances via update method
|
|
27
|
+
|
|
28
|
+
Subclasses must implement:
|
|
29
|
+
- Wrapper logic (what to inject and how)
|
|
30
|
+
- Update method signature
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
"""Initialize base injector with function registry."""
|
|
35
|
+
self._function_registry: weakref.WeakValueDictionary = (
|
|
36
|
+
weakref.WeakValueDictionary()
|
|
37
|
+
)
|
|
38
|
+
logger.debug(f"🔧 {self.__class__.__name__} initialized")
|
|
39
|
+
|
|
40
|
+
def _register_wrapper(self, function_id: str, wrapper: Callable) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Register a wrapper in the function registry.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
function_id: Unique function identifier
|
|
46
|
+
wrapper: Wrapper function to register
|
|
47
|
+
"""
|
|
48
|
+
self._function_registry[function_id] = wrapper
|
|
49
|
+
logger.debug(f"🔧 Registered wrapper for {function_id} at {hex(id(wrapper))}")
|
|
50
|
+
|
|
51
|
+
def _create_async_wrapper(
|
|
52
|
+
self,
|
|
53
|
+
func: Callable,
|
|
54
|
+
function_id: str,
|
|
55
|
+
injection_logic: Callable[[Callable, tuple, dict], tuple],
|
|
56
|
+
metadata: dict[str, Any],
|
|
57
|
+
) -> Callable:
|
|
58
|
+
"""
|
|
59
|
+
Create async wrapper with injection logic.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
func: Original async function to wrap
|
|
63
|
+
function_id: Unique function identifier
|
|
64
|
+
injection_logic: Callable that takes (func, args, kwargs) and returns (args, kwargs)
|
|
65
|
+
This function should modify kwargs to inject dependencies
|
|
66
|
+
metadata: Additional metadata to store on wrapper
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Async wrapper function
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@functools.wraps(func)
|
|
73
|
+
async def async_wrapper(*args, **kwargs):
|
|
74
|
+
# Apply injection logic to modify kwargs
|
|
75
|
+
args, kwargs = injection_logic(func, args, kwargs)
|
|
76
|
+
|
|
77
|
+
# Execute original function
|
|
78
|
+
return await func(*args, **kwargs)
|
|
79
|
+
|
|
80
|
+
# Store metadata on wrapper
|
|
81
|
+
async_wrapper._mesh_original_func = func
|
|
82
|
+
async_wrapper._mesh_function_id = function_id
|
|
83
|
+
|
|
84
|
+
# Store additional metadata
|
|
85
|
+
for key, value in metadata.items():
|
|
86
|
+
setattr(async_wrapper, key, value)
|
|
87
|
+
|
|
88
|
+
return async_wrapper
|
|
89
|
+
|
|
90
|
+
def _create_sync_wrapper(
|
|
91
|
+
self,
|
|
92
|
+
func: Callable,
|
|
93
|
+
function_id: str,
|
|
94
|
+
injection_logic: Callable[[Callable, tuple, dict], tuple],
|
|
95
|
+
metadata: dict[str, Any],
|
|
96
|
+
) -> Callable:
|
|
97
|
+
"""
|
|
98
|
+
Create sync wrapper with injection logic.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
func: Original sync function to wrap
|
|
102
|
+
function_id: Unique function identifier
|
|
103
|
+
injection_logic: Callable that takes (func, args, kwargs) and returns (args, kwargs)
|
|
104
|
+
This function should modify kwargs to inject dependencies
|
|
105
|
+
metadata: Additional metadata to store on wrapper
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Sync wrapper function
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
@functools.wraps(func)
|
|
112
|
+
def sync_wrapper(*args, **kwargs):
|
|
113
|
+
# Apply injection logic to modify kwargs
|
|
114
|
+
args, kwargs = injection_logic(func, args, kwargs)
|
|
115
|
+
|
|
116
|
+
# Execute original function
|
|
117
|
+
return func(*args, **kwargs)
|
|
118
|
+
|
|
119
|
+
# Store metadata on wrapper
|
|
120
|
+
sync_wrapper._mesh_original_func = func
|
|
121
|
+
sync_wrapper._mesh_function_id = function_id
|
|
122
|
+
|
|
123
|
+
# Store additional metadata
|
|
124
|
+
for key, value in metadata.items():
|
|
125
|
+
setattr(sync_wrapper, key, value)
|
|
126
|
+
|
|
127
|
+
return sync_wrapper
|
|
128
|
+
|
|
129
|
+
def create_wrapper_with_injection(
|
|
130
|
+
self,
|
|
131
|
+
func: Callable,
|
|
132
|
+
function_id: str,
|
|
133
|
+
injection_logic: Callable[[Callable, tuple, dict], tuple],
|
|
134
|
+
metadata: dict[str, Any],
|
|
135
|
+
register: bool = True,
|
|
136
|
+
) -> Callable:
|
|
137
|
+
"""
|
|
138
|
+
Create wrapper (async or sync) based on function type.
|
|
139
|
+
|
|
140
|
+
This is the main entry point for creating wrappers. It automatically
|
|
141
|
+
detects if the function is async or sync and creates the appropriate wrapper.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
func: Function to wrap
|
|
145
|
+
function_id: Unique function identifier
|
|
146
|
+
injection_logic: Callable that takes (func, args, kwargs) and returns (args, kwargs)
|
|
147
|
+
metadata: Additional metadata to store on wrapper
|
|
148
|
+
register: Whether to register wrapper in function_registry (default: True)
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Wrapped function with injection capability
|
|
152
|
+
"""
|
|
153
|
+
# Detect async vs sync
|
|
154
|
+
is_async = inspect.iscoroutinefunction(func)
|
|
155
|
+
|
|
156
|
+
if is_async:
|
|
157
|
+
wrapper = self._create_async_wrapper(
|
|
158
|
+
func, function_id, injection_logic, metadata
|
|
159
|
+
)
|
|
160
|
+
logger.debug(f"✅ Created async wrapper for {function_id}")
|
|
161
|
+
else:
|
|
162
|
+
wrapper = self._create_sync_wrapper(
|
|
163
|
+
func, function_id, injection_logic, metadata
|
|
164
|
+
)
|
|
165
|
+
logger.debug(f"✅ Created sync wrapper for {function_id}")
|
|
166
|
+
|
|
167
|
+
# Register wrapper if requested
|
|
168
|
+
if register:
|
|
169
|
+
self._register_wrapper(function_id, wrapper)
|
|
170
|
+
|
|
171
|
+
return wrapper
|
|
@@ -32,6 +32,18 @@ class DecoratedFunction:
|
|
|
32
32
|
self.metadata["function_name"] = self.function.__name__
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class LLMAgentMetadata:
|
|
37
|
+
"""Metadata for a function decorated with @mesh.llm."""
|
|
38
|
+
|
|
39
|
+
function: Callable
|
|
40
|
+
config: dict[str, Any] # LLM configuration (provider, model, filter, etc.)
|
|
41
|
+
output_type: Optional[type] # Pydantic model type from return annotation
|
|
42
|
+
param_name: str # Name of MeshLlmAgent parameter
|
|
43
|
+
function_id: str # Unique function ID for registry
|
|
44
|
+
registered_at: datetime
|
|
45
|
+
|
|
46
|
+
|
|
35
47
|
class DecoratorRegistry:
|
|
36
48
|
"""
|
|
37
49
|
Central registry for ALL MCP Mesh decorators.
|
|
@@ -52,19 +64,23 @@ class DecoratorRegistry:
|
|
|
52
64
|
_mesh_tools: dict[str, DecoratedFunction] = {} # Future use
|
|
53
65
|
_mesh_resources: dict[str, DecoratedFunction] = {} # Future use
|
|
54
66
|
_mesh_workflows: dict[str, DecoratedFunction] = {} # Future use
|
|
67
|
+
_mesh_llm_agents: dict[str, "LLMAgentMetadata"] = {} # LLM agents with agentic loop
|
|
55
68
|
|
|
56
69
|
# Registry for new decorator types (extensibility)
|
|
57
70
|
_custom_decorators: dict[str, dict[str, DecoratedFunction]] = {}
|
|
58
|
-
|
|
71
|
+
|
|
59
72
|
# Immediate uvicorn server storage (for preventing shutdown state)
|
|
60
73
|
_immediate_uvicorn_server: Optional[dict[str, Any]] = None
|
|
61
|
-
|
|
74
|
+
|
|
62
75
|
# FastMCP lifespan storage (for proper integration with FastAPI)
|
|
63
76
|
_fastmcp_lifespan: Optional[Any] = None
|
|
64
|
-
|
|
77
|
+
|
|
65
78
|
# FastMCP HTTP app storage (the same app instance whose lifespan was extracted)
|
|
66
79
|
_fastmcp_http_app: Optional[Any] = None
|
|
67
80
|
|
|
81
|
+
# FastMCP server info storage (for schema extraction during heartbeat)
|
|
82
|
+
_fastmcp_server_info: Optional[dict[str, Any]] = None
|
|
83
|
+
|
|
68
84
|
@classmethod
|
|
69
85
|
def register_mesh_agent(cls, func: Callable, metadata: dict[str, Any]) -> None:
|
|
70
86
|
"""
|
|
@@ -101,11 +117,13 @@ class DecoratorRegistry:
|
|
|
101
117
|
if func_name in cls._mesh_tools:
|
|
102
118
|
old_func = cls._mesh_tools[func_name].function
|
|
103
119
|
cls._mesh_tools[func_name].function = new_func
|
|
104
|
-
|
|
120
|
+
logger.debug(
|
|
105
121
|
f"🔄 DecoratorRegistry: Updated '{func_name}' from {hex(id(old_func))} to {hex(id(new_func))}"
|
|
106
122
|
)
|
|
107
123
|
else:
|
|
108
|
-
|
|
124
|
+
logger.debug(
|
|
125
|
+
f"⚠️ DecoratorRegistry: Function '{func_name}' not found for update"
|
|
126
|
+
)
|
|
109
127
|
|
|
110
128
|
@classmethod
|
|
111
129
|
def register_mesh_resource(cls, func: Callable, metadata: dict[str, Any]) -> None:
|
|
@@ -131,6 +149,53 @@ class DecoratorRegistry:
|
|
|
131
149
|
|
|
132
150
|
cls._mesh_workflows[func.__name__] = decorated_func
|
|
133
151
|
|
|
152
|
+
@classmethod
|
|
153
|
+
def register_mesh_llm(
|
|
154
|
+
cls,
|
|
155
|
+
func: Callable,
|
|
156
|
+
config: dict[str, Any],
|
|
157
|
+
output_type: Optional[type],
|
|
158
|
+
param_name: str,
|
|
159
|
+
function_id: str,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Register a @mesh.llm decorated function.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
func: The decorated function
|
|
166
|
+
config: LLM configuration (provider, model, filter, etc.)
|
|
167
|
+
output_type: Pydantic model type from return annotation
|
|
168
|
+
param_name: Name of MeshLlmAgent parameter
|
|
169
|
+
function_id: Unique function ID for registry
|
|
170
|
+
"""
|
|
171
|
+
llm_metadata = LLMAgentMetadata(
|
|
172
|
+
function=func,
|
|
173
|
+
config=config.copy(),
|
|
174
|
+
output_type=output_type,
|
|
175
|
+
param_name=param_name,
|
|
176
|
+
function_id=function_id,
|
|
177
|
+
registered_at=datetime.now(),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
cls._mesh_llm_agents[function_id] = llm_metadata
|
|
181
|
+
logger.info(
|
|
182
|
+
f"🤖 Registered LLM agent: {func.__name__} (function_id={function_id}, param={param_name}, filter={config.get('filter')}, provider={config.get('provider')})"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def update_mesh_llm_function(cls, function_id: str, new_func: Callable) -> None:
|
|
187
|
+
"""Update the function reference for a registered LLM agent (used for wrapper injection)."""
|
|
188
|
+
if function_id in cls._mesh_llm_agents:
|
|
189
|
+
old_func = cls._mesh_llm_agents[function_id].function
|
|
190
|
+
cls._mesh_llm_agents[function_id].function = new_func
|
|
191
|
+
logger.info(
|
|
192
|
+
f"🔄 DecoratorRegistry: Updated LLM function '{function_id}' from {hex(id(old_func))} to {hex(id(new_func))}"
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
logger.warning(
|
|
196
|
+
f"⚠️ DecoratorRegistry: LLM function '{function_id}' not found for update"
|
|
197
|
+
)
|
|
198
|
+
|
|
134
199
|
@classmethod
|
|
135
200
|
def register_custom_decorator(
|
|
136
201
|
cls, decorator_type: str, func: Callable, metadata: dict[str, Any]
|
|
@@ -175,6 +240,11 @@ class DecoratorRegistry:
|
|
|
175
240
|
"""Get all @mesh_workflow decorated functions."""
|
|
176
241
|
return cls._mesh_workflows.copy()
|
|
177
242
|
|
|
243
|
+
@classmethod
|
|
244
|
+
def get_mesh_llm_agents(cls) -> dict[str, LLMAgentMetadata]:
|
|
245
|
+
"""Get all @mesh.llm decorated functions."""
|
|
246
|
+
return cls._mesh_llm_agents.copy()
|
|
247
|
+
|
|
178
248
|
@classmethod
|
|
179
249
|
def get_all_by_type(cls, decorator_type: str) -> dict[str, DecoratedFunction]:
|
|
180
250
|
"""
|
|
@@ -256,6 +326,7 @@ class DecoratorRegistry:
|
|
|
256
326
|
cls._mesh_tools.clear()
|
|
257
327
|
cls._mesh_resources.clear()
|
|
258
328
|
cls._mesh_workflows.clear()
|
|
329
|
+
cls._mesh_llm_agents.clear()
|
|
259
330
|
cls._custom_decorators.clear()
|
|
260
331
|
|
|
261
332
|
# Also clear the shared agent ID from mesh.decorators
|
|
@@ -290,23 +361,21 @@ class DecoratorRegistry:
|
|
|
290
361
|
def update_agent_config(cls, updates: dict[str, Any]) -> None:
|
|
291
362
|
"""
|
|
292
363
|
Update the cached agent configuration with new values.
|
|
293
|
-
|
|
364
|
+
|
|
294
365
|
This is useful for API services that generate their agent ID
|
|
295
366
|
during pipeline execution and need to store it for telemetry.
|
|
296
|
-
|
|
367
|
+
|
|
297
368
|
Args:
|
|
298
369
|
updates: Dictionary of config values to update
|
|
299
370
|
"""
|
|
300
371
|
if cls._cached_agent_config is None:
|
|
301
372
|
# Initialize with current resolved config if not cached yet
|
|
302
373
|
cls._cached_agent_config = cls.get_resolved_agent_config().copy()
|
|
303
|
-
|
|
374
|
+
|
|
304
375
|
# Update with new values
|
|
305
376
|
cls._cached_agent_config.update(updates)
|
|
306
|
-
|
|
307
|
-
logger.debug(
|
|
308
|
-
f"🔧 Updated cached agent configuration with: {updates}"
|
|
309
|
-
)
|
|
377
|
+
|
|
378
|
+
logger.debug(f"🔧 Updated cached agent configuration with: {updates}")
|
|
310
379
|
|
|
311
380
|
@classmethod
|
|
312
381
|
def get_resolved_agent_config(cls) -> dict[str, Any]:
|
|
@@ -320,7 +389,9 @@ class DecoratorRegistry:
|
|
|
320
389
|
dict: Pre-resolved configuration with consistent agent_id
|
|
321
390
|
"""
|
|
322
391
|
# Step 1: Check if cached configuration already has agent_id (from API pipeline)
|
|
323
|
-
if cls._cached_agent_config is not None and cls._cached_agent_config.get(
|
|
392
|
+
if cls._cached_agent_config is not None and cls._cached_agent_config.get(
|
|
393
|
+
"agent_id"
|
|
394
|
+
):
|
|
324
395
|
logger.debug(
|
|
325
396
|
f"🔧 Using cached agent configuration: agent_id='{cls._cached_agent_config.get('agent_id')}'"
|
|
326
397
|
)
|
|
@@ -348,15 +419,16 @@ class DecoratorRegistry:
|
|
|
348
419
|
# Check if we're in an API context (have mesh_route decorators)
|
|
349
420
|
mesh_routes = cls.get_all_by_type("mesh_route")
|
|
350
421
|
is_api_context = len(mesh_routes) > 0
|
|
351
|
-
|
|
422
|
+
|
|
352
423
|
if is_api_context:
|
|
353
424
|
# Use API service ID generation logic for consistency
|
|
354
425
|
agent_id = cls._generate_api_service_id_fallback()
|
|
355
426
|
else:
|
|
356
427
|
# Use standard MCP agent ID generation
|
|
357
428
|
from mesh.decorators import _get_or_create_agent_id
|
|
429
|
+
|
|
358
430
|
agent_id = _get_or_create_agent_id()
|
|
359
|
-
|
|
431
|
+
|
|
360
432
|
fallback_config = {
|
|
361
433
|
"name": None,
|
|
362
434
|
"version": get_config_value(
|
|
@@ -415,44 +487,44 @@ class DecoratorRegistry:
|
|
|
415
487
|
def _generate_api_service_id_fallback(cls) -> str:
|
|
416
488
|
"""
|
|
417
489
|
Generate API service ID as fallback using same priority logic as API pipeline.
|
|
418
|
-
|
|
490
|
+
|
|
419
491
|
Priority order:
|
|
420
|
-
1. MCP_MESH_API_NAME environment variable
|
|
492
|
+
1. MCP_MESH_API_NAME environment variable
|
|
421
493
|
2. MCP_MESH_AGENT_NAME environment variable (fallback)
|
|
422
494
|
3. Default to "api-{uuid8}"
|
|
423
|
-
|
|
495
|
+
|
|
424
496
|
Returns:
|
|
425
497
|
Generated service ID with UUID suffix matching API service format
|
|
426
498
|
"""
|
|
427
499
|
import uuid
|
|
428
|
-
|
|
500
|
+
|
|
429
501
|
from ..shared.config_resolver import ValidationRule, get_config_value
|
|
430
|
-
|
|
502
|
+
|
|
431
503
|
# Check for API-specific environment variable first (same as API pipeline)
|
|
432
504
|
api_name = get_config_value(
|
|
433
505
|
"MCP_MESH_API_NAME",
|
|
434
506
|
default=None,
|
|
435
507
|
rule=ValidationRule.STRING_RULE,
|
|
436
508
|
)
|
|
437
|
-
|
|
509
|
+
|
|
438
510
|
# Fallback to general agent name env var
|
|
439
511
|
if not api_name:
|
|
440
512
|
api_name = get_config_value(
|
|
441
|
-
"MCP_MESH_AGENT_NAME",
|
|
513
|
+
"MCP_MESH_AGENT_NAME",
|
|
442
514
|
default=None,
|
|
443
515
|
rule=ValidationRule.STRING_RULE,
|
|
444
516
|
)
|
|
445
|
-
|
|
517
|
+
|
|
446
518
|
# Clean the service name if provided
|
|
447
519
|
if api_name:
|
|
448
520
|
cleaned_name = api_name.lower().replace(" ", "-").replace("_", "-")
|
|
449
521
|
cleaned_name = "-".join(part for part in cleaned_name.split("-") if part)
|
|
450
522
|
else:
|
|
451
523
|
cleaned_name = ""
|
|
452
|
-
|
|
524
|
+
|
|
453
525
|
# Generate UUID suffix
|
|
454
526
|
uuid_suffix = str(uuid.uuid4())[:8]
|
|
455
|
-
|
|
527
|
+
|
|
456
528
|
# Apply same naming logic as API pipeline
|
|
457
529
|
if not cleaned_name:
|
|
458
530
|
# No name provided: default to "api-{uuid8}"
|
|
@@ -463,8 +535,10 @@ class DecoratorRegistry:
|
|
|
463
535
|
else:
|
|
464
536
|
# Name doesn't contain "api": use "{name}-api-{uuid8}"
|
|
465
537
|
service_id = f"{cleaned_name}-api-{uuid_suffix}"
|
|
466
|
-
|
|
467
|
-
logger.debug(
|
|
538
|
+
|
|
539
|
+
logger.debug(
|
|
540
|
+
f"Generated fallback API service ID: '{service_id}' from env name: '{api_name}'"
|
|
541
|
+
)
|
|
468
542
|
return service_id
|
|
469
543
|
|
|
470
544
|
@classmethod
|
|
@@ -521,23 +595,25 @@ class DecoratorRegistry:
|
|
|
521
595
|
def store_immediate_uvicorn_server(cls, server_info: dict[str, Any]) -> None:
|
|
522
596
|
"""
|
|
523
597
|
Store reference to immediate uvicorn server started in decorator.
|
|
524
|
-
|
|
598
|
+
|
|
525
599
|
Args:
|
|
526
600
|
server_info: Dictionary containing server information:
|
|
527
601
|
- 'app': FastAPI app instance
|
|
528
602
|
- 'host': Server host
|
|
529
|
-
- 'port': Server port
|
|
603
|
+
- 'port': Server port
|
|
530
604
|
- 'thread': Thread object
|
|
531
605
|
- Any other relevant server metadata
|
|
532
606
|
"""
|
|
533
607
|
cls._immediate_uvicorn_server = server_info
|
|
534
|
-
logger.debug(
|
|
608
|
+
logger.debug(
|
|
609
|
+
f"🔄 REGISTRY: Stored immediate uvicorn server reference: {server_info.get('host')}:{server_info.get('port')}"
|
|
610
|
+
)
|
|
535
611
|
|
|
536
612
|
@classmethod
|
|
537
613
|
def get_immediate_uvicorn_server(cls) -> Optional[dict[str, Any]]:
|
|
538
614
|
"""
|
|
539
615
|
Get stored immediate uvicorn server reference.
|
|
540
|
-
|
|
616
|
+
|
|
541
617
|
Returns:
|
|
542
618
|
Server info dict if available, None otherwise
|
|
543
619
|
"""
|
|
@@ -549,11 +625,33 @@ class DecoratorRegistry:
|
|
|
549
625
|
cls._immediate_uvicorn_server = None
|
|
550
626
|
logger.debug("🔄 REGISTRY: Cleared immediate uvicorn server reference")
|
|
551
627
|
|
|
628
|
+
# Health check result storage
|
|
629
|
+
_health_check_result: dict | None = None
|
|
630
|
+
|
|
631
|
+
@classmethod
|
|
632
|
+
def store_health_check_result(cls, result: dict) -> None:
|
|
633
|
+
"""Store health check result for /health endpoint."""
|
|
634
|
+
cls._health_check_result = result
|
|
635
|
+
logger.debug(
|
|
636
|
+
f"💾 REGISTRY: Stored health check result: {result.get('status', 'unknown')}"
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
@classmethod
|
|
640
|
+
def get_health_check_result(cls) -> dict | None:
|
|
641
|
+
"""Get stored health check result."""
|
|
642
|
+
return cls._health_check_result
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def clear_health_check_result(cls) -> None:
|
|
646
|
+
"""Clear stored health check result."""
|
|
647
|
+
cls._health_check_result = None
|
|
648
|
+
logger.debug("🗑️ REGISTRY: Cleared health check result")
|
|
649
|
+
|
|
552
650
|
@classmethod
|
|
553
651
|
def store_fastmcp_lifespan(cls, lifespan: Any) -> None:
|
|
554
652
|
"""
|
|
555
653
|
Store FastMCP lifespan for integration with FastAPI.
|
|
556
|
-
|
|
654
|
+
|
|
557
655
|
Args:
|
|
558
656
|
lifespan: FastMCP lifespan function
|
|
559
657
|
"""
|
|
@@ -564,7 +662,7 @@ class DecoratorRegistry:
|
|
|
564
662
|
def get_fastmcp_lifespan(cls) -> Optional[Any]:
|
|
565
663
|
"""
|
|
566
664
|
Get stored FastMCP lifespan.
|
|
567
|
-
|
|
665
|
+
|
|
568
666
|
Returns:
|
|
569
667
|
FastMCP lifespan if available, None otherwise
|
|
570
668
|
"""
|
|
@@ -580,7 +678,7 @@ class DecoratorRegistry:
|
|
|
580
678
|
def store_fastmcp_http_app(cls, http_app: Any) -> None:
|
|
581
679
|
"""
|
|
582
680
|
Store FastMCP HTTP app (the same instance whose lifespan was extracted).
|
|
583
|
-
|
|
681
|
+
|
|
584
682
|
Args:
|
|
585
683
|
http_app: FastMCP HTTP app instance
|
|
586
684
|
"""
|
|
@@ -591,7 +689,7 @@ class DecoratorRegistry:
|
|
|
591
689
|
def get_fastmcp_http_app(cls) -> Optional[Any]:
|
|
592
690
|
"""
|
|
593
691
|
Get stored FastMCP HTTP app.
|
|
594
|
-
|
|
692
|
+
|
|
595
693
|
Returns:
|
|
596
694
|
FastMCP HTTP app if available, None otherwise
|
|
597
695
|
"""
|
|
@@ -603,6 +701,35 @@ class DecoratorRegistry:
|
|
|
603
701
|
cls._fastmcp_http_app = None
|
|
604
702
|
logger.debug("🔄 REGISTRY: Cleared FastMCP HTTP app reference")
|
|
605
703
|
|
|
704
|
+
@classmethod
|
|
705
|
+
def store_fastmcp_server_info(cls, server_info: dict[str, Any]) -> None:
|
|
706
|
+
"""
|
|
707
|
+
Store FastMCP server info for schema extraction during heartbeat.
|
|
708
|
+
|
|
709
|
+
Args:
|
|
710
|
+
server_info: Dictionary of server_name -> server metadata (including tools)
|
|
711
|
+
"""
|
|
712
|
+
cls._fastmcp_server_info = server_info
|
|
713
|
+
logger.debug(
|
|
714
|
+
f"🔄 REGISTRY: Stored FastMCP server info for {len(server_info)} servers"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
@classmethod
|
|
718
|
+
def get_fastmcp_server_info(cls) -> Optional[dict[str, Any]]:
|
|
719
|
+
"""
|
|
720
|
+
Get stored FastMCP server info.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
FastMCP server info if available, None otherwise
|
|
724
|
+
"""
|
|
725
|
+
return cls._fastmcp_server_info
|
|
726
|
+
|
|
727
|
+
@classmethod
|
|
728
|
+
def clear_fastmcp_server_info(cls) -> None:
|
|
729
|
+
"""Clear stored FastMCP server info reference."""
|
|
730
|
+
cls._fastmcp_server_info = None
|
|
731
|
+
logger.debug("🔄 REGISTRY: Cleared FastMCP server info reference")
|
|
732
|
+
|
|
606
733
|
|
|
607
734
|
# Convenience functions for external access
|
|
608
735
|
def get_all_mesh_agents() -> dict[str, DecoratedFunction]:
|