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
|
@@ -11,13 +11,10 @@ from .fastapiserver_setup import FastAPIServerSetupStep
|
|
|
11
11
|
from .fastmcpserver_discovery import FastMCPServerDiscoveryStep
|
|
12
12
|
from .heartbeat_loop import HeartbeatLoopStep
|
|
13
13
|
from .heartbeat_preparation import HeartbeatPreparationStep
|
|
14
|
-
from .startup_orchestrator import (
|
|
15
|
-
MeshOrchestrator,
|
|
14
|
+
from .startup_orchestrator import (MeshOrchestrator,
|
|
16
15
|
clear_debounce_coordinator,
|
|
17
16
|
get_debounce_coordinator,
|
|
18
|
-
get_global_orchestrator,
|
|
19
|
-
start_runtime,
|
|
20
|
-
)
|
|
17
|
+
get_global_orchestrator, start_runtime)
|
|
21
18
|
from .startup_pipeline import StartupPipeline
|
|
22
19
|
|
|
23
20
|
__all__ = [
|
|
@@ -37,10 +37,10 @@ class ConfigurationStep(PipelineStep):
|
|
|
37
37
|
has_explicit_agent = bool(mesh_agents)
|
|
38
38
|
|
|
39
39
|
# Resolve registry URL (not part of agent parameters)
|
|
40
|
+
# Default is handled by Rust core
|
|
40
41
|
registry_url = get_config_value(
|
|
41
42
|
"MCP_MESH_REGISTRY_URL",
|
|
42
43
|
override=None, # No decorator override for registry URL
|
|
43
|
-
default="http://localhost:8000",
|
|
44
44
|
rule=ValidationRule.URL_RULE,
|
|
45
45
|
)
|
|
46
46
|
|
|
@@ -236,11 +236,9 @@ class FastAPIServerSetupStep(PipelineStep):
|
|
|
236
236
|
try:
|
|
237
237
|
from fastapi import FastAPI
|
|
238
238
|
|
|
239
|
-
from .lifespan_factory import (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
create_single_fastmcp_lifespan,
|
|
243
|
-
)
|
|
239
|
+
from .lifespan_factory import (create_minimal_lifespan,
|
|
240
|
+
create_multiple_fastmcp_lifespan,
|
|
241
|
+
create_single_fastmcp_lifespan)
|
|
244
242
|
|
|
245
243
|
agent_name = agent_config.get("name", "mcp-mesh-agent")
|
|
246
244
|
agent_description = agent_config.get(
|
|
@@ -330,7 +328,8 @@ class FastAPIServerSetupStep(PipelineStep):
|
|
|
330
328
|
if health_check_fn:
|
|
331
329
|
# Use health check cache if configured
|
|
332
330
|
from ...engine.decorator_registry import DecoratorRegistry
|
|
333
|
-
from ...shared.health_check_manager import
|
|
331
|
+
from ...shared.health_check_manager import \
|
|
332
|
+
get_health_status_with_cache
|
|
334
333
|
|
|
335
334
|
health_status = await get_health_status_with_cache(
|
|
336
335
|
agent_id=agent_name,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from ...shared.config_resolver import ValidationRule, get_config_value
|
|
7
|
-
from ...shared.registry_client_wrapper import RegistryClientWrapper
|
|
8
7
|
from ..shared import PipelineResult, PipelineStatus, PipelineStep
|
|
9
8
|
|
|
10
9
|
|
|
@@ -48,16 +47,16 @@ class HeartbeatLoopStep(PipelineStep):
|
|
|
48
47
|
# Check for explicit standalone mode configuration
|
|
49
48
|
standalone_mode = self._get_standalone_mode()
|
|
50
49
|
|
|
51
|
-
# Import heartbeat task
|
|
52
|
-
from ..mcp_heartbeat import
|
|
50
|
+
# Import Rust-backed heartbeat task (required - raises RuntimeError if Rust core missing)
|
|
51
|
+
from ..mcp_heartbeat.rust_heartbeat import rust_heartbeat_task
|
|
53
52
|
|
|
54
|
-
# Create heartbeat config -
|
|
53
|
+
# Create heartbeat config - Rust core handles registry connection
|
|
55
54
|
heartbeat_config = {
|
|
56
|
-
"registry_wrapper": None, #
|
|
55
|
+
"registry_wrapper": None, # Rust core manages registry connection
|
|
57
56
|
"agent_id": agent_id,
|
|
58
57
|
"interval": heartbeat_interval,
|
|
59
58
|
"context": context, # Pass full context for health status building
|
|
60
|
-
"heartbeat_task_fn":
|
|
59
|
+
"heartbeat_task_fn": rust_heartbeat_task, # Use Rust-backed heartbeat
|
|
61
60
|
"standalone_mode": standalone_mode,
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -181,9 +181,8 @@ class DebounceCoordinator:
|
|
|
181
181
|
# For API services, ONLY do dependency injection - user controls their FastAPI server
|
|
182
182
|
# Dependency injection is already complete from pipeline execution
|
|
183
183
|
# Optionally start heartbeat in background (non-blocking)
|
|
184
|
-
from ..api_heartbeat.api_lifespan_integration import
|
|
185
|
-
api_heartbeat_lifespan_task
|
|
186
|
-
)
|
|
184
|
+
from ..api_heartbeat.api_lifespan_integration import \
|
|
185
|
+
api_heartbeat_lifespan_task
|
|
187
186
|
|
|
188
187
|
self._setup_heartbeat_background(
|
|
189
188
|
heartbeat_config,
|
|
@@ -197,15 +196,26 @@ class DebounceCoordinator:
|
|
|
197
196
|
)
|
|
198
197
|
return # Don't block - let user's uvicorn run
|
|
199
198
|
elif fastapi_app and binding_config:
|
|
200
|
-
# For MCP agents - use
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
# For MCP agents - use Rust-backed heartbeat task from config
|
|
200
|
+
# HeartbeatLoopStep sets this to rust_heartbeat_task
|
|
201
|
+
heartbeat_task_fn = heartbeat_config.get("heartbeat_task_fn")
|
|
202
|
+
|
|
203
|
+
# Validate heartbeat_task_fn is callable, fall back to Rust heartbeat if not
|
|
204
|
+
if heartbeat_task_fn is None or not callable(heartbeat_task_fn):
|
|
205
|
+
if heartbeat_task_fn is not None:
|
|
206
|
+
self.logger.warning(
|
|
207
|
+
f"heartbeat_task_fn from config is not callable: {type(heartbeat_task_fn)}, using Rust heartbeat"
|
|
208
|
+
)
|
|
209
|
+
# Rust heartbeat is required - no Python fallback
|
|
210
|
+
from ..mcp_heartbeat.rust_heartbeat import \
|
|
211
|
+
rust_heartbeat_task
|
|
212
|
+
|
|
213
|
+
heartbeat_task_fn = rust_heartbeat_task
|
|
204
214
|
|
|
205
215
|
self._setup_heartbeat_background(
|
|
206
216
|
heartbeat_config,
|
|
207
217
|
pipeline_context,
|
|
208
|
-
|
|
218
|
+
heartbeat_task_fn,
|
|
209
219
|
)
|
|
210
220
|
|
|
211
221
|
# Check if server was already reused from immediate uvicorn start
|
|
@@ -370,7 +380,9 @@ class DebounceCoordinator:
|
|
|
370
380
|
|
|
371
381
|
def run_heartbeat():
|
|
372
382
|
"""Run heartbeat in separate thread with its own event loop."""
|
|
373
|
-
self.logger.debug(
|
|
383
|
+
self.logger.debug(
|
|
384
|
+
f"Starting background heartbeat thread for {entity_id}"
|
|
385
|
+
)
|
|
374
386
|
try:
|
|
375
387
|
loop = asyncio.new_event_loop()
|
|
376
388
|
asyncio.set_event_loop(loop)
|
|
@@ -9,14 +9,9 @@ and FastAPI server preparation.
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
11
|
from ..shared.mesh_pipeline import MeshPipeline
|
|
12
|
-
from . import (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
FastAPIServerSetupStep,
|
|
16
|
-
FastMCPServerDiscoveryStep,
|
|
17
|
-
HeartbeatLoopStep,
|
|
18
|
-
HeartbeatPreparationStep,
|
|
19
|
-
)
|
|
12
|
+
from . import (ConfigurationStep, DecoratorCollectionStep,
|
|
13
|
+
FastAPIServerSetupStep, FastMCPServerDiscoveryStep,
|
|
14
|
+
HeartbeatLoopStep, HeartbeatPreparationStep)
|
|
20
15
|
from .server_discovery import ServerDiscoveryStep
|
|
21
16
|
|
|
22
17
|
logger = logging.getLogger(__name__)
|
|
@@ -78,7 +78,6 @@ class MeshPipeline:
|
|
|
78
78
|
step_result = await step.execute(self.context)
|
|
79
79
|
executed_steps += 1
|
|
80
80
|
|
|
81
|
-
|
|
82
81
|
# Log step result
|
|
83
82
|
if step_result.is_success():
|
|
84
83
|
self.logger.info(
|
|
@@ -113,7 +112,6 @@ class MeshPipeline:
|
|
|
113
112
|
except Exception as e:
|
|
114
113
|
executed_steps += 1
|
|
115
114
|
|
|
116
|
-
|
|
117
115
|
error_msg = f"Step '{step.name}' threw exception: {e}"
|
|
118
116
|
overall_result.add_error(error_msg)
|
|
119
117
|
self.logger.error(
|
_mcp_mesh/reload.py
CHANGED
|
@@ -103,9 +103,7 @@ def terminate_process(process: subprocess.Popen, timeout: int = 3) -> None:
|
|
|
103
103
|
try:
|
|
104
104
|
process.wait(timeout=timeout)
|
|
105
105
|
except subprocess.TimeoutExpired:
|
|
106
|
-
logger.warning(
|
|
107
|
-
f"Process {pid} didn't terminate gracefully, force killing..."
|
|
108
|
-
)
|
|
106
|
+
logger.warning(f"Process {pid} didn't terminate gracefully, force killing...")
|
|
109
107
|
# Force kill the entire process group
|
|
110
108
|
try:
|
|
111
109
|
os.killpg(pid, signal.SIGKILL)
|
_mcp_mesh/shared/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ Shared utilities and types built on the official MCP SDK.
|
|
|
5
5
|
Common functionality used across server and client components.
|
|
6
6
|
|
|
7
7
|
Core mesh processing infrastructure has been moved to mcp_mesh.engine.
|
|
8
|
+
Registry communication is handled by the Rust core.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
# Import only non-circular dependencies at module level
|
|
@@ -13,7 +14,6 @@ from .support_types import DependencyConfig, HealthStatus
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"HealthStatus",
|
|
15
16
|
"DependencyConfig",
|
|
16
|
-
"RegistryClient",
|
|
17
17
|
"ContentExtractor",
|
|
18
18
|
"configure_logging",
|
|
19
19
|
]
|
|
@@ -22,13 +22,7 @@ __all__ = [
|
|
|
22
22
|
# Lazy imports for circular dependency resolution
|
|
23
23
|
def __getattr__(name):
|
|
24
24
|
"""Lazy import to avoid circular dependencies."""
|
|
25
|
-
if name == "
|
|
26
|
-
from ..generated.mcp_mesh_registry_client.api_client import (
|
|
27
|
-
ApiClient as RegistryClient,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
return RegistryClient
|
|
31
|
-
elif name == "ContentExtractor":
|
|
25
|
+
if name == "ContentExtractor":
|
|
32
26
|
from .content_extractor import ContentExtractor
|
|
33
27
|
|
|
34
28
|
return ContentExtractor
|
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Configuration value resolver with validation rules.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Delegates config resolution to Rust core for MCP Mesh config keys to ensure
|
|
5
|
+
consistent behavior across all language SDKs. Non-mesh config uses Python fallback.
|
|
6
|
+
|
|
7
|
+
Resolution priority (handled by Rust core): ENV > override > default
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
import logging
|
|
9
|
-
import os
|
|
10
11
|
from enum import Enum
|
|
11
|
-
from typing import Any
|
|
12
|
+
from typing import Any
|
|
12
13
|
from urllib.parse import urlparse
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
17
|
+
# Try to import the Rust core module for config resolution
|
|
18
|
+
# Falls back to Python-only resolution if not available
|
|
19
|
+
try:
|
|
20
|
+
import mcp_mesh_core
|
|
21
|
+
|
|
22
|
+
_RUST_CORE_AVAILABLE = True
|
|
23
|
+
except ImportError as e:
|
|
24
|
+
mcp_mesh_core = None # type: ignore[assignment]
|
|
25
|
+
_RUST_CORE_AVAILABLE = False
|
|
26
|
+
logger.warning(
|
|
27
|
+
"mcp_mesh_core not available - falling back to Python-only config resolution. "
|
|
28
|
+
"Build/install mcp-mesh-core for full functionality: cd src/runtime/core && maturin develop"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Map env var names to Rust config key names
|
|
32
|
+
_ENV_TO_RUST_KEY: dict[str, str] = {
|
|
33
|
+
"MCP_MESH_REGISTRY_URL": "registry_url",
|
|
34
|
+
"MCP_MESH_HTTP_HOST": "http_host",
|
|
35
|
+
"MCP_MESH_HTTP_PORT": "http_port",
|
|
36
|
+
"MCP_MESH_NAMESPACE": "namespace",
|
|
37
|
+
"MCP_MESH_AGENT_NAME": "agent_name",
|
|
38
|
+
"MCP_MESH_AGENT_ID": "agent_id",
|
|
39
|
+
"MCP_MESH_HEALTH_INTERVAL": "health_interval",
|
|
40
|
+
"MCP_MESH_DISTRIBUTED_TRACING_ENABLED": "distributed_tracing_enabled",
|
|
41
|
+
"REDIS_URL": "redis_url",
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
|
|
17
45
|
class ValidationRule(Enum):
|
|
18
46
|
"""Validation rules for configuration values."""
|
|
@@ -41,6 +69,9 @@ def get_config_value(
|
|
|
41
69
|
Resolve configuration value with precedence: ENV > override > default
|
|
42
70
|
Then validate against the specified rule.
|
|
43
71
|
|
|
72
|
+
For MCP Mesh config keys (MCP_MESH_*, REDIS_URL), resolution is delegated
|
|
73
|
+
to Rust core for consistency across all language SDKs.
|
|
74
|
+
|
|
44
75
|
Args:
|
|
45
76
|
env_var: Environment variable name
|
|
46
77
|
override: Programmatic override value
|
|
@@ -48,92 +79,105 @@ def get_config_value(
|
|
|
48
79
|
rule: Validation rule to apply
|
|
49
80
|
|
|
50
81
|
Returns:
|
|
51
|
-
Validated configuration value
|
|
82
|
+
Validated configuration value, or None if no default was provided
|
|
83
|
+
and the resolved value failed validation.
|
|
52
84
|
|
|
53
85
|
Raises:
|
|
54
|
-
ConfigResolutionError: If
|
|
86
|
+
ConfigResolutionError: If both the resolved value and an explicit
|
|
87
|
+
default fail validation (indicates a programming error).
|
|
55
88
|
"""
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# Check environment variable first (highest precedence)
|
|
61
|
-
env_value = os.environ.get(env_var)
|
|
62
|
-
if env_value is not None:
|
|
63
|
-
raw_value = env_value
|
|
64
|
-
source = "environment"
|
|
65
|
-
# Check override value second
|
|
66
|
-
elif override is not None:
|
|
67
|
-
raw_value = override
|
|
68
|
-
source = "override"
|
|
69
|
-
# Use default value last
|
|
89
|
+
# Check if this is a known mesh config key - delegate to Rust core if available
|
|
90
|
+
rust_key = _ENV_TO_RUST_KEY.get(env_var)
|
|
91
|
+
if rust_key is not None and _RUST_CORE_AVAILABLE:
|
|
92
|
+
raw_value = _resolve_via_rust(rust_key, override, default, rule)
|
|
70
93
|
else:
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
# Non-mesh config or Rust core unavailable - use Python fallback
|
|
95
|
+
raw_value = _resolve_via_python(env_var, override, default)
|
|
73
96
|
|
|
74
|
-
#
|
|
97
|
+
# Validate and convert the value
|
|
75
98
|
try:
|
|
76
|
-
|
|
77
|
-
return validated_value
|
|
78
|
-
|
|
99
|
+
return _validate_value(raw_value, rule, env_var)
|
|
79
100
|
except ConfigResolutionError as e:
|
|
80
|
-
# If validation fails, log error and try to fall back following precedence order
|
|
81
101
|
logger.error(f"Config validation failed for {env_var}: {e}")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if source == "environment" and override is not None:
|
|
85
|
-
# Environment failed, try override
|
|
102
|
+
# Try fallback to default if validation failed
|
|
103
|
+
if default is not None and raw_value != default:
|
|
86
104
|
try:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
105
|
+
return _validate_value(default, rule, env_var)
|
|
106
|
+
except ConfigResolutionError as default_error:
|
|
107
|
+
# Both raw_value and explicit default failed validation - this is a programming error
|
|
108
|
+
raise ConfigResolutionError(
|
|
109
|
+
f"{env_var}: both resolved value '{raw_value}' and default '{default}' failed validation"
|
|
110
|
+
) from default_error
|
|
111
|
+
# No default provided or raw_value == default, return None for optional config
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _resolve_via_rust(
|
|
116
|
+
rust_key: str, override: Any, default: Any, rule: ValidationRule
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""Resolve config value via Rust core.
|
|
119
|
+
|
|
120
|
+
Maintains ENV > override > default precedence by always calling Rust resolver.
|
|
121
|
+
Invalid overrides are treated as None so Rust can fall back to ENV vars.
|
|
122
|
+
"""
|
|
123
|
+
if mcp_mesh_core is None:
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
"mcp_mesh_core is not available - this function should not be called"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Convert override to string for Rust (it expects Option<String>)
|
|
129
|
+
param_str = str(override) if override is not None else None
|
|
130
|
+
|
|
131
|
+
# Use appropriate Rust function based on validation rule
|
|
132
|
+
if rule == ValidationRule.TRUTHY_RULE:
|
|
133
|
+
# Boolean resolution - try to coerce override, use None if invalid
|
|
134
|
+
# This ensures Rust can still check ENV vars when override is invalid
|
|
135
|
+
param_bool = None
|
|
136
|
+
if override is not None:
|
|
137
|
+
if isinstance(override, bool):
|
|
138
|
+
param_bool = override
|
|
139
|
+
elif isinstance(override, str):
|
|
140
|
+
lower_val = override.lower()
|
|
141
|
+
if lower_val in ("true", "1", "yes", "on"):
|
|
142
|
+
param_bool = True
|
|
143
|
+
elif lower_val in ("false", "0", "no", "off"):
|
|
144
|
+
param_bool = False
|
|
145
|
+
# else: invalid string - leave param_bool as None, let Rust check ENV
|
|
146
|
+
else:
|
|
147
|
+
param_bool = bool(override)
|
|
148
|
+
return mcp_mesh_core.resolve_config_bool_py(rust_key, param_bool)
|
|
149
|
+
|
|
150
|
+
elif rule in (ValidationRule.PORT_RULE, ValidationRule.NONZERO_RULE):
|
|
151
|
+
# Integer resolution - try to coerce override, use None if invalid
|
|
152
|
+
# This ensures Rust can still check ENV vars when override is invalid
|
|
153
|
+
param_int = None
|
|
154
|
+
if override is not None:
|
|
124
155
|
try:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
156
|
+
param_int = int(override)
|
|
157
|
+
except (ValueError, TypeError):
|
|
158
|
+
# Invalid override - leave param_int as None, let Rust check ENV
|
|
159
|
+
pass
|
|
160
|
+
result = mcp_mesh_core.resolve_config_int_py(rust_key, param_int)
|
|
161
|
+
return result if result is not None else default
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
# String resolution (default)
|
|
165
|
+
# Rust core returns empty string when no value found, treat as None
|
|
166
|
+
result = mcp_mesh_core.resolve_config_py(rust_key, param_str)
|
|
167
|
+
return result if result else default
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _resolve_via_python(env_var: str, override: Any, default: Any) -> Any:
|
|
171
|
+
"""Resolve config value via Python os.environ (fallback for non-mesh config)."""
|
|
172
|
+
import os
|
|
173
|
+
|
|
174
|
+
env_value = os.environ.get(env_var)
|
|
175
|
+
if env_value is not None:
|
|
176
|
+
return env_value
|
|
177
|
+
elif override is not None:
|
|
178
|
+
return override
|
|
179
|
+
else:
|
|
180
|
+
return default
|
|
137
181
|
|
|
138
182
|
|
|
139
183
|
def _validate_value(value: Any, rule: ValidationRule, env_var: str) -> Any:
|
_mcp_mesh/shared/defaults.py
CHANGED
|
@@ -1,32 +1,103 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Centralized system defaults for MCP Mesh.
|
|
3
3
|
|
|
4
|
-
This module provides
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
This module provides access to default configuration values. For config keys
|
|
5
|
+
that exist in Rust core (registry_url, namespace, health_interval, etc.),
|
|
6
|
+
defaults are fetched from Rust to ensure consistency across all SDKs.
|
|
7
|
+
|
|
8
|
+
Python-specific defaults (like AUTO_RUN, HTTP_ENABLED) remain here as they
|
|
9
|
+
are not applicable to other language SDKs.
|
|
7
10
|
"""
|
|
8
11
|
|
|
12
|
+
import logging
|
|
9
13
|
from typing import Any
|
|
10
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Try to import the Rust core module for defaults
|
|
18
|
+
try:
|
|
19
|
+
import mcp_mesh_core
|
|
20
|
+
|
|
21
|
+
_RUST_CORE_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
mcp_mesh_core = None # type: ignore[assignment]
|
|
24
|
+
_RUST_CORE_AVAILABLE = False
|
|
25
|
+
logger.debug("mcp_mesh_core not available - using Python-only defaults")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_rust_default(key: str, fallback: Any = None) -> Any:
|
|
29
|
+
"""Get default value from Rust core, with fallback for when core is unavailable."""
|
|
30
|
+
if _RUST_CORE_AVAILABLE and mcp_mesh_core is not None:
|
|
31
|
+
result = mcp_mesh_core.get_default_py(key)
|
|
32
|
+
if result is not None:
|
|
33
|
+
return result
|
|
34
|
+
return fallback
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_rust_default_int(key: str, fallback: int) -> int:
|
|
38
|
+
"""Get integer default value from Rust core."""
|
|
39
|
+
if _RUST_CORE_AVAILABLE and mcp_mesh_core is not None:
|
|
40
|
+
result = mcp_mesh_core.get_default_py(key)
|
|
41
|
+
if result is not None:
|
|
42
|
+
try:
|
|
43
|
+
return int(result)
|
|
44
|
+
except (ValueError, TypeError):
|
|
45
|
+
pass
|
|
46
|
+
return fallback
|
|
47
|
+
|
|
11
48
|
|
|
12
49
|
class MeshDefaults:
|
|
13
|
-
"""Centralized defaults for all MCP Mesh configuration values.
|
|
50
|
+
"""Centralized defaults for all MCP Mesh configuration values.
|
|
51
|
+
|
|
52
|
+
Config keys that exist in Rust core fetch their defaults from Rust.
|
|
53
|
+
Python-specific config values have their defaults defined here.
|
|
54
|
+
"""
|
|
14
55
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
56
|
+
# ==========================================================================
|
|
57
|
+
# Defaults from Rust core (fetched dynamically)
|
|
58
|
+
# ==========================================================================
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
@property
|
|
62
|
+
def HEALTH_INTERVAL(cls) -> int:
|
|
63
|
+
"""Heartbeat interval in seconds. From Rust core."""
|
|
64
|
+
return _get_rust_default_int("health_interval", 5)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
@property
|
|
68
|
+
def NAMESPACE(cls) -> str:
|
|
69
|
+
"""Default namespace. From Rust core."""
|
|
70
|
+
return _get_rust_default("namespace", "default")
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
@property
|
|
74
|
+
def REGISTRY_URL(cls) -> str:
|
|
75
|
+
"""Default registry URL. From Rust core."""
|
|
76
|
+
return _get_rust_default("registry_url", "http://localhost:8000")
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
@property
|
|
80
|
+
def REDIS_URL(cls) -> str:
|
|
81
|
+
"""Default Redis URL. From Rust core."""
|
|
82
|
+
return _get_rust_default("redis_url", "redis://localhost:6379")
|
|
83
|
+
|
|
84
|
+
# ==========================================================================
|
|
85
|
+
# Python-specific defaults (not in Rust core)
|
|
86
|
+
# ==========================================================================
|
|
18
87
|
|
|
19
88
|
# HTTP server configuration
|
|
20
|
-
HTTP_HOST = "0.0.0.0"
|
|
21
|
-
HTTP_PORT = 0 #
|
|
89
|
+
HTTP_HOST = "0.0.0.0" # Binding address (Rust core has auto-detect for external IP)
|
|
90
|
+
HTTP_PORT = 0 # Auto-assign port
|
|
22
91
|
HTTP_ENABLED = True
|
|
23
92
|
|
|
24
|
-
# Agent configuration
|
|
25
|
-
|
|
26
|
-
|
|
93
|
+
# Agent behavior configuration
|
|
94
|
+
AUTO_RUN = True # Auto-start agent after decoration
|
|
95
|
+
AUTO_RUN_INTERVAL = 10 # seconds - debounce for auto-run
|
|
96
|
+
|
|
97
|
+
# Version
|
|
27
98
|
VERSION = "1.0.0"
|
|
28
99
|
|
|
29
|
-
# Registry configuration
|
|
100
|
+
# Registry configuration
|
|
30
101
|
REGISTRY_TIMEOUT = 30 # seconds
|
|
31
102
|
|
|
32
103
|
@classmethod
|
|
@@ -38,12 +109,16 @@ class MeshDefaults:
|
|
|
38
109
|
Dictionary of all default configuration values
|
|
39
110
|
"""
|
|
40
111
|
return {
|
|
112
|
+
# From Rust core
|
|
41
113
|
"health_interval": cls.HEALTH_INTERVAL,
|
|
114
|
+
"namespace": cls.NAMESPACE,
|
|
115
|
+
"registry_url": cls.REGISTRY_URL,
|
|
116
|
+
"redis_url": cls.REDIS_URL,
|
|
117
|
+
# Python-specific
|
|
42
118
|
"auto_run_interval": cls.AUTO_RUN_INTERVAL,
|
|
43
119
|
"http_host": cls.HTTP_HOST,
|
|
44
120
|
"http_port": cls.HTTP_PORT,
|
|
45
121
|
"http_enabled": cls.HTTP_ENABLED,
|
|
46
|
-
"namespace": cls.NAMESPACE,
|
|
47
122
|
"auto_run": cls.AUTO_RUN,
|
|
48
123
|
"version": cls.VERSION,
|
|
49
124
|
"registry_timeout": cls.REGISTRY_TIMEOUT,
|