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
@@ -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
- create_minimal_lifespan,
241
- create_multiple_fastmcp_lifespan,
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 get_health_status_with_cache
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, Dict, Optional
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 function
52
- from ..mcp_heartbeat import heartbeat_lifespan_task
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 - registry connection will be attempted in heartbeat pipeline
53
+ # Create heartbeat config - Rust core handles registry connection
55
54
  heartbeat_config = {
56
- "registry_wrapper": None, # Will be created in heartbeat pipeline
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": heartbeat_lifespan_task, # Pass function to avoid cross-imports
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 same daemon thread pattern as API apps
201
- from ..mcp_heartbeat.lifespan_integration import (
202
- heartbeat_lifespan_task,
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
- heartbeat_lifespan_task,
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(f"Starting background heartbeat thread for {entity_id}")
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
- ConfigurationStep,
14
- DecoratorCollectionStep,
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)
@@ -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 == "RegistryClient":
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
- Provides centralized environment variable handling with consistent validation
5
- and graceful error handling with fallback to defaults.
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, Union
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 validation fails and no valid default
86
+ ConfigResolutionError: If both the resolved value and an explicit
87
+ default fail validation (indicates a programming error).
55
88
  """
56
- # Step 1: Determine raw value following precedence order
57
- raw_value = None
58
- source = "default"
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
- raw_value = default
72
- source = "default"
94
+ # Non-mesh config or Rust core unavailable - use Python fallback
95
+ raw_value = _resolve_via_python(env_var, override, default)
73
96
 
74
- # Step 2: Validate and convert the value
97
+ # Validate and convert the value
75
98
  try:
76
- validated_value = _validate_value(raw_value, rule, env_var)
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
- # Try fallback in precedence order: env > override > default
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
- logger.warning(
88
- f"Falling back to override value for {env_var}: {override}"
89
- )
90
- validated_override = _validate_value(override, rule, env_var)
91
- return validated_override
92
- except ConfigResolutionError:
93
- # Override also failed, try default
94
- if default is not None:
95
- try:
96
- logger.warning(
97
- f"Falling back to default value for {env_var}: {default}"
98
- )
99
- validated_default = _validate_value(default, rule, env_var)
100
- return validated_default
101
- except ConfigResolutionError:
102
- logger.error(
103
- f"All values for {env_var} are invalid, using None"
104
- )
105
- return None
106
- else:
107
- logger.error(
108
- f"Override and default values for {env_var} are invalid, using None"
109
- )
110
- return None
111
- elif source == "environment" and default is not None:
112
- # Environment failed and no override, try default
113
- try:
114
- logger.warning(
115
- f"Falling back to default value for {env_var}: {default}"
116
- )
117
- validated_default = _validate_value(default, rule, env_var)
118
- return validated_default
119
- except ConfigResolutionError:
120
- logger.error(f"Default value for {env_var} is also invalid, using None")
121
- return None
122
- elif source == "override" and default is not None:
123
- # Override failed, try default
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
- logger.warning(
126
- f"Falling back to default value for {env_var}: {default}"
127
- )
128
- validated_default = _validate_value(default, rule, env_var)
129
- return validated_default
130
- except ConfigResolutionError:
131
- logger.error(f"Default value for {env_var} is also invalid, using None")
132
- return None
133
- else:
134
- # Already tried the last option or no fallbacks available
135
- logger.error(f"No valid fallback for {env_var}, using None")
136
- return None
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:
@@ -1,32 +1,103 @@
1
1
  """
2
2
  Centralized system defaults for MCP Mesh.
3
3
 
4
- This module provides a single source of truth for all default configuration values
5
- in the MCP Mesh system. All defaults should be defined here and accessed through
6
- the config_resolver system to ensure consistent precedence handling.
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
- # Health and heartbeat configuration
16
- HEALTH_INTERVAL = 5 # seconds - fast heartbeat optimization
17
- AUTO_RUN_INTERVAL = 10 # seconds
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 # auto-assign
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
- NAMESPACE = "default"
26
- AUTO_RUN = True
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 defaults (if needed in future)
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,