mcp-mesh 0.7.21__py3-none-any.whl → 0.8.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/dependency_injector.py +13 -15
- _mcp_mesh/engine/http_wrapper.py +69 -10
- _mcp_mesh/engine/mesh_llm_agent.py +29 -10
- _mcp_mesh/engine/mesh_llm_agent_injector.py +77 -41
- _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/signature_analyzer.py +58 -68
- _mcp_mesh/engine/unified_mcp_proxy.py +19 -35
- _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 +429 -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 +710 -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 +31 -8
- _mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +6 -7
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +23 -11
- _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 +5 -4
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/METADATA +7 -5
- mcp_mesh-0.8.0.dist-info/RECORD +85 -0
- mesh/__init__.py +12 -1
- mesh/decorators.py +248 -33
- mesh/helpers.py +52 -0
- mesh/types.py +40 -13
- _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.0.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.7.21.dist-info → mcp_mesh-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,15 +8,19 @@ robust app discovery, and graceful error handling.
|
|
|
8
8
|
|
|
9
9
|
import gc
|
|
10
10
|
import logging
|
|
11
|
+
import threading
|
|
11
12
|
import time
|
|
12
13
|
from typing import List, Optional, Set
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
17
|
+
# Lock for thread-safe singleton initialization
|
|
18
|
+
_middleware_manager_lock = threading.Lock()
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
class FastAPIMiddlewareManager:
|
|
18
22
|
"""Enhanced FastAPI middleware injection manager.
|
|
19
|
-
|
|
23
|
+
|
|
20
24
|
Provides robust middleware injection capabilities with:
|
|
21
25
|
- Multiple app discovery methods
|
|
22
26
|
- App state detection and validation
|
|
@@ -24,60 +28,72 @@ class FastAPIMiddlewareManager:
|
|
|
24
28
|
- Graceful error handling
|
|
25
29
|
- Monkey-patch FastAPI creation for immediate injection
|
|
26
30
|
"""
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
def __init__(self):
|
|
29
33
|
self._processed_apps: Set[int] = set() # Track processed apps by id()
|
|
30
34
|
self._monkey_patch_applied = False
|
|
31
35
|
self._pending_middleware_needed = False # Flag that middleware is needed
|
|
32
|
-
|
|
36
|
+
|
|
33
37
|
def enable_middleware_injection(self) -> bool:
|
|
34
38
|
"""
|
|
35
39
|
Enable middleware injection via monkey-patching FastAPI creation.
|
|
36
|
-
|
|
40
|
+
|
|
37
41
|
This sets up automatic middleware injection when FastAPI apps are created,
|
|
38
42
|
eliminating timing issues with app startup.
|
|
39
|
-
|
|
43
|
+
|
|
40
44
|
Returns:
|
|
41
45
|
bool: True if monkey-patch was applied successfully
|
|
42
46
|
"""
|
|
43
47
|
if self._monkey_patch_applied:
|
|
44
48
|
logger.debug("🔍 TRACING: Monkey-patch already applied")
|
|
45
49
|
return True
|
|
46
|
-
|
|
50
|
+
|
|
47
51
|
try:
|
|
48
52
|
from fastapi import FastAPI
|
|
49
|
-
|
|
53
|
+
|
|
50
54
|
# Store original FastAPI.__init__
|
|
51
|
-
if not hasattr(FastAPI,
|
|
55
|
+
if not hasattr(FastAPI, "_original_init"):
|
|
52
56
|
FastAPI._original_init = FastAPI.__init__
|
|
53
|
-
|
|
57
|
+
|
|
54
58
|
# Create enhanced __init__ that adds middleware immediately
|
|
55
59
|
def enhanced_fastapi_init(self, *args, **kwargs):
|
|
56
60
|
# Call original FastAPI initialization
|
|
57
61
|
result = FastAPI._original_init(self, *args, **kwargs)
|
|
58
|
-
|
|
62
|
+
|
|
59
63
|
# Immediately add middleware to this newly created app
|
|
60
64
|
try:
|
|
61
65
|
manager = get_fastapi_middleware_manager()
|
|
62
66
|
if manager._pending_middleware_needed:
|
|
63
67
|
success = manager.add_middleware_to_specific_app(self)
|
|
64
68
|
if success:
|
|
65
|
-
logger.debug(
|
|
69
|
+
logger.debug(
|
|
70
|
+
f"🔍 TRACING: Auto-injected middleware to FastAPI app '{getattr(self, 'title', 'Unknown')}' during creation"
|
|
71
|
+
)
|
|
66
72
|
else:
|
|
67
|
-
logger.debug(
|
|
73
|
+
logger.debug(
|
|
74
|
+
f"🔍 TRACING: Failed to auto-inject middleware during app creation"
|
|
75
|
+
)
|
|
68
76
|
except Exception as e:
|
|
69
77
|
# Never break FastAPI app creation
|
|
70
|
-
logger.debug(
|
|
71
|
-
|
|
78
|
+
logger.debug(
|
|
79
|
+
f"🔍 TRACING: Auto-injection failed during app creation: {e}"
|
|
80
|
+
)
|
|
81
|
+
|
|
72
82
|
return result
|
|
73
|
-
|
|
83
|
+
|
|
74
84
|
# Apply the monkey-patch
|
|
75
85
|
FastAPI.__init__ = enhanced_fastapi_init
|
|
76
86
|
self._monkey_patch_applied = True
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
|
|
88
|
+
# Ensure manager is initialized before any app instances are created
|
|
89
|
+
# This guarantees the closure in enhanced_fastapi_init can rely on a ready manager
|
|
90
|
+
get_fastapi_middleware_manager()
|
|
91
|
+
|
|
92
|
+
logger.debug(
|
|
93
|
+
"🔍 TRACING: Successfully applied FastAPI creation monkey-patch"
|
|
94
|
+
)
|
|
79
95
|
return True
|
|
80
|
-
|
|
96
|
+
|
|
81
97
|
except Exception as e:
|
|
82
98
|
logger.debug(f"🔍 TRACING: Failed to apply FastAPI monkey-patch: {e}")
|
|
83
99
|
return False
|
|
@@ -85,89 +101,95 @@ class FastAPIMiddlewareManager:
|
|
|
85
101
|
def request_middleware_injection(self) -> bool:
|
|
86
102
|
"""
|
|
87
103
|
Request that middleware be injected into FastAPI apps.
|
|
88
|
-
|
|
104
|
+
|
|
89
105
|
This method should be called from @mesh.route decorators to signal
|
|
90
106
|
that middleware injection is needed.
|
|
91
|
-
|
|
107
|
+
|
|
92
108
|
Returns:
|
|
93
109
|
bool: True if injection was set up successfully
|
|
94
110
|
"""
|
|
95
111
|
self._pending_middleware_needed = True
|
|
96
|
-
|
|
112
|
+
|
|
97
113
|
# Try immediate discovery first (for apps that already exist)
|
|
98
114
|
immediate_success = self.add_tracing_middleware_to_discovered_apps()
|
|
99
|
-
|
|
115
|
+
|
|
100
116
|
# Also enable monkey-patch for future app creation
|
|
101
117
|
monkey_patch_success = self.enable_middleware_injection()
|
|
102
|
-
|
|
103
|
-
logger.debug(
|
|
118
|
+
|
|
119
|
+
logger.debug(
|
|
120
|
+
f"🔍 TRACING: Middleware injection requested - immediate: {immediate_success}, monkey-patch: {monkey_patch_success}"
|
|
121
|
+
)
|
|
104
122
|
return immediate_success or monkey_patch_success
|
|
105
123
|
|
|
106
124
|
def add_tracing_middleware_to_discovered_apps(self) -> bool:
|
|
107
125
|
"""
|
|
108
126
|
Add tracing middleware to all discovered FastAPI apps.
|
|
109
|
-
|
|
127
|
+
|
|
110
128
|
Returns:
|
|
111
129
|
bool: True if any middleware was successfully added, False otherwise
|
|
112
130
|
"""
|
|
113
|
-
logger.debug(
|
|
114
|
-
|
|
131
|
+
logger.debug(
|
|
132
|
+
"🔍 TRACING: Starting enhanced middleware injection for FastAPI apps..."
|
|
133
|
+
)
|
|
134
|
+
|
|
115
135
|
apps = self._discover_fastapi_apps()
|
|
116
136
|
if not apps:
|
|
117
137
|
logger.debug("🔍 TRACING: No FastAPI apps discovered")
|
|
118
138
|
return False
|
|
119
|
-
|
|
139
|
+
|
|
120
140
|
success_count = 0
|
|
121
141
|
for app in apps:
|
|
122
142
|
if self._add_middleware_to_app_with_retry(app):
|
|
123
143
|
success_count += 1
|
|
124
|
-
|
|
125
|
-
logger.debug(
|
|
144
|
+
|
|
145
|
+
logger.debug(
|
|
146
|
+
f"🔍 TRACING: Enhanced middleware injection completed - {success_count}/{len(apps)} apps processed"
|
|
147
|
+
)
|
|
126
148
|
return success_count > 0
|
|
127
|
-
|
|
149
|
+
|
|
128
150
|
def add_middleware_to_specific_app(self, app) -> bool:
|
|
129
151
|
"""
|
|
130
152
|
Add middleware to a specific FastAPI app.
|
|
131
|
-
|
|
153
|
+
|
|
132
154
|
Args:
|
|
133
155
|
app: FastAPI application instance
|
|
134
|
-
|
|
156
|
+
|
|
135
157
|
Returns:
|
|
136
158
|
bool: True if middleware was successfully added
|
|
137
159
|
"""
|
|
138
160
|
return self._add_middleware_to_app_with_retry(app)
|
|
139
|
-
|
|
161
|
+
|
|
140
162
|
def _discover_fastapi_apps(self) -> List:
|
|
141
163
|
"""
|
|
142
164
|
Discover FastAPI apps using multiple methods.
|
|
143
|
-
|
|
165
|
+
|
|
144
166
|
Returns:
|
|
145
167
|
List of FastAPI app instances
|
|
146
168
|
"""
|
|
147
169
|
apps = []
|
|
148
|
-
|
|
170
|
+
|
|
149
171
|
# Try to import FastAPI
|
|
150
172
|
try:
|
|
151
173
|
from fastapi import FastAPI
|
|
152
174
|
except ImportError:
|
|
153
175
|
logger.debug("🔍 TRACING: FastAPI not available")
|
|
154
176
|
return apps
|
|
155
|
-
|
|
177
|
+
|
|
156
178
|
# Method 1: Garbage collector discovery (current approach)
|
|
157
179
|
gc_apps = self._discover_apps_via_gc(FastAPI)
|
|
158
180
|
logger.debug(f"🔍 TRACING: GC discovery found {len(gc_apps)} apps")
|
|
159
181
|
apps.extend(gc_apps)
|
|
160
|
-
|
|
182
|
+
|
|
161
183
|
# Method 2: Module globals discovery
|
|
162
184
|
module_apps = self._discover_apps_via_modules(FastAPI)
|
|
163
185
|
logger.debug(f"🔍 TRACING: Module discovery found {len(module_apps)} apps")
|
|
164
186
|
apps.extend(module_apps)
|
|
165
|
-
|
|
187
|
+
|
|
166
188
|
# Method 3: Stack frame inspection (new)
|
|
167
189
|
stack_apps = self._discover_apps_via_stack(FastAPI)
|
|
168
190
|
logger.debug(f"🔍 TRACING: Stack discovery found {len(stack_apps)} apps")
|
|
169
191
|
apps.extend(stack_apps)
|
|
170
|
-
|
|
192
|
+
|
|
171
193
|
# Remove duplicates while preserving order
|
|
172
194
|
unique_apps = []
|
|
173
195
|
seen_ids = set()
|
|
@@ -176,10 +198,10 @@ class FastAPIMiddlewareManager:
|
|
|
176
198
|
if app_id not in seen_ids:
|
|
177
199
|
unique_apps.append(app)
|
|
178
200
|
seen_ids.add(app_id)
|
|
179
|
-
|
|
201
|
+
|
|
180
202
|
logger.debug(f"🔍 TRACING: Discovered {len(unique_apps)} unique FastAPI apps")
|
|
181
203
|
return unique_apps
|
|
182
|
-
|
|
204
|
+
|
|
183
205
|
def _discover_apps_via_gc(self, FastAPI) -> List:
|
|
184
206
|
"""Discover FastAPI apps via garbage collector."""
|
|
185
207
|
apps = []
|
|
@@ -190,28 +212,31 @@ class FastAPIMiddlewareManager:
|
|
|
190
212
|
except Exception as e:
|
|
191
213
|
logger.debug(f"🔍 TRACING: GC discovery failed: {e}")
|
|
192
214
|
return apps
|
|
193
|
-
|
|
215
|
+
|
|
194
216
|
def _discover_apps_via_modules(self, FastAPI) -> List:
|
|
195
217
|
"""Discover FastAPI apps via module globals."""
|
|
196
218
|
apps = []
|
|
197
219
|
try:
|
|
198
220
|
import sys
|
|
221
|
+
|
|
199
222
|
for module_name, module in sys.modules.items():
|
|
200
|
-
if module and hasattr(module,
|
|
223
|
+
if module and hasattr(module, "__dict__"):
|
|
201
224
|
for attr_name, attr_value in module.__dict__.items():
|
|
202
225
|
if isinstance(attr_value, FastAPI):
|
|
203
|
-
logger.debug(
|
|
226
|
+
logger.debug(
|
|
227
|
+
f"🔍 TRACING: Found FastAPI app '{attr_name}' in module '{module_name}'"
|
|
228
|
+
)
|
|
204
229
|
apps.append(attr_value)
|
|
205
230
|
except Exception as e:
|
|
206
231
|
logger.debug(f"🔍 TRACING: Module discovery failed: {e}")
|
|
207
232
|
return apps
|
|
208
|
-
|
|
233
|
+
|
|
209
234
|
def _discover_apps_via_stack(self, FastAPI) -> List:
|
|
210
235
|
"""Discover FastAPI apps via stack frame inspection."""
|
|
211
236
|
apps = []
|
|
212
237
|
try:
|
|
213
238
|
import inspect
|
|
214
|
-
|
|
239
|
+
|
|
215
240
|
# Look through stack frames for 'app' variables
|
|
216
241
|
for frame_info in inspect.stack():
|
|
217
242
|
frame = frame_info.frame
|
|
@@ -220,7 +245,9 @@ class FastAPIMiddlewareManager:
|
|
|
220
245
|
for var_dict in [frame.f_locals, frame.f_globals]:
|
|
221
246
|
for var_name, var_value in var_dict.items():
|
|
222
247
|
if isinstance(var_value, FastAPI):
|
|
223
|
-
logger.debug(
|
|
248
|
+
logger.debug(
|
|
249
|
+
f"🔍 TRACING: Found FastAPI app '{var_name}' in stack frame"
|
|
250
|
+
)
|
|
224
251
|
apps.append(var_value)
|
|
225
252
|
finally:
|
|
226
253
|
# Avoid reference cycles
|
|
@@ -228,128 +255,147 @@ class FastAPIMiddlewareManager:
|
|
|
228
255
|
except Exception as e:
|
|
229
256
|
logger.debug(f"🔍 TRACING: Stack discovery failed: {e}")
|
|
230
257
|
return apps
|
|
231
|
-
|
|
258
|
+
|
|
232
259
|
def _add_middleware_to_app_with_retry(self, app) -> bool:
|
|
233
260
|
"""
|
|
234
261
|
Add middleware to a single app with retry logic.
|
|
235
|
-
|
|
262
|
+
|
|
236
263
|
Args:
|
|
237
264
|
app: FastAPI application instance
|
|
238
|
-
|
|
265
|
+
|
|
239
266
|
Returns:
|
|
240
267
|
bool: True if middleware was successfully added
|
|
241
268
|
"""
|
|
242
269
|
app_id = id(app)
|
|
243
|
-
app_title = getattr(app,
|
|
244
|
-
|
|
270
|
+
app_title = getattr(app, "title", "Unknown FastAPI App")
|
|
271
|
+
|
|
245
272
|
# Skip if already processed
|
|
246
273
|
if app_id in self._processed_apps:
|
|
247
274
|
logger.debug(f"🔍 TRACING: App '{app_title}' already processed, skipping")
|
|
248
275
|
return False
|
|
249
|
-
|
|
276
|
+
|
|
250
277
|
logger.debug(f"🔍 TRACING: Processing app '{app_title}' (app_{app_id})")
|
|
251
|
-
|
|
278
|
+
|
|
252
279
|
# Check if middleware already exists
|
|
253
280
|
if self._has_tracing_middleware(app):
|
|
254
|
-
logger.debug(
|
|
281
|
+
logger.debug(
|
|
282
|
+
f"🔍 TRACING: App '{app_title}' already has tracing middleware"
|
|
283
|
+
)
|
|
255
284
|
self._processed_apps.add(app_id)
|
|
256
285
|
return False
|
|
257
|
-
|
|
286
|
+
|
|
258
287
|
# Check if app can accept middleware
|
|
259
288
|
if not self._can_add_middleware(app):
|
|
260
|
-
logger.debug(
|
|
289
|
+
logger.debug(
|
|
290
|
+
f"🔍 TRACING: App '{app_title}' cannot accept middleware (already started)"
|
|
291
|
+
)
|
|
261
292
|
return False
|
|
262
|
-
|
|
293
|
+
|
|
263
294
|
# Attempt to add middleware with retry logic
|
|
264
295
|
for attempt in range(3):
|
|
265
296
|
try:
|
|
266
297
|
self._add_middleware_to_app(app)
|
|
267
|
-
logger.debug(
|
|
298
|
+
logger.debug(
|
|
299
|
+
f"🔍 TRACING: Successfully added middleware to '{app_title}' on attempt {attempt + 1}"
|
|
300
|
+
)
|
|
268
301
|
self._processed_apps.add(app_id)
|
|
269
302
|
return True
|
|
270
|
-
|
|
303
|
+
|
|
271
304
|
except Exception as e:
|
|
272
305
|
error_msg = str(e)
|
|
273
|
-
if
|
|
306
|
+
if (
|
|
307
|
+
"Cannot add middleware after an application has started"
|
|
308
|
+
in error_msg
|
|
309
|
+
):
|
|
274
310
|
if attempt < 2:
|
|
275
|
-
logger.debug(
|
|
311
|
+
logger.debug(
|
|
312
|
+
f"🔍 TRACING: App startup timing issue for '{app_title}', retrying in 50ms..."
|
|
313
|
+
)
|
|
276
314
|
time.sleep(0.05) # Brief delay
|
|
277
315
|
continue
|
|
278
316
|
else:
|
|
279
|
-
logger.debug(
|
|
317
|
+
logger.debug(
|
|
318
|
+
f"🔍 TRACING: App '{app_title}' already started after {attempt + 1} attempts"
|
|
319
|
+
)
|
|
280
320
|
return False
|
|
281
321
|
else:
|
|
282
|
-
logger.debug(
|
|
322
|
+
logger.debug(
|
|
323
|
+
f"🔍 TRACING: Failed to add middleware to '{app_title}': {e}"
|
|
324
|
+
)
|
|
283
325
|
return False
|
|
284
|
-
|
|
326
|
+
|
|
285
327
|
return False
|
|
286
|
-
|
|
328
|
+
|
|
287
329
|
def _can_add_middleware(self, app) -> bool:
|
|
288
330
|
"""
|
|
289
331
|
Check if middleware can be added to the app.
|
|
290
|
-
|
|
332
|
+
|
|
291
333
|
Args:
|
|
292
334
|
app: FastAPI application instance
|
|
293
|
-
|
|
335
|
+
|
|
294
336
|
Returns:
|
|
295
337
|
bool: True if middleware can be added
|
|
296
338
|
"""
|
|
297
339
|
try:
|
|
298
340
|
# Check for obvious signs the app has started
|
|
299
|
-
if hasattr(app,
|
|
341
|
+
if hasattr(app, "_server") and app._server is not None:
|
|
300
342
|
return False
|
|
301
|
-
|
|
343
|
+
|
|
302
344
|
# Check app state
|
|
303
|
-
if hasattr(app,
|
|
345
|
+
if hasattr(app, "state") and hasattr(app.state, "started"):
|
|
304
346
|
if app.state.started:
|
|
305
347
|
return False
|
|
306
|
-
|
|
348
|
+
|
|
307
349
|
# Try a harmless test - check if we can access middleware list
|
|
308
|
-
if hasattr(app,
|
|
350
|
+
if hasattr(app, "user_middleware"):
|
|
309
351
|
# If we can access this without error, app is likely still configurable
|
|
310
352
|
return True
|
|
311
|
-
|
|
353
|
+
|
|
312
354
|
return True
|
|
313
|
-
|
|
355
|
+
|
|
314
356
|
except Exception as e:
|
|
315
357
|
logger.debug(f"🔍 TRACING: Middleware capability check failed: {e}")
|
|
316
358
|
return False
|
|
317
|
-
|
|
359
|
+
|
|
318
360
|
def _has_tracing_middleware(self, app) -> bool:
|
|
319
361
|
"""
|
|
320
362
|
Check if the app already has our tracing middleware.
|
|
321
|
-
|
|
363
|
+
|
|
322
364
|
Args:
|
|
323
365
|
app: FastAPI application instance
|
|
324
|
-
|
|
366
|
+
|
|
325
367
|
Returns:
|
|
326
368
|
bool: True if tracing middleware is already present
|
|
327
369
|
"""
|
|
328
370
|
try:
|
|
329
|
-
if hasattr(app,
|
|
371
|
+
if hasattr(app, "user_middleware"):
|
|
330
372
|
for middleware in app.user_middleware:
|
|
331
|
-
if hasattr(middleware,
|
|
373
|
+
if hasattr(middleware, "cls"):
|
|
332
374
|
# Check for both old and new middleware names
|
|
333
375
|
middleware_name = middleware.cls.__name__
|
|
334
|
-
if middleware_name in (
|
|
376
|
+
if middleware_name in (
|
|
377
|
+
"MCPMeshTracingMiddleware",
|
|
378
|
+
"FastAPITracingMiddleware",
|
|
379
|
+
):
|
|
335
380
|
return True
|
|
336
381
|
return False
|
|
337
382
|
except Exception as e:
|
|
338
383
|
logger.debug(f"🔍 TRACING: Middleware detection failed: {e}")
|
|
339
384
|
return False
|
|
340
|
-
|
|
385
|
+
|
|
341
386
|
def _add_middleware_to_app(self, app):
|
|
342
387
|
"""Add dedicated FastAPI tracing middleware to a single FastAPI app."""
|
|
343
|
-
from ..tracing.fastapi_tracing_middleware import
|
|
344
|
-
|
|
388
|
+
from ..tracing.fastapi_tracing_middleware import \
|
|
389
|
+
FastAPITracingMiddleware
|
|
390
|
+
|
|
345
391
|
# Add the dedicated FastAPI tracing middleware
|
|
346
392
|
app.add_middleware(FastAPITracingMiddleware, logger_instance=logger)
|
|
347
393
|
logger.debug(f"🔍 TRACING: Added dedicated FastAPI tracing middleware to app")
|
|
348
|
-
|
|
394
|
+
|
|
349
395
|
def get_stats(self) -> dict:
|
|
350
396
|
"""
|
|
351
397
|
Get statistics about processed apps.
|
|
352
|
-
|
|
398
|
+
|
|
353
399
|
Returns:
|
|
354
400
|
dict: Statistics about middleware injection
|
|
355
401
|
"""
|
|
@@ -364,8 +410,20 @@ _middleware_manager: Optional[FastAPIMiddlewareManager] = None
|
|
|
364
410
|
|
|
365
411
|
|
|
366
412
|
def get_fastapi_middleware_manager() -> FastAPIMiddlewareManager:
|
|
367
|
-
"""Get or create global FastAPI middleware manager instance.
|
|
413
|
+
"""Get or create global FastAPI middleware manager instance.
|
|
414
|
+
|
|
415
|
+
Uses double-checked locking for thread-safe singleton initialization.
|
|
416
|
+
"""
|
|
368
417
|
global _middleware_manager
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
418
|
+
|
|
419
|
+
# First check without lock (fast path)
|
|
420
|
+
if _middleware_manager is not None:
|
|
421
|
+
return _middleware_manager
|
|
422
|
+
|
|
423
|
+
# Acquire lock for initialization
|
|
424
|
+
with _middleware_manager_lock:
|
|
425
|
+
# Double-check after acquiring lock
|
|
426
|
+
if _middleware_manager is None:
|
|
427
|
+
_middleware_manager = FastAPIMiddlewareManager()
|
|
428
|
+
|
|
429
|
+
return _middleware_manager
|
|
@@ -6,8 +6,8 @@ Provides clean, testable logic for determining hostnames for different purposes:
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
|
|
10
|
-
import
|
|
9
|
+
|
|
10
|
+
import mcp_mesh_core
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -20,8 +20,9 @@ class HostResolver:
|
|
|
20
20
|
"""Get external hostname for registry advertisement.
|
|
21
21
|
|
|
22
22
|
This is what other agents will use to connect to this agent.
|
|
23
|
+
Uses Rust core for consistent config resolution across all SDKs.
|
|
23
24
|
|
|
24
|
-
Priority order:
|
|
25
|
+
Priority order (handled by Rust core):
|
|
25
26
|
1. MCP_MESH_HTTP_HOST (explicit override - for production K8s deployments)
|
|
26
27
|
2. Auto-detection (socket-based external IP - for development/testing)
|
|
27
28
|
3. localhost (fallback)
|
|
@@ -29,23 +30,10 @@ class HostResolver:
|
|
|
29
30
|
Returns:
|
|
30
31
|
str: External hostname for registry advertisement
|
|
31
32
|
"""
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return explicit_host
|
|
37
|
-
|
|
38
|
-
# Priority 2: Auto-detection for development/testing
|
|
39
|
-
try:
|
|
40
|
-
auto_detected = HostResolver._auto_detect_external_ip()
|
|
41
|
-
logger.debug(f"Auto-detected external host: {auto_detected}")
|
|
42
|
-
return auto_detected
|
|
43
|
-
except Exception as e:
|
|
44
|
-
logger.warning(f"Failed to auto-detect external IP: {e}")
|
|
45
|
-
|
|
46
|
-
# Priority 3: Fallback
|
|
47
|
-
logger.debug("Using fallback external host: localhost")
|
|
48
|
-
return "localhost"
|
|
33
|
+
# Rust core handles: ENV > auto-detect > localhost
|
|
34
|
+
host = mcp_mesh_core.resolve_config_py("http_host", None)
|
|
35
|
+
logger.debug(f"Resolved external host via Rust core: {host}")
|
|
36
|
+
return host
|
|
49
37
|
|
|
50
38
|
@staticmethod
|
|
51
39
|
def get_binding_host() -> str:
|
|
@@ -58,29 +46,3 @@ class HostResolver:
|
|
|
58
46
|
str: Always "0.0.0.0" for binding to all interfaces
|
|
59
47
|
"""
|
|
60
48
|
return "0.0.0.0"
|
|
61
|
-
|
|
62
|
-
@staticmethod
|
|
63
|
-
def _auto_detect_external_ip() -> str:
|
|
64
|
-
"""Auto-detect external IP by connecting to a public DNS server.
|
|
65
|
-
|
|
66
|
-
This determines what IP address would be used for outbound connections,
|
|
67
|
-
which is typically the correct IP for other services to connect back to.
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
str: Auto-detected external IP address
|
|
71
|
-
|
|
72
|
-
Raises:
|
|
73
|
-
Exception: If auto-detection fails
|
|
74
|
-
"""
|
|
75
|
-
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
76
|
-
# Connect to a public DNS server to determine our outbound IP
|
|
77
|
-
s.connect(("8.8.8.8", 80))
|
|
78
|
-
local_ip = s.getsockname()[0]
|
|
79
|
-
|
|
80
|
-
# Validate the IP isn't localhost
|
|
81
|
-
if local_ip.startswith("127."):
|
|
82
|
-
raise Exception(
|
|
83
|
-
"Auto-detected IP is localhost, not useful for external connections"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
return local_ip
|