mcp-mesh 0.5.7__py3-none-any.whl → 0.6.0__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 +136 -33
- _mcp_mesh/engine/dependency_injector.py +91 -18
- _mcp_mesh/engine/http_wrapper.py +5 -22
- _mcp_mesh/engine/llm_config.py +41 -0
- _mcp_mesh/engine/llm_errors.py +115 -0
- _mcp_mesh/engine/mesh_llm_agent.py +440 -0
- _mcp_mesh/engine/mesh_llm_agent_injector.py +487 -0
- _mcp_mesh/engine/response_parser.py +240 -0
- _mcp_mesh/engine/signature_analyzer.py +229 -99
- _mcp_mesh/engine/tool_executor.py +169 -0
- _mcp_mesh/engine/tool_schema_builder.py +125 -0
- _mcp_mesh/engine/unified_mcp_proxy.py +14 -12
- _mcp_mesh/generated/.openapi-generator/FILES +4 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +81 -44
- _mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +72 -35
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +132 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +172 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +92 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +121 -0
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +98 -51
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +93 -44
- _mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +84 -41
- _mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +9 -72
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +6 -3
- _mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +222 -0
- _mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +7 -0
- _mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +65 -4
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +2 -2
- _mcp_mesh/shared/registry_client_wrapper.py +60 -4
- _mcp_mesh/utils/fastmcp_schema_extractor.py +476 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/RECORD +39 -25
- mesh/__init__.py +8 -4
- mesh/decorators.py +344 -2
- mesh/types.py +145 -94
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.7.dist-info → mcp_mesh-0.6.0.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
|
"""
|
|
@@ -131,6 +147,53 @@ class DecoratorRegistry:
|
|
|
131
147
|
|
|
132
148
|
cls._mesh_workflows[func.__name__] = decorated_func
|
|
133
149
|
|
|
150
|
+
@classmethod
|
|
151
|
+
def register_mesh_llm(
|
|
152
|
+
cls,
|
|
153
|
+
func: Callable,
|
|
154
|
+
config: dict[str, Any],
|
|
155
|
+
output_type: Optional[type],
|
|
156
|
+
param_name: str,
|
|
157
|
+
function_id: str,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Register a @mesh.llm decorated function.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
func: The decorated function
|
|
164
|
+
config: LLM configuration (provider, model, filter, etc.)
|
|
165
|
+
output_type: Pydantic model type from return annotation
|
|
166
|
+
param_name: Name of MeshLlmAgent parameter
|
|
167
|
+
function_id: Unique function ID for registry
|
|
168
|
+
"""
|
|
169
|
+
llm_metadata = LLMAgentMetadata(
|
|
170
|
+
function=func,
|
|
171
|
+
config=config.copy(),
|
|
172
|
+
output_type=output_type,
|
|
173
|
+
param_name=param_name,
|
|
174
|
+
function_id=function_id,
|
|
175
|
+
registered_at=datetime.now(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
cls._mesh_llm_agents[function_id] = llm_metadata
|
|
179
|
+
logger.info(
|
|
180
|
+
f"🤖 Registered LLM agent: {func.__name__} (function_id={function_id}, param={param_name}, filter={config.get('filter')}, provider={config.get('provider')})"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def update_mesh_llm_function(cls, function_id: str, new_func: Callable) -> None:
|
|
185
|
+
"""Update the function reference for a registered LLM agent (used for wrapper injection)."""
|
|
186
|
+
if function_id in cls._mesh_llm_agents:
|
|
187
|
+
old_func = cls._mesh_llm_agents[function_id].function
|
|
188
|
+
cls._mesh_llm_agents[function_id].function = new_func
|
|
189
|
+
logger.info(
|
|
190
|
+
f"🔄 DecoratorRegistry: Updated LLM function '{function_id}' from {hex(id(old_func))} to {hex(id(new_func))}"
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
logger.warning(
|
|
194
|
+
f"⚠️ DecoratorRegistry: LLM function '{function_id}' not found for update"
|
|
195
|
+
)
|
|
196
|
+
|
|
134
197
|
@classmethod
|
|
135
198
|
def register_custom_decorator(
|
|
136
199
|
cls, decorator_type: str, func: Callable, metadata: dict[str, Any]
|
|
@@ -175,6 +238,11 @@ class DecoratorRegistry:
|
|
|
175
238
|
"""Get all @mesh_workflow decorated functions."""
|
|
176
239
|
return cls._mesh_workflows.copy()
|
|
177
240
|
|
|
241
|
+
@classmethod
|
|
242
|
+
def get_mesh_llm_agents(cls) -> dict[str, LLMAgentMetadata]:
|
|
243
|
+
"""Get all @mesh.llm decorated functions."""
|
|
244
|
+
return cls._mesh_llm_agents.copy()
|
|
245
|
+
|
|
178
246
|
@classmethod
|
|
179
247
|
def get_all_by_type(cls, decorator_type: str) -> dict[str, DecoratedFunction]:
|
|
180
248
|
"""
|
|
@@ -256,6 +324,7 @@ class DecoratorRegistry:
|
|
|
256
324
|
cls._mesh_tools.clear()
|
|
257
325
|
cls._mesh_resources.clear()
|
|
258
326
|
cls._mesh_workflows.clear()
|
|
327
|
+
cls._mesh_llm_agents.clear()
|
|
259
328
|
cls._custom_decorators.clear()
|
|
260
329
|
|
|
261
330
|
# Also clear the shared agent ID from mesh.decorators
|
|
@@ -290,23 +359,21 @@ class DecoratorRegistry:
|
|
|
290
359
|
def update_agent_config(cls, updates: dict[str, Any]) -> None:
|
|
291
360
|
"""
|
|
292
361
|
Update the cached agent configuration with new values.
|
|
293
|
-
|
|
362
|
+
|
|
294
363
|
This is useful for API services that generate their agent ID
|
|
295
364
|
during pipeline execution and need to store it for telemetry.
|
|
296
|
-
|
|
365
|
+
|
|
297
366
|
Args:
|
|
298
367
|
updates: Dictionary of config values to update
|
|
299
368
|
"""
|
|
300
369
|
if cls._cached_agent_config is None:
|
|
301
370
|
# Initialize with current resolved config if not cached yet
|
|
302
371
|
cls._cached_agent_config = cls.get_resolved_agent_config().copy()
|
|
303
|
-
|
|
372
|
+
|
|
304
373
|
# Update with new values
|
|
305
374
|
cls._cached_agent_config.update(updates)
|
|
306
|
-
|
|
307
|
-
logger.debug(
|
|
308
|
-
f"🔧 Updated cached agent configuration with: {updates}"
|
|
309
|
-
)
|
|
375
|
+
|
|
376
|
+
logger.debug(f"🔧 Updated cached agent configuration with: {updates}")
|
|
310
377
|
|
|
311
378
|
@classmethod
|
|
312
379
|
def get_resolved_agent_config(cls) -> dict[str, Any]:
|
|
@@ -320,7 +387,9 @@ class DecoratorRegistry:
|
|
|
320
387
|
dict: Pre-resolved configuration with consistent agent_id
|
|
321
388
|
"""
|
|
322
389
|
# 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(
|
|
390
|
+
if cls._cached_agent_config is not None and cls._cached_agent_config.get(
|
|
391
|
+
"agent_id"
|
|
392
|
+
):
|
|
324
393
|
logger.debug(
|
|
325
394
|
f"🔧 Using cached agent configuration: agent_id='{cls._cached_agent_config.get('agent_id')}'"
|
|
326
395
|
)
|
|
@@ -348,15 +417,16 @@ class DecoratorRegistry:
|
|
|
348
417
|
# Check if we're in an API context (have mesh_route decorators)
|
|
349
418
|
mesh_routes = cls.get_all_by_type("mesh_route")
|
|
350
419
|
is_api_context = len(mesh_routes) > 0
|
|
351
|
-
|
|
420
|
+
|
|
352
421
|
if is_api_context:
|
|
353
422
|
# Use API service ID generation logic for consistency
|
|
354
423
|
agent_id = cls._generate_api_service_id_fallback()
|
|
355
424
|
else:
|
|
356
425
|
# Use standard MCP agent ID generation
|
|
357
426
|
from mesh.decorators import _get_or_create_agent_id
|
|
427
|
+
|
|
358
428
|
agent_id = _get_or_create_agent_id()
|
|
359
|
-
|
|
429
|
+
|
|
360
430
|
fallback_config = {
|
|
361
431
|
"name": None,
|
|
362
432
|
"version": get_config_value(
|
|
@@ -415,44 +485,44 @@ class DecoratorRegistry:
|
|
|
415
485
|
def _generate_api_service_id_fallback(cls) -> str:
|
|
416
486
|
"""
|
|
417
487
|
Generate API service ID as fallback using same priority logic as API pipeline.
|
|
418
|
-
|
|
488
|
+
|
|
419
489
|
Priority order:
|
|
420
|
-
1. MCP_MESH_API_NAME environment variable
|
|
490
|
+
1. MCP_MESH_API_NAME environment variable
|
|
421
491
|
2. MCP_MESH_AGENT_NAME environment variable (fallback)
|
|
422
492
|
3. Default to "api-{uuid8}"
|
|
423
|
-
|
|
493
|
+
|
|
424
494
|
Returns:
|
|
425
495
|
Generated service ID with UUID suffix matching API service format
|
|
426
496
|
"""
|
|
427
497
|
import uuid
|
|
428
|
-
|
|
498
|
+
|
|
429
499
|
from ..shared.config_resolver import ValidationRule, get_config_value
|
|
430
|
-
|
|
500
|
+
|
|
431
501
|
# Check for API-specific environment variable first (same as API pipeline)
|
|
432
502
|
api_name = get_config_value(
|
|
433
503
|
"MCP_MESH_API_NAME",
|
|
434
504
|
default=None,
|
|
435
505
|
rule=ValidationRule.STRING_RULE,
|
|
436
506
|
)
|
|
437
|
-
|
|
507
|
+
|
|
438
508
|
# Fallback to general agent name env var
|
|
439
509
|
if not api_name:
|
|
440
510
|
api_name = get_config_value(
|
|
441
|
-
"MCP_MESH_AGENT_NAME",
|
|
511
|
+
"MCP_MESH_AGENT_NAME",
|
|
442
512
|
default=None,
|
|
443
513
|
rule=ValidationRule.STRING_RULE,
|
|
444
514
|
)
|
|
445
|
-
|
|
515
|
+
|
|
446
516
|
# Clean the service name if provided
|
|
447
517
|
if api_name:
|
|
448
518
|
cleaned_name = api_name.lower().replace(" ", "-").replace("_", "-")
|
|
449
519
|
cleaned_name = "-".join(part for part in cleaned_name.split("-") if part)
|
|
450
520
|
else:
|
|
451
521
|
cleaned_name = ""
|
|
452
|
-
|
|
522
|
+
|
|
453
523
|
# Generate UUID suffix
|
|
454
524
|
uuid_suffix = str(uuid.uuid4())[:8]
|
|
455
|
-
|
|
525
|
+
|
|
456
526
|
# Apply same naming logic as API pipeline
|
|
457
527
|
if not cleaned_name:
|
|
458
528
|
# No name provided: default to "api-{uuid8}"
|
|
@@ -463,8 +533,10 @@ class DecoratorRegistry:
|
|
|
463
533
|
else:
|
|
464
534
|
# Name doesn't contain "api": use "{name}-api-{uuid8}"
|
|
465
535
|
service_id = f"{cleaned_name}-api-{uuid_suffix}"
|
|
466
|
-
|
|
467
|
-
logger.debug(
|
|
536
|
+
|
|
537
|
+
logger.debug(
|
|
538
|
+
f"Generated fallback API service ID: '{service_id}' from env name: '{api_name}'"
|
|
539
|
+
)
|
|
468
540
|
return service_id
|
|
469
541
|
|
|
470
542
|
@classmethod
|
|
@@ -521,23 +593,25 @@ class DecoratorRegistry:
|
|
|
521
593
|
def store_immediate_uvicorn_server(cls, server_info: dict[str, Any]) -> None:
|
|
522
594
|
"""
|
|
523
595
|
Store reference to immediate uvicorn server started in decorator.
|
|
524
|
-
|
|
596
|
+
|
|
525
597
|
Args:
|
|
526
598
|
server_info: Dictionary containing server information:
|
|
527
599
|
- 'app': FastAPI app instance
|
|
528
600
|
- 'host': Server host
|
|
529
|
-
- 'port': Server port
|
|
601
|
+
- 'port': Server port
|
|
530
602
|
- 'thread': Thread object
|
|
531
603
|
- Any other relevant server metadata
|
|
532
604
|
"""
|
|
533
605
|
cls._immediate_uvicorn_server = server_info
|
|
534
|
-
logger.debug(
|
|
606
|
+
logger.debug(
|
|
607
|
+
f"🔄 REGISTRY: Stored immediate uvicorn server reference: {server_info.get('host')}:{server_info.get('port')}"
|
|
608
|
+
)
|
|
535
609
|
|
|
536
610
|
@classmethod
|
|
537
611
|
def get_immediate_uvicorn_server(cls) -> Optional[dict[str, Any]]:
|
|
538
612
|
"""
|
|
539
613
|
Get stored immediate uvicorn server reference.
|
|
540
|
-
|
|
614
|
+
|
|
541
615
|
Returns:
|
|
542
616
|
Server info dict if available, None otherwise
|
|
543
617
|
"""
|
|
@@ -553,7 +627,7 @@ class DecoratorRegistry:
|
|
|
553
627
|
def store_fastmcp_lifespan(cls, lifespan: Any) -> None:
|
|
554
628
|
"""
|
|
555
629
|
Store FastMCP lifespan for integration with FastAPI.
|
|
556
|
-
|
|
630
|
+
|
|
557
631
|
Args:
|
|
558
632
|
lifespan: FastMCP lifespan function
|
|
559
633
|
"""
|
|
@@ -564,7 +638,7 @@ class DecoratorRegistry:
|
|
|
564
638
|
def get_fastmcp_lifespan(cls) -> Optional[Any]:
|
|
565
639
|
"""
|
|
566
640
|
Get stored FastMCP lifespan.
|
|
567
|
-
|
|
641
|
+
|
|
568
642
|
Returns:
|
|
569
643
|
FastMCP lifespan if available, None otherwise
|
|
570
644
|
"""
|
|
@@ -580,7 +654,7 @@ class DecoratorRegistry:
|
|
|
580
654
|
def store_fastmcp_http_app(cls, http_app: Any) -> None:
|
|
581
655
|
"""
|
|
582
656
|
Store FastMCP HTTP app (the same instance whose lifespan was extracted).
|
|
583
|
-
|
|
657
|
+
|
|
584
658
|
Args:
|
|
585
659
|
http_app: FastMCP HTTP app instance
|
|
586
660
|
"""
|
|
@@ -591,7 +665,7 @@ class DecoratorRegistry:
|
|
|
591
665
|
def get_fastmcp_http_app(cls) -> Optional[Any]:
|
|
592
666
|
"""
|
|
593
667
|
Get stored FastMCP HTTP app.
|
|
594
|
-
|
|
668
|
+
|
|
595
669
|
Returns:
|
|
596
670
|
FastMCP HTTP app if available, None otherwise
|
|
597
671
|
"""
|
|
@@ -603,6 +677,35 @@ class DecoratorRegistry:
|
|
|
603
677
|
cls._fastmcp_http_app = None
|
|
604
678
|
logger.debug("🔄 REGISTRY: Cleared FastMCP HTTP app reference")
|
|
605
679
|
|
|
680
|
+
@classmethod
|
|
681
|
+
def store_fastmcp_server_info(cls, server_info: dict[str, Any]) -> None:
|
|
682
|
+
"""
|
|
683
|
+
Store FastMCP server info for schema extraction during heartbeat.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
server_info: Dictionary of server_name -> server metadata (including tools)
|
|
687
|
+
"""
|
|
688
|
+
cls._fastmcp_server_info = server_info
|
|
689
|
+
logger.debug(
|
|
690
|
+
f"🔄 REGISTRY: Stored FastMCP server info for {len(server_info)} servers"
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
@classmethod
|
|
694
|
+
def get_fastmcp_server_info(cls) -> Optional[dict[str, Any]]:
|
|
695
|
+
"""
|
|
696
|
+
Get stored FastMCP server info.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
FastMCP server info if available, None otherwise
|
|
700
|
+
"""
|
|
701
|
+
return cls._fastmcp_server_info
|
|
702
|
+
|
|
703
|
+
@classmethod
|
|
704
|
+
def clear_fastmcp_server_info(cls) -> None:
|
|
705
|
+
"""Clear stored FastMCP server info reference."""
|
|
706
|
+
cls._fastmcp_server_info = None
|
|
707
|
+
logger.debug("🔄 REGISTRY: Cleared FastMCP server info reference")
|
|
708
|
+
|
|
606
709
|
|
|
607
710
|
# Convenience functions for external access
|
|
608
711
|
def get_all_mesh_agents() -> dict[str, DecoratedFunction]:
|