10
10
mcp = FastMCP ("roslyn-analyzers-dev" )
11
11
BASE_DIR = Path (__file__ ).resolve ().parents [2 ] # repo root (../../)
12
12
DEFAULT_TIMEOUT = 900 # 15 minutes for long builds/tests
13
+ STATE_DIR = BASE_DIR / ".mcp_state"
14
+ STATE_DIR .mkdir (exist_ok = True )
15
+ RESTORE_SENTINEL = STATE_DIR / "restored.ok"
16
+
17
+ def _ensure_restored () -> bool :
18
+ """
19
+ Ensure NuGet restore (and initial SDK warmup) has happened once per server session.
20
+ Returns True if this call performed the restore (i.e., cold start), else False.
21
+ """
22
+ if RESTORE_SENTINEL .exists ():
23
+ return False # already warm
24
+
25
+ # Quick SDK warmup to avoid first-time experience overhead
26
+ _run (["dotnet" , "--info" ], timeout = 60 )
27
+
28
+ # Restore the whole solution once so later commands can use --no-restore
29
+ _run (["dotnet" , "restore" , "Philips.CodeAnalysis.sln" ], timeout = 600 )
30
+
31
+ # Optional: compile the test project once, so later we can use --no-build
32
+ _run ([
33
+ "dotnet" , "build" ,
34
+ "Philips.CodeAnalysis.Test/Philips.CodeAnalysis.Test.csproj" ,
35
+ "--configuration" , "Release" ,
36
+ "--no-restore"
37
+ ], timeout = 600 )
38
+
39
+ RESTORE_SENTINEL .write_text ("ok" , encoding = "utf-8" )
40
+ return True
13
41
14
42
def _run (cmd : list [str ], * , timeout : int = DEFAULT_TIMEOUT ) -> tuple [int , str ]:
15
43
if (
@@ -71,6 +99,7 @@ def search_helpers() -> Dict[str, Any]:
71
99
@mcp .tool
72
100
def build_strict () -> Dict [str , Any ]:
73
101
"""dotnet build solution with warnings as errors."""
102
+ _ensure_restored ()
74
103
_run (["dotnet" , "clean" , "Philips.CodeAnalysis.sln" ])
75
104
rc , out = _run ([
76
105
"dotnet" , "build" , "Philips.CodeAnalysis.sln" ,
@@ -82,12 +111,25 @@ def build_strict() -> Dict[str, Any]:
82
111
@mcp .tool
83
112
def run_tests () -> Dict [str , Any ]:
84
113
"""Run tests against main test project."""
85
- # Use 120s timeout to accommodate both building and testing from clean state
86
- # Tests take ~49s including build when starting clean, so 120s provides adequate buffer
87
- rc , out = _run ([
114
+ # Warmup/restore on the very first call; returns True if we just did it now.
115
+ did_restore_now = _ensure_restored ()
116
+
117
+ # If we just restored/built, tests will be quick — and we can skip restore/build
118
+ cmd = [
88
119
"dotnet" , "test" , "Philips.CodeAnalysis.Test/Philips.CodeAnalysis.Test.csproj" ,
89
- "--configuration" , "Release" , "--logger" , "trx;LogFileName=test-results.trx"
90
- ], timeout = 120 )
120
+ "--configuration" , "Release" ,
121
+ "--logger" , "trx;LogFileName=test-results.trx" ,
122
+ "--no-restore"
123
+ ]
124
+ # If the initial build above succeeded, we can also skip building:
125
+ test_bin = BASE_DIR / "Philips.CodeAnalysis.Test" / "bin" / "Release"
126
+ if test_bin .exists ():
127
+ cmd .append ("--no-build" )
128
+
129
+ # Give a bigger timeout only on the first ever run, else be tight
130
+ timeout = 600 if did_restore_now else 180
131
+
132
+ rc , out = _run (cmd , timeout = timeout )
91
133
92
134
# Parse test results from output
93
135
test_results = {"passed" : 0 , "failed" : 0 , "skipped" : 0 , "total" : 0 , "duration" : "" }
0 commit comments