mcp-mesh 0.5.4__py3-none-any.whl → 0.5.6__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 +5 -2
- _mcp_mesh/engine/decorator_registry.py +95 -0
- _mcp_mesh/engine/mcp_client_proxy.py +17 -7
- _mcp_mesh/engine/unified_mcp_proxy.py +43 -40
- _mcp_mesh/pipeline/api_startup/fastapi_discovery.py +4 -167
- _mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +4 -0
- _mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +13 -0
- _mcp_mesh/pipeline/mcp_startup/__init__.py +2 -0
- _mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +306 -163
- _mcp_mesh/pipeline/mcp_startup/server_discovery.py +164 -0
- _mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +198 -160
- _mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +7 -4
- _mcp_mesh/pipeline/shared/mesh_pipeline.py +4 -0
- _mcp_mesh/shared/server_discovery.py +312 -0
- _mcp_mesh/shared/simple_shutdown.py +217 -0
- {mcp_mesh-0.5.4.dist-info → mcp_mesh-0.5.6.dist-info}/METADATA +1 -1
- {mcp_mesh-0.5.4.dist-info → mcp_mesh-0.5.6.dist-info}/RECORD +20 -18
- mesh/decorators.py +303 -36
- _mcp_mesh/engine/threading_utils.py +0 -223
- {mcp_mesh-0.5.4.dist-info → mcp_mesh-0.5.6.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.5.4.dist-info → mcp_mesh-0.5.6.dist-info}/licenses/LICENSE +0 -0
mesh/decorators.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing import Any, TypeVar
|
|
|
12
12
|
# Import from _mcp_mesh for registry and runtime integration
|
|
13
13
|
from _mcp_mesh.engine.decorator_registry import DecoratorRegistry
|
|
14
14
|
from _mcp_mesh.shared.config_resolver import ValidationRule, get_config_value
|
|
15
|
+
from _mcp_mesh.shared.simple_shutdown import start_blocking_loop_with_shutdown_support
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
@@ -24,6 +25,164 @@ _runtime_processor: Any | None = None
|
|
|
24
25
|
_SHARED_AGENT_ID: str | None = None
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def _start_uvicorn_immediately(http_host: str, http_port: int):
|
|
29
|
+
"""
|
|
30
|
+
Start basic uvicorn server immediately to prevent Python interpreter shutdown.
|
|
31
|
+
|
|
32
|
+
This prevents the DNS threading conflicts by ensuring uvicorn takes control
|
|
33
|
+
before the script ends and Python enters shutdown state.
|
|
34
|
+
"""
|
|
35
|
+
logger.info(
|
|
36
|
+
f"🎯 IMMEDIATE UVICORN: _start_uvicorn_immediately() called with host={http_host}, port={http_port}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import asyncio
|
|
41
|
+
import threading
|
|
42
|
+
import time
|
|
43
|
+
|
|
44
|
+
import uvicorn
|
|
45
|
+
from fastapi import FastAPI
|
|
46
|
+
|
|
47
|
+
logger.info(
|
|
48
|
+
"📦 IMMEDIATE UVICORN: Successfully imported uvicorn, FastAPI, threading, asyncio"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Get stored FastMCP lifespan if available
|
|
52
|
+
fastmcp_lifespan = None
|
|
53
|
+
try:
|
|
54
|
+
from _mcp_mesh.engine.decorator_registry import DecoratorRegistry
|
|
55
|
+
|
|
56
|
+
fastmcp_lifespan = DecoratorRegistry.get_fastmcp_lifespan()
|
|
57
|
+
if fastmcp_lifespan:
|
|
58
|
+
logger.info(
|
|
59
|
+
"✅ IMMEDIATE UVICORN: Found stored FastMCP lifespan, will integrate with FastAPI"
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
logger.info(
|
|
63
|
+
"🔍 IMMEDIATE UVICORN: No FastMCP lifespan found, creating basic FastAPI app"
|
|
64
|
+
)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning(f"⚠️ IMMEDIATE UVICORN: Failed to get FastMCP lifespan: {e}")
|
|
67
|
+
|
|
68
|
+
# Create FastAPI app with FastMCP lifespan if available
|
|
69
|
+
if fastmcp_lifespan:
|
|
70
|
+
app = FastAPI(title="MCP Mesh Agent (Starting)", lifespan=fastmcp_lifespan)
|
|
71
|
+
logger.info(
|
|
72
|
+
"📦 IMMEDIATE UVICORN: Created FastAPI app with FastMCP lifespan integration"
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
app = FastAPI(title="MCP Mesh Agent (Starting)")
|
|
76
|
+
logger.info("📦 IMMEDIATE UVICORN: Created minimal FastAPI app")
|
|
77
|
+
|
|
78
|
+
# Add basic health endpoint
|
|
79
|
+
@app.get("/health")
|
|
80
|
+
def health():
|
|
81
|
+
return {
|
|
82
|
+
"status": "immediate_uvicorn",
|
|
83
|
+
"message": "MCP Mesh agent started via immediate uvicorn",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@app.get("/immediate-status")
|
|
87
|
+
def immediate_status():
|
|
88
|
+
return {
|
|
89
|
+
"immediate_uvicorn": True,
|
|
90
|
+
"message": "This server was started immediately in decorator",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logger.info("📦 IMMEDIATE UVICORN: Added health endpoints")
|
|
94
|
+
|
|
95
|
+
# Determine port (0 means auto-assign)
|
|
96
|
+
port = http_port if http_port > 0 else 8080
|
|
97
|
+
|
|
98
|
+
logger.info(
|
|
99
|
+
f"🚀 IMMEDIATE UVICORN: Starting uvicorn server on {http_host}:{port}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Use uvicorn.run() for proper signal handling (enables FastAPI lifespan shutdown)
|
|
103
|
+
logger.info(
|
|
104
|
+
"⚡ IMMEDIATE UVICORN: Starting server with uvicorn.run() for proper signal handling"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Start uvicorn server in background thread (NON-daemon to keep process alive)
|
|
108
|
+
def run_server():
|
|
109
|
+
"""Run uvicorn server in background thread with proper signal handling."""
|
|
110
|
+
try:
|
|
111
|
+
logger.info(
|
|
112
|
+
f"🌟 IMMEDIATE UVICORN: Starting server on {http_host}:{port}"
|
|
113
|
+
)
|
|
114
|
+
# Use uvicorn.run() instead of Server().run() for proper signal handling
|
|
115
|
+
uvicorn.run(
|
|
116
|
+
app,
|
|
117
|
+
host=http_host,
|
|
118
|
+
port=port,
|
|
119
|
+
log_level="info",
|
|
120
|
+
timeout_graceful_shutdown=30, # Allow time for registry cleanup
|
|
121
|
+
access_log=False, # Reduce noise
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"❌ IMMEDIATE UVICORN: Server failed: {e}")
|
|
125
|
+
import traceback
|
|
126
|
+
|
|
127
|
+
logger.error(f"Server traceback: {traceback.format_exc()}")
|
|
128
|
+
|
|
129
|
+
# Start server in non-daemon thread so it can handle signals properly
|
|
130
|
+
thread = threading.Thread(target=run_server, daemon=False)
|
|
131
|
+
thread.start()
|
|
132
|
+
|
|
133
|
+
logger.info(
|
|
134
|
+
"🔒 IMMEDIATE UVICORN: Server thread started (daemon=False) - can handle signals"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Store server reference in DecoratorRegistry BEFORE starting (critical timing)
|
|
138
|
+
server_info = {
|
|
139
|
+
"app": app,
|
|
140
|
+
"server": None, # No server object with uvicorn.run()
|
|
141
|
+
"config": None, # No config object needed
|
|
142
|
+
"host": http_host,
|
|
143
|
+
"port": port,
|
|
144
|
+
"thread": thread, # Server thread (non-daemon)
|
|
145
|
+
"type": "immediate_uvicorn_running",
|
|
146
|
+
"status": "running", # Server is now running in background thread
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Import here to avoid circular imports
|
|
150
|
+
from _mcp_mesh.engine.decorator_registry import DecoratorRegistry
|
|
151
|
+
|
|
152
|
+
DecoratorRegistry.store_immediate_uvicorn_server(server_info)
|
|
153
|
+
|
|
154
|
+
logger.info(
|
|
155
|
+
"🔄 IMMEDIATE UVICORN: Server reference stored in DecoratorRegistry BEFORE pipeline starts"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Give server a moment to start
|
|
159
|
+
time.sleep(1)
|
|
160
|
+
|
|
161
|
+
logger.info(
|
|
162
|
+
f"✅ IMMEDIATE UVICORN: Uvicorn server running on {http_host}:{port} (daemon thread)"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Set up registry context for shutdown cleanup (use defaults initially)
|
|
166
|
+
import os
|
|
167
|
+
|
|
168
|
+
from _mcp_mesh.shared.simple_shutdown import _simple_shutdown_coordinator
|
|
169
|
+
|
|
170
|
+
registry_url = os.getenv("MCP_MESH_REGISTRY_URL", "http://localhost:8000")
|
|
171
|
+
agent_id = "unknown" # Will be updated by pipeline when available
|
|
172
|
+
_simple_shutdown_coordinator.set_shutdown_context(registry_url, agent_id)
|
|
173
|
+
|
|
174
|
+
# CRITICAL FIX: Keep main thread alive to prevent shutdown state
|
|
175
|
+
# This matches the working test setup pattern that prevents DNS resolution failures
|
|
176
|
+
# Uses simple shutdown with signal handlers for clean registry cleanup
|
|
177
|
+
start_blocking_loop_with_shutdown_support(thread)
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(
|
|
181
|
+
f"❌ IMMEDIATE UVICORN: Failed to start immediate uvicorn server: {e}"
|
|
182
|
+
)
|
|
183
|
+
# Don't fail decorator application - pipeline can still try to start normally
|
|
184
|
+
|
|
185
|
+
|
|
27
186
|
def _trigger_debounced_processing():
|
|
28
187
|
"""
|
|
29
188
|
Trigger debounced processing when a decorator is applied.
|
|
@@ -37,6 +196,7 @@ def _trigger_debounced_processing():
|
|
|
37
196
|
coordinator = get_debounce_coordinator()
|
|
38
197
|
coordinator.trigger_processing()
|
|
39
198
|
logger.debug("⚡ Triggered debounced processing")
|
|
199
|
+
|
|
40
200
|
except ImportError:
|
|
41
201
|
# Pipeline orchestrator not available - graceful degradation
|
|
42
202
|
logger.debug(
|
|
@@ -391,10 +551,18 @@ def agent(
|
|
|
391
551
|
if auto_run_interval < 1:
|
|
392
552
|
raise ValueError("auto_run_interval must be at least 1 second")
|
|
393
553
|
|
|
394
|
-
#
|
|
554
|
+
# Separate binding host (for uvicorn server) from external host (for registry)
|
|
395
555
|
from _mcp_mesh.shared.host_resolver import HostResolver
|
|
396
556
|
|
|
397
|
-
|
|
557
|
+
# HOST variable for uvicorn binding (documented in environment-variables.md)
|
|
558
|
+
binding_host = get_config_value(
|
|
559
|
+
"HOST",
|
|
560
|
+
default="0.0.0.0",
|
|
561
|
+
rule=ValidationRule.STRING_RULE,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# External hostname for registry advertisement (MCP_MESH_HTTP_HOST)
|
|
565
|
+
external_host = HostResolver.get_external_host()
|
|
398
566
|
|
|
399
567
|
final_http_port = get_config_value(
|
|
400
568
|
"MCP_MESH_HTTP_PORT",
|
|
@@ -449,7 +617,7 @@ def agent(
|
|
|
449
617
|
"name": name,
|
|
450
618
|
"version": version,
|
|
451
619
|
"description": description,
|
|
452
|
-
"http_host":
|
|
620
|
+
"http_host": external_host,
|
|
453
621
|
"http_port": final_http_port,
|
|
454
622
|
"enable_http": final_enable_http,
|
|
455
623
|
"namespace": final_namespace,
|
|
@@ -476,10 +644,89 @@ def agent(
|
|
|
476
644
|
except Exception as e:
|
|
477
645
|
logger.error(f"Runtime registration failed for agent {name}: {e}")
|
|
478
646
|
|
|
479
|
-
# Auto-run functionality
|
|
647
|
+
# Auto-run functionality: start uvicorn immediately to prevent Python shutdown state
|
|
480
648
|
if final_auto_run:
|
|
481
649
|
logger.info(
|
|
482
|
-
f"🚀 Auto-run enabled for agent '{name}' -
|
|
650
|
+
f"🚀 AGENT DECORATOR: Auto-run enabled for agent '{name}' - starting uvicorn immediately to prevent shutdown state"
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
# Create FastMCP lifespan before starting uvicorn for proper integration
|
|
654
|
+
fastmcp_lifespan = None
|
|
655
|
+
try:
|
|
656
|
+
# Try to create FastMCP server and extract lifespan
|
|
657
|
+
logger.info(
|
|
658
|
+
"🔍 AGENT DECORATOR: Creating FastMCP server for lifespan extraction"
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Look for FastMCP app in current module
|
|
662
|
+
import sys
|
|
663
|
+
|
|
664
|
+
current_module = sys.modules.get(target.__module__)
|
|
665
|
+
if current_module:
|
|
666
|
+
# Look for 'app' attribute (standard FastMCP pattern)
|
|
667
|
+
if hasattr(current_module, "app"):
|
|
668
|
+
fastmcp_server = current_module.app
|
|
669
|
+
logger.info(
|
|
670
|
+
f"🔍 AGENT DECORATOR: Found FastMCP server: {type(fastmcp_server)}"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Create FastMCP HTTP app with stateless transport to get lifespan
|
|
674
|
+
if hasattr(fastmcp_server, "http_app") and callable(
|
|
675
|
+
fastmcp_server.http_app
|
|
676
|
+
):
|
|
677
|
+
try:
|
|
678
|
+
fastmcp_http_app = fastmcp_server.http_app(
|
|
679
|
+
stateless_http=True, transport="streamable-http"
|
|
680
|
+
)
|
|
681
|
+
if hasattr(fastmcp_http_app, "lifespan"):
|
|
682
|
+
fastmcp_lifespan = fastmcp_http_app.lifespan
|
|
683
|
+
logger.info(
|
|
684
|
+
"✅ AGENT DECORATOR: Extracted FastMCP lifespan for FastAPI integration"
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
# Store both lifespan and HTTP app in DecoratorRegistry for uvicorn and pipeline to use
|
|
688
|
+
DecoratorRegistry.store_fastmcp_lifespan(
|
|
689
|
+
fastmcp_lifespan
|
|
690
|
+
)
|
|
691
|
+
DecoratorRegistry.store_fastmcp_http_app(
|
|
692
|
+
fastmcp_http_app
|
|
693
|
+
)
|
|
694
|
+
logger.info(
|
|
695
|
+
"✅ AGENT DECORATOR: Stored FastMCP HTTP app for proper mounting"
|
|
696
|
+
)
|
|
697
|
+
else:
|
|
698
|
+
logger.warning(
|
|
699
|
+
"⚠️ AGENT DECORATOR: FastMCP HTTP app has no lifespan attribute"
|
|
700
|
+
)
|
|
701
|
+
except Exception as e:
|
|
702
|
+
logger.warning(
|
|
703
|
+
f"⚠️ AGENT DECORATOR: Failed to create FastMCP HTTP app: {e}"
|
|
704
|
+
)
|
|
705
|
+
else:
|
|
706
|
+
logger.warning(
|
|
707
|
+
"⚠️ AGENT DECORATOR: FastMCP server has no http_app method"
|
|
708
|
+
)
|
|
709
|
+
else:
|
|
710
|
+
logger.info(
|
|
711
|
+
"🔍 AGENT DECORATOR: No FastMCP 'app' found in current module - will handle in pipeline"
|
|
712
|
+
)
|
|
713
|
+
else:
|
|
714
|
+
logger.warning(
|
|
715
|
+
"⚠️ AGENT DECORATOR: Could not access current module for FastMCP discovery"
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
except Exception as e:
|
|
719
|
+
logger.warning(
|
|
720
|
+
f"⚠️ AGENT DECORATOR: FastMCP lifespan creation failed: {e}"
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
logger.info(
|
|
724
|
+
f"🎯 AGENT DECORATOR: About to call _start_uvicorn_immediately({binding_host}, {final_http_port})"
|
|
725
|
+
)
|
|
726
|
+
# Start basic uvicorn server immediately to prevent interpreter shutdown
|
|
727
|
+
_start_uvicorn_immediately(binding_host, final_http_port)
|
|
728
|
+
logger.info(
|
|
729
|
+
"✅ AGENT DECORATOR: _start_uvicorn_immediately() call completed"
|
|
483
730
|
)
|
|
484
731
|
|
|
485
732
|
return target
|
|
@@ -494,17 +741,17 @@ def route(
|
|
|
494
741
|
) -> Callable[[T], T]:
|
|
495
742
|
"""
|
|
496
743
|
FastAPI route handler decorator for dependency injection.
|
|
497
|
-
|
|
744
|
+
|
|
498
745
|
Enables automatic dependency injection of MCP agents into FastAPI route handlers,
|
|
499
746
|
eliminating the need for manual MCP client management in backend services.
|
|
500
|
-
|
|
747
|
+
|
|
501
748
|
Args:
|
|
502
749
|
dependencies: Optional list of agent capabilities to inject (default: [])
|
|
503
750
|
**kwargs: Additional metadata for the route
|
|
504
|
-
|
|
751
|
+
|
|
505
752
|
Returns:
|
|
506
753
|
The original route handler function with dependency injection enabled
|
|
507
|
-
|
|
754
|
+
|
|
508
755
|
Example:
|
|
509
756
|
@app.post("/upload")
|
|
510
757
|
@mesh.route(dependencies=["pdf-extractor", "user-service"])
|
|
@@ -518,28 +765,30 @@ def route(
|
|
|
518
765
|
await user_service.update_profile(user_data, result)
|
|
519
766
|
return {"success": True}
|
|
520
767
|
"""
|
|
521
|
-
|
|
768
|
+
|
|
522
769
|
def decorator(target: T) -> T:
|
|
523
770
|
# Validate and process dependencies (reuse logic from tool decorator)
|
|
524
771
|
if dependencies is not None:
|
|
525
772
|
if not isinstance(dependencies, list):
|
|
526
773
|
raise ValueError("dependencies must be a list")
|
|
527
|
-
|
|
774
|
+
|
|
528
775
|
validated_dependencies = []
|
|
529
776
|
for dep in dependencies:
|
|
530
777
|
if isinstance(dep, str):
|
|
531
778
|
# Simple string dependency
|
|
532
|
-
validated_dependencies.append(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
779
|
+
validated_dependencies.append(
|
|
780
|
+
{
|
|
781
|
+
"capability": dep,
|
|
782
|
+
"tags": [],
|
|
783
|
+
}
|
|
784
|
+
)
|
|
536
785
|
elif isinstance(dep, dict):
|
|
537
786
|
# Complex dependency with metadata
|
|
538
787
|
if "capability" not in dep:
|
|
539
788
|
raise ValueError("dependency must have 'capability' field")
|
|
540
789
|
if not isinstance(dep["capability"], str):
|
|
541
790
|
raise ValueError("dependency capability must be a string")
|
|
542
|
-
|
|
791
|
+
|
|
543
792
|
# Validate optional dependency fields
|
|
544
793
|
dep_tags = dep.get("tags", [])
|
|
545
794
|
if not isinstance(dep_tags, list):
|
|
@@ -547,11 +796,11 @@ def route(
|
|
|
547
796
|
for tag in dep_tags:
|
|
548
797
|
if not isinstance(tag, str):
|
|
549
798
|
raise ValueError("all dependency tags must be strings")
|
|
550
|
-
|
|
799
|
+
|
|
551
800
|
dep_version = dep.get("version")
|
|
552
801
|
if dep_version is not None and not isinstance(dep_version, str):
|
|
553
802
|
raise ValueError("dependency version must be a string")
|
|
554
|
-
|
|
803
|
+
|
|
555
804
|
dependency_dict = {
|
|
556
805
|
"capability": dep["capability"],
|
|
557
806
|
"tags": dep_tags,
|
|
@@ -563,20 +812,20 @@ def route(
|
|
|
563
812
|
raise ValueError("dependencies must be strings or dictionaries")
|
|
564
813
|
else:
|
|
565
814
|
validated_dependencies = []
|
|
566
|
-
|
|
815
|
+
|
|
567
816
|
# Build route metadata
|
|
568
817
|
metadata = {
|
|
569
818
|
"dependencies": validated_dependencies,
|
|
570
819
|
"description": getattr(target, "__doc__", None),
|
|
571
820
|
**kwargs,
|
|
572
821
|
}
|
|
573
|
-
|
|
822
|
+
|
|
574
823
|
# Store metadata on function
|
|
575
824
|
target._mesh_route_metadata = metadata
|
|
576
|
-
|
|
825
|
+
|
|
577
826
|
# Register with DecoratorRegistry using custom decorator type
|
|
578
827
|
DecoratorRegistry.register_custom_decorator("mesh_route", target, metadata)
|
|
579
|
-
|
|
828
|
+
|
|
580
829
|
# Try to add tracing middleware to any FastAPI apps we can find immediately
|
|
581
830
|
# This ensures middleware is added before the app starts
|
|
582
831
|
try:
|
|
@@ -584,20 +833,22 @@ def route(
|
|
|
584
833
|
except Exception as e:
|
|
585
834
|
# Don't fail decorator application due to middleware issues
|
|
586
835
|
logger.debug(f"Failed to add immediate tracing middleware: {e}")
|
|
587
|
-
|
|
836
|
+
|
|
588
837
|
logger.debug(
|
|
589
838
|
f"🔍 Route '{target.__name__}' registered with {len(validated_dependencies)} dependencies"
|
|
590
839
|
)
|
|
591
|
-
|
|
840
|
+
|
|
592
841
|
try:
|
|
593
842
|
# Import here to avoid circular imports
|
|
594
843
|
from _mcp_mesh.engine.dependency_injector import get_global_injector
|
|
595
844
|
|
|
596
|
-
# Extract dependency names for injector
|
|
845
|
+
# Extract dependency names for injector
|
|
597
846
|
dependency_names = [dep["capability"] for dep in validated_dependencies]
|
|
598
847
|
|
|
599
848
|
# Log the original function pointer
|
|
600
|
-
logger.debug(
|
|
849
|
+
logger.debug(
|
|
850
|
+
f"🔸 ORIGINAL route function pointer: {target} at {hex(id(target))}"
|
|
851
|
+
)
|
|
601
852
|
|
|
602
853
|
injector = get_global_injector()
|
|
603
854
|
wrapped = injector.create_injection_wrapper(target, dependency_names)
|
|
@@ -612,12 +863,14 @@ def route(
|
|
|
612
863
|
|
|
613
864
|
# Store the wrapper on the original function for reference
|
|
614
865
|
target._mesh_injection_wrapper = wrapped
|
|
615
|
-
|
|
866
|
+
|
|
616
867
|
# Also store a flag on the wrapper itself so route integration can detect it
|
|
617
868
|
wrapped._mesh_is_injection_wrapper = True
|
|
618
869
|
|
|
619
870
|
# Return the wrapped function - FastAPI will register this wrapper when it runs
|
|
620
|
-
logger.debug(
|
|
871
|
+
logger.debug(
|
|
872
|
+
f"✅ Returning injection wrapper for route '{target.__name__}'"
|
|
873
|
+
)
|
|
621
874
|
logger.debug(f"🔹 Returning WRAPPER: {wrapped} at {hex(id(wrapped))}")
|
|
622
875
|
|
|
623
876
|
# Trigger debounced processing before returning
|
|
@@ -629,32 +882,36 @@ def route(
|
|
|
629
882
|
logger.error(
|
|
630
883
|
f"Route dependency injection setup failed for {target.__name__}: {e}"
|
|
631
884
|
)
|
|
632
|
-
|
|
885
|
+
|
|
633
886
|
# Fallback: return original function and trigger processing
|
|
634
887
|
_trigger_debounced_processing()
|
|
635
888
|
return target
|
|
636
|
-
|
|
889
|
+
|
|
637
890
|
return decorator
|
|
638
891
|
|
|
639
892
|
|
|
640
893
|
def _add_tracing_middleware_immediately():
|
|
641
894
|
"""
|
|
642
895
|
Request tracing middleware injection using monkey-patch approach.
|
|
643
|
-
|
|
896
|
+
|
|
644
897
|
This sets up automatic middleware injection for both existing and future
|
|
645
898
|
FastAPI apps, eliminating timing issues with app startup/lifespan.
|
|
646
899
|
"""
|
|
647
900
|
try:
|
|
648
|
-
from _mcp_mesh.shared.fastapi_middleware_manager import
|
|
649
|
-
|
|
901
|
+
from _mcp_mesh.shared.fastapi_middleware_manager import (
|
|
902
|
+
get_fastapi_middleware_manager,
|
|
903
|
+
)
|
|
904
|
+
|
|
650
905
|
manager = get_fastapi_middleware_manager()
|
|
651
906
|
success = manager.request_middleware_injection()
|
|
652
|
-
|
|
907
|
+
|
|
653
908
|
if success:
|
|
654
|
-
logger.debug(
|
|
909
|
+
logger.debug(
|
|
910
|
+
"🔍 TRACING: Middleware injection setup completed (monkey-patch + discovery)"
|
|
911
|
+
)
|
|
655
912
|
else:
|
|
656
913
|
logger.debug("🔍 TRACING: Middleware injection setup failed")
|
|
657
|
-
|
|
914
|
+
|
|
658
915
|
except Exception as e:
|
|
659
916
|
# Never fail decorator application
|
|
660
917
|
logger.debug(f"🔍 TRACING: Middleware injection setup failed: {e}")
|
|
@@ -662,3 +919,13 @@ def _add_tracing_middleware_immediately():
|
|
|
662
919
|
|
|
663
920
|
# Middleware injection is now handled by FastAPIMiddlewareManager
|
|
664
921
|
# in _mcp_mesh.shared.fastapi_middleware_manager
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
# Graceful shutdown functions have been moved to _mcp_mesh.shared.graceful_shutdown_manager
|
|
925
|
+
# This maintains backward compatibility for existing pipeline code
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def set_shutdown_context(context: dict[str, Any]):
|
|
929
|
+
"""Set context for graceful shutdown (called from pipeline)."""
|
|
930
|
+
# Delegate to the shared graceful shutdown manager
|
|
931
|
+
set_global_shutdown_context(context)
|