#
tokens: 42012/50000 44/44 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── build-on-windows.bat
├── Build-OnWindows.ps1
├── claude-mcp-config-wsl.json
├── CLAUDE.md
├── DotNetFrameworkMCP.sln
├── LICENSE
├── mcp-config-example.json
├── ProjectPlan.md
├── README.md
├── run-tcp-server-vs2022.bat
├── run-tcp-server.bat
├── src
│   └── DotNetFrameworkMCP.Server
│       ├── appsettings.json
│       ├── Configuration
│       │   └── McpServerConfiguration.cs
│       ├── DotNetFrameworkMCP.Server.csproj
│       ├── Executors
│       │   ├── BaseTestExecutor.cs
│       │   ├── DotNetBuildExecutor.cs
│       │   ├── DotNetTestExecutor.cs
│       │   ├── ExecutorFactory.cs
│       │   ├── IBuildExecutor.cs
│       │   ├── ITestExecutor.cs
│       │   ├── MSBuildExecutor.cs
│       │   └── VSTestExecutor.cs
│       ├── Models
│       │   ├── AnalyzeSolutionRequest.cs
│       │   ├── BuildProjectRequest.cs
│       │   ├── RunProjectRequest.cs
│       │   └── RunTestsRequest.cs
│       ├── Program.cs
│       ├── Protocol
│       │   ├── McpMessage.cs
│       │   └── ToolDefinition.cs
│       ├── Services
│       │   ├── ProcessBasedBuildService.cs
│       │   ├── TcpMcpServer.cs
│       │   └── TestRunnerService.cs
│       └── Tools
│           ├── BuildProjectHandler.cs
│           ├── IToolHandler.cs
│           └── RunTestsHandler.cs
├── start-tcp-server.bat
├── test-dotnet-cli.bat
├── test-messages.json
├── test-tcp-server.sh
├── tests
│   └── DotNetFrameworkMCP.Server.Tests
│       ├── DotNetFrameworkMCP.Server.Tests.csproj
│       ├── Executors
│       │   ├── ExecutorFactoryTests.cs
│       │   └── MSBuildExecutorTests.cs
│       ├── GlobalUsings.cs
│       ├── Services
│       │   ├── ProcessBasedBuildServiceTests.cs
│       │   └── TestRunnerServiceTests.cs
│       └── Tools
│           ├── BuildProjectHandlerTests.cs
│           └── RunTestsHandlerTests.cs
└── wsl-mcp-bridge.sh
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | ## Ignore Visual Studio temporary files, build results, and
  2 | ## files generated by popular Visual Studio add-ons.
  3 | ##
  4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
  5 | 
  6 | # User-specific files
  7 | *.rsuser
  8 | *.suo
  9 | *.user
 10 | *.userosscache
 11 | *.sln.docstates
 12 | 
 13 | # User-specific files (MonoDevelop/Xamarin Studio)
 14 | *.userprefs
 15 | 
 16 | # Mono auto generated files
 17 | mono_crash.*
 18 | 
 19 | # Build results
 20 | [Dd]ebug/
 21 | [Dd]ebugPublic/
 22 | [Rr]elease/
 23 | [Rr]eleases/
 24 | x64/
 25 | x86/
 26 | [Ww][Ii][Nn]32/
 27 | [Aa][Rr][Mm]/
 28 | [Aa][Rr][Mm]64/
 29 | bld/
 30 | [Bb]in/
 31 | [Oo]bj/
 32 | [Ll]og/
 33 | [Ll]ogs/
 34 | 
 35 | # Visual Studio 2015/2017 cache/options directory
 36 | .vs/
 37 | # Uncomment if you have tasks that create the project's static files in wwwroot
 38 | #wwwroot/
 39 | 
 40 | # Visual Studio 2017 auto generated files
 41 | Generated\ Files/
 42 | 
 43 | # MSTest test Results
 44 | [Tt]est[Rr]esult*/
 45 | [Bb]uild[Ll]og.*
 46 | 
 47 | # NUnit
 48 | *.VisualState.xml
 49 | TestResult.xml
 50 | nunit-*.xml
 51 | 
 52 | # Build Results of an ATL Project
 53 | [Dd]ebugPS/
 54 | [Rr]eleasePS/
 55 | dlldata.c
 56 | 
 57 | # Benchmark Results
 58 | BenchmarkDotNet.Artifacts/
 59 | 
 60 | # .NET
 61 | project.lock.json
 62 | project.fragment.lock.json
 63 | artifacts/
 64 | 
 65 | # ASP.NET Scaffolding
 66 | ScaffoldingReadMe.txt
 67 | 
 68 | # StyleCop
 69 | StyleCopReport.xml
 70 | 
 71 | # Files built by Visual Studio
 72 | *_i.c
 73 | *_p.c
 74 | *_h.h
 75 | *.ilk
 76 | *.meta
 77 | *.obj
 78 | *.iobj
 79 | *.pch
 80 | *.pdb
 81 | *.ipdb
 82 | *.pgc
 83 | *.pgd
 84 | *.rsp
 85 | *.sbr
 86 | *.tlb
 87 | *.tli
 88 | *.tlh
 89 | *.tmp
 90 | *.tmp_proj
 91 | *_wpftmp.csproj
 92 | *.log
 93 | *.tlog
 94 | *.vspscc
 95 | *.vssscc
 96 | .builds
 97 | *.pidb
 98 | *.svclog
 99 | *.scc
100 | 
101 | # Chutzpah Test files
102 | _Chutzpah*
103 | 
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 | 
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 | 
121 | # Visual Studio Trace Files
122 | *.e2e
123 | 
124 | # TFS 2012 Local Workspace
125 | $tf/
126 | 
127 | # Guidance Automation Toolkit
128 | *.gpState
129 | 
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 | 
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 | 
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 | 
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 | 
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 | 
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 | 
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 | 
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 | 
163 | # Web workbench (sass)
164 | .sass-cache/
165 | 
166 | # Installshield output folder
167 | [Ee]xpress/
168 | 
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 | 
179 | # Click-Once directory
180 | publish/
181 | 
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 | 
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 | 
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 | 
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 | 
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 | 
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 | 
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 | 
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 | 
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 | 
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 | 
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 | 
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 | 
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 | 
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 | 
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 | 
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 | 
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 | 
288 | # Visual Studio 6 build log
289 | *.plg
290 | 
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 | 
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 | 
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 | 
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 | 
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 | 
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 | 
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 | 
320 | # FAKE - F# Make
321 | .fake/
322 | 
323 | # CodeRush personal settings
324 | .cr/personal
325 | 
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 | 
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 | 
334 | # Tabs Studio
335 | *.tss
336 | 
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 | 
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 | 
346 | # OpenCover UI analysis results
347 | OpenCover/
348 | 
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 | 
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 | 
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 | 
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 | 
361 | # Local History for Visual Studio
362 | .localhistory/
363 | 
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 | 
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 | 
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 | 
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 | 
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 | 
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 | 
387 | # Local History for Visual Studio Code
388 | .history/
389 | 
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 | 
397 | # JetBrains Rider
398 | *.sln.iml
399 | .idea/
400 | 
401 | # macOS
402 | .DS_Store
403 | 
404 | # Published output
405 | publish/
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Claude Development Guidelines
  2 | 
  3 | ## Project Information
  4 | 
  5 | This is a .NET Framework MCP (Model Context Protocol) server for Windows that enables building, testing, and running .NET Framework projects remotely.
  6 | 
  7 | ## Testing Framework
  8 | 
  9 | **Use NUnit for all tests** - This project uses NUnit, not xUnit or MSTest.
 10 | 
 11 | ### Test Structure:
 12 | ```csharp
 13 | using NUnit.Framework;
 14 | 
 15 | [TestFixture]
 16 | public class MyServiceTests
 17 | {
 18 |     [SetUp]
 19 |     public void SetUp()
 20 |     {
 21 |         // Initialize test dependencies
 22 |     }
 23 | 
 24 |     [TearDown] 
 25 |     public void TearDown()
 26 |     {
 27 |         // Cleanup resources
 28 |     }
 29 | 
 30 |     [Test]
 31 |     public void MyMethod_WithValidInput_ReturnsExpectedResult()
 32 |     {
 33 |         // Arrange
 34 |         // Act
 35 |         // Assert
 36 |         Assert.That(result, Is.EqualTo(expected));
 37 |     }
 38 | 
 39 |     [TestCase("value1", "expected1")]
 40 |     [TestCase("value2", "expected2")]
 41 |     public void MyMethod_WithDifferentInputs_ReturnsExpectedResults(string input, string expected)
 42 |     {
 43 |         // Test implementation
 44 |     }
 45 | }
 46 | ```
 47 | 
 48 | ### NUnit Assertions:
 49 | - Use `Assert.That(actual, Is.EqualTo(expected))` instead of `Assert.Equal()`
 50 | - Use `Assert.That(collection, Has.Count.EqualTo(3))` for collections
 51 | - Use `Assert.That(text, Does.Contain("substring"))` for string checks
 52 | - Use `Times.Once` with Moq for verifications
 53 | 
 54 | ## Build Commands
 55 | 
 56 | ### Development:
 57 | ```bash
 58 | # Build the server
 59 | dotnet build src/DotNetFrameworkMCP.Server
 60 | 
 61 | # Run tests
 62 | dotnet test
 63 | 
 64 | # Run with dotnet CLI enabled
 65 | set MCPSERVER__UseDotNetCli=true
 66 | dotnet run --project src/DotNetFrameworkMCP.Server -- --port 3001
 67 | ```
 68 | 
 69 | ### IMPORTANT: Pre-Commit Workflow
 70 | **ALWAYS build and test before committing!**
 71 | 
 72 | ```bash
 73 | # 1. Build to ensure no compilation errors
 74 | dotnet build
 75 | 
 76 | # 2. Run tests to ensure functionality works
 77 | dotnet test
 78 | 
 79 | # 3. Only commit if both succeed
 80 | git add -A
 81 | git commit -m "Your commit message"
 82 | ```
 83 | 
 84 | If build or tests fail, fix the issues before committing. Never commit broken code.
 85 | 
 86 | ### Production:
 87 | ```bash
 88 | # Build self-contained executable
 89 | build-on-windows.bat
 90 | 
 91 | # Run the compiled executable
 92 | run-tcp-server.bat
 93 | ```
 94 | 
 95 | ## Architecture Notes
 96 | 
 97 | - Uses Strategy pattern for build/test executors
 98 | - MSBuild vs dotnet CLI selection via configuration
 99 | - Factory pattern for executor creation
100 | - Services delegate to executors, no conditional logic
101 | - All executors implement `IBuildExecutor` or `ITestExecutor`
102 | 
103 | ## Configuration
104 | 
105 | Key settings in `appsettings.json`:
106 | - `UseDotNetCli`: Switch between MSBuild and dotnet CLI
107 | - `PreferredVSVersion`: VS version preference for MSBuild
108 | - `BuildTimeout`: Build operation timeout
109 | - `TestTimeout`: Test operation timeout
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------

```csharp
1 | global using NUnit.Framework;
```

--------------------------------------------------------------------------------
/claude-mcp-config-wsl.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "dotnet-framework": {
 4 |       "command": "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework/wsl-mcp-bridge.sh",
 5 |       "env": {
 6 |         "MCP_DEBUG": "true"
 7 |       }
 8 |     }
 9 |   }
10 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Tools/IToolHandler.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json;
 2 | using DotNetFrameworkMCP.Server.Protocol;
 3 | 
 4 | namespace DotNetFrameworkMCP.Server.Tools;
 5 | 
 6 | public interface IToolHandler
 7 | {
 8 |     string Name { get; }
 9 |     ToolDefinition GetDefinition();
10 |     Task<object> ExecuteAsync(JsonElement arguments);
11 | }
```

--------------------------------------------------------------------------------
/mcp-config-example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "dotnet-framework": {
 4 |       "command": "dotnet",
 5 |       "args": [
 6 |         "run",
 7 |         "--project",
 8 |         "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework/src/DotNetFrameworkMCP.Server"
 9 |       ],
10 |       "env": {
11 |         "MCPSERVER_EnableDetailedLogging": "true"
12 |       }
13 |     }
14 |   }
15 | }
```

--------------------------------------------------------------------------------
/test-dotnet-cli.bat:
--------------------------------------------------------------------------------

```
 1 | @echo off
 2 | echo Testing dotnet CLI support...
 3 | echo.
 4 | echo Starting server with dotnet CLI enabled...
 5 | 
 6 | REM Build the server first
 7 | cd src\DotNetFrameworkMCP.Server
 8 | dotnet build
 9 | 
10 | REM Run with dotnet CLI enabled
11 | set MCPSERVER__UseDotNetCli=true
12 | set MCPSERVER__EnableDetailedLogging=true
13 | 
14 | echo.
15 | echo Running server with dotnet CLI support enabled...
16 | dotnet run -- --port 3001
```

--------------------------------------------------------------------------------
/run-tcp-server.bat:
--------------------------------------------------------------------------------

```
 1 | @echo off
 2 | echo Starting .NET Framework MCP Server in TCP mode...
 3 | echo Server will listen on port 3001
 4 | echo Press Ctrl+C to stop
 5 | echo.
 6 | 
 7 | cd /d "%~dp0"
 8 | 
 9 | if exist "publish\DotNetFrameworkMCP.Server.exe" (
10 |     publish\DotNetFrameworkMCP.Server.exe --port 3001
11 | ) else (
12 |     echo ERROR: Server executable not found!
13 |     echo Please run build-on-windows.bat first to build the project.
14 |     pause
15 |     exit /b 1
16 | )
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/appsettings.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "McpServer": {
 3 |     "MsBuildPath": "auto",
 4 |     "DefaultConfiguration": "Debug",
 5 |     "DefaultPlatform": "Any CPU",
 6 |     "TestTimeout": 300000,
 7 |     "BuildTimeout": 1200000,
 8 |     "EnableDetailedLogging": true,
 9 |     "PreferredVSVersion": "2022",
10 |     "UseDotNetCli": false,
11 |     "DotNetPath": "dotnet"
12 |   },
13 |   "Logging": {
14 |     "LogLevel": {
15 |       "Default": "Information",
16 |       "DotNetFrameworkMCP.Server": "Debug"
17 |     }
18 |   }
19 | }
```

--------------------------------------------------------------------------------
/start-tcp-server.bat:
--------------------------------------------------------------------------------

```
 1 | @echo off
 2 | echo Starting .NET Framework MCP Server in TCP mode...
 3 | echo Server will listen on port 3001
 4 | echo Press Ctrl+C to stop
 5 | echo.
 6 | 
 7 | cd /d "%~dp0"
 8 | 
 9 | if exist "publish\DotNetFrameworkMCP.Server.exe" (
10 |     echo Using compiled executable...
11 |     publish\DotNetFrameworkMCP.Server.exe --port 3001
12 | ) else (
13 |     echo Using dotnet run (building if needed)...
14 |     dotnet run --project "src\DotNetFrameworkMCP.Server" -- --port 3001
15 | )
16 | 
17 | pause
```

--------------------------------------------------------------------------------
/wsl-mcp-bridge.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # MCP Bridge Script for WSL
 4 | # This script connects to the Windows TCP MCP server from WSL
 5 | 
 6 | # Configuration
 7 | WINDOWS_HOST="localhost"  # Or use the actual Windows IP if needed
 8 | MCP_PORT="3001"
 9 | 
10 | # Check if netcat is available
11 | if ! command -v nc &> /dev/null; then
12 |     echo "Error: netcat (nc) is required but not installed." >&2
13 |     echo "Install it with: sudo apt install netcat-openbsd" >&2
14 |     exit 1
15 | fi
16 | 
17 | # Connect to the Windows MCP server via TCP
18 | exec nc "$WINDOWS_HOST" "$MCP_PORT"
```

--------------------------------------------------------------------------------
/test-messages.json:
--------------------------------------------------------------------------------

```json
 1 | // Example MCP messages to test the server
 2 | 
 3 | // 1. Initialize the server
 4 | {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}
 5 | 
 6 | // 2. List available tools
 7 | {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
 8 | 
 9 | // 3. Call the build_project tool
10 | {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "build_project", "arguments": {"path": "./src/DotNetFrameworkMCP.Server/DotNetFrameworkMCP.Server.csproj", "configuration": "Debug", "restore": true}}}
```

--------------------------------------------------------------------------------
/build-on-windows.bat:
--------------------------------------------------------------------------------

```
 1 | @echo off
 2 | echo Building .NET Framework MCP Server...
 3 | echo.
 4 | 
 5 | cd /d "%~dp0"
 6 | 
 7 | echo Restoring packages...
 8 | dotnet restore
 9 | 
10 | echo.
11 | echo Building Release configuration...
12 | dotnet build -c Release
13 | 
14 | echo.
15 | echo Publishing self-contained executable...
16 | dotnet publish src\DotNetFrameworkMCP.Server -c Release -r win-x64 --self-contained -o publish
17 | 
18 | echo.
19 | echo Build complete! 
20 | echo Executable location: publish\DotNetFrameworkMCP.Server.exe
21 | echo.
22 | echo You can now run the server with:
23 | echo   publish\DotNetFrameworkMCP.Server.exe --tcp --port 3001
24 | echo.
25 | pause
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Models/RunProjectRequest.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Models;
 4 | 
 5 | public class RunProjectRequest
 6 | {
 7 |     [JsonPropertyName("path")]
 8 |     public string Path { get; set; } = string.Empty;
 9 | 
10 |     [JsonPropertyName("args")]
11 |     public List<string>? Args { get; set; }
12 | 
13 |     [JsonPropertyName("workingDirectory")]
14 |     public string? WorkingDirectory { get; set; }
15 | }
16 | 
17 | public class RunResult
18 | {
19 |     [JsonPropertyName("exitCode")]
20 |     public int ExitCode { get; set; }
21 | 
22 |     [JsonPropertyName("output")]
23 |     public string Output { get; set; } = string.Empty;
24 | 
25 |     [JsonPropertyName("error")]
26 |     public string Error { get; set; } = string.Empty;
27 | 
28 |     [JsonPropertyName("duration")]
29 |     public double Duration { get; set; }
30 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Configuration/McpServerConfiguration.cs:
--------------------------------------------------------------------------------

```csharp
 1 | namespace DotNetFrameworkMCP.Server.Configuration;
 2 | 
 3 | public class McpServerConfiguration
 4 | {
 5 |     public string MsBuildPath { get; set; } = "auto";
 6 |     public string DefaultConfiguration { get; set; } = "Debug";
 7 |     public string DefaultPlatform { get; set; } = "Any CPU";
 8 |     public int TestTimeout { get; set; } = 300000;
 9 |     public int BuildTimeout { get; set; } = 1200000; // 20 minutes for large solutions
10 |     public bool EnableDetailedLogging { get; set; } = false;
11 |     public string PreferredVSVersion { get; set; } = "2022"; // Options: "2022", "2019", "auto"
12 |     public bool UseDotNetCli { get; set; } = false; // Use dotnet CLI instead of MSBuild
13 |     public string DotNetPath { get; set; } = "dotnet"; // Path to dotnet CLI executable
14 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/ITestExecutor.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Models;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Executors;
 4 | 
 5 | /// <summary>
 6 | /// Interface for test execution strategies
 7 | /// </summary>
 8 | public interface ITestExecutor
 9 | {
10 |     /// <summary>
11 |     /// Executes tests for the specified project
12 |     /// </summary>
13 |     /// <param name="projectPath">Path to the test project</param>
14 |     /// <param name="filter">Optional test filter expression</param>
15 |     /// <param name="verbose">Whether to enable verbose output</param>
16 |     /// <param name="cancellationToken">Cancellation token</param>
17 |     /// <returns>Test result with total, passed, failed, skipped counts and details</returns>
18 |     Task<TestResult> ExecuteTestsAsync(
19 |         string projectPath,
20 |         string? filter,
21 |         bool verbose,
22 |         CancellationToken cancellationToken = default);
23 | }
```

--------------------------------------------------------------------------------
/test-tcp-server.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | echo "Testing TCP MCP Server..."
 4 | 
 5 | # Start the server in background
 6 | cd "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework"
 7 | dotnet run --project src/DotNetFrameworkMCP.Server -- --port 3001 &
 8 | SERVER_PID=$!
 9 | 
10 | # Wait a moment for server to start
11 | sleep 2
12 | 
13 | echo "Server started with PID $SERVER_PID"
14 | echo "Testing connection..."
15 | 
16 | # Test the server
17 | {
18 |     echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}'
19 |     sleep 0.1
20 |     echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'
21 |     sleep 0.1
22 |     echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "build_project", "arguments": {"path": "./src/DotNetFrameworkMCP.Server/DotNetFrameworkMCP.Server.csproj", "configuration": "Debug", "restore": true}}}'
23 |     sleep 1
24 | } | nc localhost 3001
25 | 
26 | echo "Test completed. Stopping server..."
27 | kill $SERVER_PID
28 | wait $SERVER_PID 2>/dev/null
29 | echo "Server stopped."
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/IBuildExecutor.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Models;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Executors;
 4 | 
 5 | /// <summary>
 6 | /// Interface for build execution strategies
 7 | /// </summary>
 8 | public interface IBuildExecutor
 9 | {
10 |     /// <summary>
11 |     /// Executes a build for the specified project
12 |     /// </summary>
13 |     /// <param name="projectPath">Path to the project or solution file</param>
14 |     /// <param name="configuration">Build configuration (e.g., Debug, Release)</param>
15 |     /// <param name="platform">Target platform (e.g., Any CPU, x86, x64)</param>
16 |     /// <param name="restore">Whether to restore NuGet packages</param>
17 |     /// <param name="cancellationToken">Cancellation token</param>
18 |     /// <returns>Build result with success status, errors, warnings, and output</returns>
19 |     Task<BuildResult> ExecuteBuildAsync(
20 |         string projectPath,
21 |         string configuration,
22 |         string platform,
23 |         bool restore,
24 |         CancellationToken cancellationToken = default);
25 | }
```

--------------------------------------------------------------------------------
/Build-OnWindows.ps1:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env pwsh
 2 | 
 3 | Write-Host "Building .NET Framework MCP Server..." -ForegroundColor Green
 4 | Write-Host ""
 5 | 
 6 | # Change to script directory
 7 | Set-Location $PSScriptRoot
 8 | 
 9 | Write-Host "Restoring packages..." -ForegroundColor Yellow
10 | dotnet restore
11 | 
12 | Write-Host ""
13 | Write-Host "Building Release configuration..." -ForegroundColor Yellow
14 | dotnet build -c Release
15 | 
16 | if ($LASTEXITCODE -ne 0) {
17 |     Write-Host "Build failed!" -ForegroundColor Red
18 |     exit 1
19 | }
20 | 
21 | Write-Host ""
22 | Write-Host "Publishing self-contained executable..." -ForegroundColor Yellow
23 | dotnet publish src\DotNetFrameworkMCP.Server -c Release -r win-x64 --self-contained -o publish
24 | 
25 | if ($LASTEXITCODE -ne 0) {
26 |     Write-Host "Publish failed!" -ForegroundColor Red
27 |     exit 1
28 | }
29 | 
30 | Write-Host ""
31 | Write-Host "Build complete!" -ForegroundColor Green
32 | Write-Host "Executable location: publish\DotNetFrameworkMCP.Server.exe" -ForegroundColor Cyan
33 | Write-Host ""
34 | Write-Host "You can now run the server with:" -ForegroundColor Yellow
35 | Write-Host "  .\publish\DotNetFrameworkMCP.Server.exe --tcp --port 3001" -ForegroundColor White
36 | Write-Host ""
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/ExecutorFactory.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Configuration;
 2 | using Microsoft.Extensions.DependencyInjection;
 3 | using Microsoft.Extensions.Options;
 4 | 
 5 | namespace DotNetFrameworkMCP.Server.Executors;
 6 | 
 7 | /// <summary>
 8 | /// Factory for creating build and test executors based on configuration
 9 | /// </summary>
10 | public interface IExecutorFactory
11 | {
12 |     IBuildExecutor CreateBuildExecutor();
13 |     ITestExecutor CreateTestExecutor();
14 | }
15 | 
16 | public class ExecutorFactory : IExecutorFactory
17 | {
18 |     private readonly IServiceProvider _serviceProvider;
19 |     private readonly McpServerConfiguration _configuration;
20 | 
21 |     public ExecutorFactory(IServiceProvider serviceProvider, IOptions<McpServerConfiguration> configuration)
22 |     {
23 |         _serviceProvider = serviceProvider;
24 |         _configuration = configuration.Value;
25 |     }
26 | 
27 |     public IBuildExecutor CreateBuildExecutor()
28 |     {
29 |         return _configuration.UseDotNetCli
30 |             ? _serviceProvider.GetRequiredService<DotNetBuildExecutor>()
31 |             : _serviceProvider.GetRequiredService<MSBuildExecutor>();
32 |     }
33 | 
34 |     public ITestExecutor CreateTestExecutor()
35 |     {
36 |         return _configuration.UseDotNetCli
37 |             ? _serviceProvider.GetRequiredService<DotNetTestExecutor>()
38 |             : _serviceProvider.GetRequiredService<VSTestExecutor>();
39 |     }
40 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Protocol/ToolDefinition.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Protocol;
 4 | 
 5 | public class ToolDefinition
 6 | {
 7 |     [JsonPropertyName("name")]
 8 |     public string Name { get; set; } = string.Empty;
 9 | 
10 |     [JsonPropertyName("description")]
11 |     public string Description { get; set; } = string.Empty;
12 | 
13 |     [JsonPropertyName("inputSchema")]
14 |     public JsonSchema InputSchema { get; set; } = new();
15 | }
16 | 
17 | public class JsonSchema
18 | {
19 |     [JsonPropertyName("type")]
20 |     public string Type { get; set; } = "object";
21 | 
22 |     [JsonPropertyName("properties")]
23 |     public Dictionary<string, SchemaProperty> Properties { get; set; } = new();
24 | 
25 |     [JsonPropertyName("required")]
26 |     public List<string> Required { get; set; } = new();
27 | }
28 | 
29 | public class SchemaProperty
30 | {
31 |     [JsonPropertyName("type")]
32 |     public string Type { get; set; } = string.Empty;
33 | 
34 |     [JsonPropertyName("description")]
35 |     public string? Description { get; set; }
36 | 
37 |     [JsonPropertyName("enum")]
38 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
39 |     public List<string>? Enum { get; set; }
40 | 
41 |     [JsonPropertyName("default")]
42 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
43 |     public object? Default { get; set; }
44 | 
45 |     [JsonPropertyName("items")]
46 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
47 |     public SchemaProperty? Items { get; set; }
48 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Models/BuildProjectRequest.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Models;
 4 | 
 5 | public class BuildProjectRequest
 6 | {
 7 |     [JsonPropertyName("path")]
 8 |     public string Path { get; set; } = string.Empty;
 9 | 
10 |     [JsonPropertyName("configuration")]
11 |     public string Configuration { get; set; } = "Debug";
12 | 
13 |     [JsonPropertyName("platform")]
14 |     public string Platform { get; set; } = "Any CPU";
15 | 
16 |     [JsonPropertyName("restore")]
17 |     public bool Restore { get; set; } = true;
18 | }
19 | 
20 | public class BuildResult
21 | {
22 |     [JsonPropertyName("success")]
23 |     public bool Success { get; set; }
24 | 
25 |     [JsonPropertyName("errors")]
26 |     public List<BuildMessage> Errors { get; set; } = new();
27 | 
28 |     [JsonPropertyName("warnings")]
29 |     public List<BuildMessage> Warnings { get; set; } = new();
30 | 
31 |     [JsonPropertyName("buildTime")]
32 |     public double BuildTime { get; set; }
33 | 
34 |     [JsonPropertyName("output")]
35 |     public string Output { get; set; } = string.Empty;
36 | }
37 | 
38 | public class BuildMessage
39 | {
40 |     [JsonPropertyName("file")]
41 |     public string? File { get; set; }
42 | 
43 |     [JsonPropertyName("line")]
44 |     public int Line { get; set; }
45 | 
46 |     [JsonPropertyName("column")]
47 |     public int Column { get; set; }
48 | 
49 |     [JsonPropertyName("code")]
50 |     public string? Code { get; set; }
51 | 
52 |     [JsonPropertyName("message")]
53 |     public string Message { get; set; } = string.Empty;
54 | 
55 |     [JsonPropertyName("project")]
56 |     public string? Project { get; set; }
57 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Protocol/McpMessage.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Protocol;
 4 | 
 5 | public class McpMessage
 6 | {
 7 |     [JsonPropertyName("jsonrpc")]
 8 |     public string JsonRpc { get; set; } = "2.0";
 9 | 
10 |     [JsonPropertyName("id")]
11 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
12 |     public object? Id { get; set; }
13 | 
14 |     [JsonPropertyName("method")]
15 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
16 |     public string? Method { get; set; }
17 | 
18 |     [JsonPropertyName("params")]
19 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
20 |     public object? Params { get; set; }
21 | 
22 |     [JsonPropertyName("result")]
23 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
24 |     public object? Result { get; set; }
25 | 
26 |     [JsonPropertyName("error")]
27 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
28 |     public McpError? Error { get; set; }
29 | }
30 | 
31 | public class McpError
32 | {
33 |     [JsonPropertyName("code")]
34 |     public int Code { get; set; }
35 | 
36 |     [JsonPropertyName("message")]
37 |     public string Message { get; set; } = string.Empty;
38 | 
39 |     [JsonPropertyName("data")]
40 |     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
41 |     public object? Data { get; set; }
42 | }
43 | 
44 | public static class McpErrorCodes
45 | {
46 |     public const int ParseError = -32700;
47 |     public const int InvalidRequest = -32600;
48 |     public const int MethodNotFound = -32601;
49 |     public const int InvalidParams = -32602;
50 |     public const int InternalError = -32603;
51 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Models/RunTestsRequest.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Models;
 4 | 
 5 | public class RunTestsRequest
 6 | {
 7 |     [JsonPropertyName("path")]
 8 |     public string Path { get; set; } = string.Empty;
 9 | 
10 |     [JsonPropertyName("filter")]
11 |     public string? Filter { get; set; }
12 | 
13 |     [JsonPropertyName("verbose")]
14 |     public bool Verbose { get; set; } = false;
15 | }
16 | 
17 | public class TestResult
18 | {
19 |     [JsonPropertyName("totalTests")]
20 |     public int TotalTests { get; set; }
21 | 
22 |     [JsonPropertyName("passedTests")]
23 |     public int PassedTests { get; set; }
24 | 
25 |     [JsonPropertyName("failedTests")]
26 |     public int FailedTests { get; set; }
27 | 
28 |     [JsonPropertyName("skippedTests")]
29 |     public int SkippedTests { get; set; }
30 | 
31 |     [JsonPropertyName("duration")]
32 |     public double Duration { get; set; }
33 | 
34 |     [JsonPropertyName("testDetails")]
35 |     public List<TestDetail> TestDetails { get; set; } = new();
36 | 
37 |     [JsonPropertyName("output")]
38 |     public string Output { get; set; } = string.Empty;
39 | }
40 | 
41 | public class TestDetail
42 | {
43 |     [JsonPropertyName("name")]
44 |     public string Name { get; set; } = string.Empty;
45 | 
46 |     [JsonPropertyName("className")]
47 |     public string ClassName { get; set; } = string.Empty;
48 | 
49 |     [JsonPropertyName("result")]
50 |     public string Result { get; set; } = string.Empty;
51 | 
52 |     [JsonPropertyName("duration")]
53 |     public double Duration { get; set; }
54 | 
55 |     [JsonPropertyName("errorMessage")]
56 |     public string? ErrorMessage { get; set; }
57 | 
58 |     [JsonPropertyName("stackTrace")]
59 |     public string? StackTrace { get; set; }
60 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Models/AnalyzeSolutionRequest.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace DotNetFrameworkMCP.Server.Models;
 4 | 
 5 | public class AnalyzeSolutionRequest
 6 | {
 7 |     [JsonPropertyName("path")]
 8 |     public string Path { get; set; } = string.Empty;
 9 | }
10 | 
11 | public class SolutionAnalysis
12 | {
13 |     [JsonPropertyName("solutionName")]
14 |     public string SolutionName { get; set; } = string.Empty;
15 | 
16 |     [JsonPropertyName("projects")]
17 |     public List<ProjectInfo> Projects { get; set; } = new();
18 | 
19 |     [JsonPropertyName("totalProjects")]
20 |     public int TotalProjects { get; set; }
21 | }
22 | 
23 | public class ProjectInfo
24 | {
25 |     [JsonPropertyName("name")]
26 |     public string Name { get; set; } = string.Empty;
27 | 
28 |     [JsonPropertyName("path")]
29 |     public string Path { get; set; } = string.Empty;
30 | 
31 |     [JsonPropertyName("type")]
32 |     public string Type { get; set; } = string.Empty;
33 | 
34 |     [JsonPropertyName("targetFramework")]
35 |     public string TargetFramework { get; set; } = string.Empty;
36 | 
37 |     [JsonPropertyName("outputType")]
38 |     public string OutputType { get; set; } = string.Empty;
39 | 
40 |     [JsonPropertyName("dependencies")]
41 |     public List<string> Dependencies { get; set; } = new();
42 | 
43 |     [JsonPropertyName("packages")]
44 |     public List<PackageInfo> Packages { get; set; } = new();
45 | }
46 | 
47 | public class ListPackagesRequest
48 | {
49 |     [JsonPropertyName("path")]
50 |     public string Path { get; set; } = string.Empty;
51 | }
52 | 
53 | public class PackageInfo
54 | {
55 |     [JsonPropertyName("name")]
56 |     public string Name { get; set; } = string.Empty;
57 | 
58 |     [JsonPropertyName("version")]
59 |     public string Version { get; set; } = string.Empty;
60 | }
```

--------------------------------------------------------------------------------
/run-tcp-server-vs2022.bat:
--------------------------------------------------------------------------------

```
 1 | @echo off
 2 | echo Starting .NET Framework MCP Server with Visual Studio 2022 MSBuild...
 3 | echo Server will listen on port 3001
 4 | echo Press Ctrl+C to stop
 5 | echo.
 6 | 
 7 | cd /d "%~dp0"
 8 | 
 9 | REM Set MSBuild path to VS2022 (adjust path as needed)
10 | setlocal EnableDelayedExpansion
11 | 
12 | set "paths[0]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin"
13 | set "paths[1]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin"
14 | set "paths[2]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin"
15 | set "paths[3]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin"
16 | set "paths[4]=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin"
17 | set "paths[5]=C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin"
18 | set "paths[6]=C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin"
19 | set "paths[7]=C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin"
20 | set "paths[8]=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin"
21 | set "paths[9]=C:\Program Files\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin"
22 | 
23 | set "MSBUILD_PATH="
24 | for /L %%i in (0,1,9) do (
25 |     call set "currentPath=%%paths[%%i]%%"
26 |     if exist "!currentPath!" (
27 |         set "MSBUILD_PATH=!currentPath!"
28 |         goto :found
29 |     )
30 | )
31 | 
32 | :found
33 | if defined MSBUILD_PATH (
34 |     echo Using MSBuild from: %MSBUILD_PATH%
35 | ) else (
36 |     echo Error: MSBuild path not found.
37 |     pause
38 | )
39 | 
40 | echo.
41 | 
42 | REM Run DotNetFramework MCP Server
43 | if exist "publish\DotNetFrameworkMCP.Server.exe" (
44 |     echo Using compiled executable...
45 |     "publish\DotNetFrameworkMCP.Server.exe" --port 3001
46 | ) else (
47 |     echo Using dotnet run, building if needed...
48 |     dotnet run --project "src\DotNetFrameworkMCP.Server" -- --port 3001
49 | )
50 | 
51 | pause
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Services/ProcessBasedBuildService.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Executors;
 2 | using DotNetFrameworkMCP.Server.Models;
 3 | using Microsoft.Extensions.Logging;
 4 | 
 5 | namespace DotNetFrameworkMCP.Server.Services;
 6 | 
 7 | public interface IProcessBasedBuildService
 8 | {
 9 |     Task<Models.BuildResult> BuildProjectAsync(string projectPath, string configuration, string platform, bool restore, CancellationToken cancellationToken = default);
10 | }
11 | 
12 | public class ProcessBasedBuildService : IProcessBasedBuildService
13 | {
14 |     private readonly ILogger<ProcessBasedBuildService> _logger;
15 |     private readonly IExecutorFactory _executorFactory;
16 | 
17 |     public ProcessBasedBuildService(
18 |         ILogger<ProcessBasedBuildService> logger,
19 |         IExecutorFactory executorFactory)
20 |     {
21 |         _logger = logger;
22 |         _executorFactory = executorFactory;
23 |     }
24 | 
25 |     public async Task<Models.BuildResult> BuildProjectAsync(
26 |         string projectPath,
27 |         string configuration,
28 |         string platform,
29 |         bool restore,
30 |         CancellationToken cancellationToken = default)
31 |     {
32 |         try
33 |         {
34 |             _logger.LogInformation("Starting build for project: {ProjectPath}", projectPath);
35 |             
36 |             var buildExecutor = _executorFactory.CreateBuildExecutor();
37 |             return await buildExecutor.ExecuteBuildAsync(projectPath, configuration, platform, restore, cancellationToken);
38 |         }
39 |         catch (Exception ex)
40 |         {
41 |             _logger.LogError(ex, "Failed to create build executor or execute build for project: {ProjectPath}", projectPath);
42 |             
43 |             return new Models.BuildResult
44 |             {
45 |                 Success = false,
46 |                 Errors = new List<BuildMessage>
47 |                 {
48 |                     new BuildMessage
49 |                     {
50 |                         Message = ex.Message,
51 |                         File = projectPath
52 |                     }
53 |                 },
54 |                 Warnings = new List<BuildMessage>(),
55 |                 BuildTime = 0,
56 |                 Output = $"Build service failed: {ex.Message}"
57 |             };
58 |         }
59 |     }
60 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Services/TestRunnerService.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Executors;
 2 | using DotNetFrameworkMCP.Server.Models;
 3 | using Microsoft.Extensions.Logging;
 4 | 
 5 | namespace DotNetFrameworkMCP.Server.Services;
 6 | 
 7 | public interface ITestRunnerService
 8 | {
 9 |     Task<TestResult> RunTestsAsync(string projectPath, string? filter, bool verbose, CancellationToken cancellationToken = default);
10 | }
11 | 
12 | public class TestRunnerService : ITestRunnerService
13 | {
14 |     private readonly ILogger<TestRunnerService> _logger;
15 |     private readonly IExecutorFactory _executorFactory;
16 | 
17 |     public TestRunnerService(
18 |         ILogger<TestRunnerService> logger,
19 |         IExecutorFactory executorFactory)
20 |     {
21 |         _logger = logger;
22 |         _executorFactory = executorFactory;
23 |     }
24 | 
25 |     public async Task<TestResult> RunTestsAsync(
26 |         string projectPath,
27 |         string? filter,
28 |         bool verbose,
29 |         CancellationToken cancellationToken = default)
30 |     {
31 |         try
32 |         {
33 |             _logger.LogInformation("Starting test run for project: {ProjectPath}", projectPath);
34 |             
35 |             var testExecutor = _executorFactory.CreateTestExecutor();
36 |             return await testExecutor.ExecuteTestsAsync(projectPath, filter, verbose, cancellationToken);
37 |         }
38 |         catch (Exception ex)
39 |         {
40 |             _logger.LogError(ex, "Failed to create test executor or execute tests for project: {ProjectPath}", projectPath);
41 |             
42 |             return new TestResult
43 |             {
44 |                 TotalTests = 0,
45 |                 PassedTests = 0,
46 |                 FailedTests = 0,
47 |                 SkippedTests = 0,
48 |                 Duration = 0,
49 |                 TestDetails = new List<TestDetail>
50 |                 {
51 |                     new TestDetail
52 |                     {
53 |                         Name = "Test Execution Error",
54 |                         ClassName = "System",
55 |                         Result = "Failed",
56 |                         Duration = 0,
57 |                         ErrorMessage = ex.Message,
58 |                         StackTrace = ex.StackTrace
59 |                     }
60 |                 },
61 |                 Output = $"Test service failed: {ex.Message}"
62 |             };
63 |         }
64 |     }
65 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Tools/RunTestsHandler.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using System.Text.Json;
 2 | using DotNetFrameworkMCP.Server.Configuration;
 3 | using DotNetFrameworkMCP.Server.Models;
 4 | using DotNetFrameworkMCP.Server.Protocol;
 5 | using DotNetFrameworkMCP.Server.Services;
 6 | using Microsoft.Extensions.Logging;
 7 | using Microsoft.Extensions.Options;
 8 | 
 9 | namespace DotNetFrameworkMCP.Server.Tools;
10 | 
11 | public class RunTestsHandler : IToolHandler
12 | {
13 |     private readonly ILogger<RunTestsHandler> _logger;
14 |     private readonly McpServerConfiguration _configuration;
15 |     private readonly ITestRunnerService _testRunnerService;
16 | 
17 |     public RunTestsHandler(
18 |         ILogger<RunTestsHandler> logger,
19 |         IOptions<McpServerConfiguration> configuration,
20 |         ITestRunnerService testRunnerService)
21 |     {
22 |         _logger = logger;
23 |         _configuration = configuration.Value;
24 |         _testRunnerService = testRunnerService;
25 |     }
26 | 
27 |     public string Name => "run_tests";
28 | 
29 |     public ToolDefinition GetDefinition()
30 |     {
31 |         return new ToolDefinition
32 |         {
33 |             Name = Name,
34 |             Description = "Run tests in a .NET test project",
35 |             InputSchema = new JsonSchema
36 |             {
37 |                 Type = "object",
38 |                 Properties = new Dictionary<string, SchemaProperty>
39 |                 {
40 |                     ["path"] = new SchemaProperty
41 |                     {
42 |                         Type = "string",
43 |                         Description = "Path to test project (.csproj file)"
44 |                     },
45 |                     ["filter"] = new SchemaProperty
46 |                     {
47 |                         Type = "string",
48 |                         Description = "Test filter expression (optional)"
49 |                     },
50 |                     ["verbose"] = new SchemaProperty
51 |                     {
52 |                         Type = "boolean",
53 |                         Description = "Enable verbose output",
54 |                         Default = false
55 |                     }
56 |                 },
57 |                 Required = new List<string> { "path" }
58 |             }
59 |         };
60 |     }
61 | 
62 |     public async Task<object> ExecuteAsync(JsonElement arguments)
63 |     {
64 |         var request = JsonSerializer.Deserialize<RunTestsRequest>(arguments.GetRawText());
65 |         if (request == null || string.IsNullOrEmpty(request.Path))
66 |         {
67 |             throw new ArgumentException("Invalid test run request");
68 |         }
69 | 
70 |         _logger.LogInformation("Running tests for project: {Path}", request.Path);
71 | 
72 |         if (!string.IsNullOrEmpty(request.Filter))
73 |         {
74 |             _logger.LogInformation("Using test filter: {Filter}", request.Filter);
75 |         }
76 | 
77 |         // Create cancellation token with timeout
78 |         using var cts = new CancellationTokenSource(_configuration.TestTimeout);
79 | 
80 |         try
81 |         {
82 |             return await _testRunnerService.RunTestsAsync(
83 |                 request.Path,
84 |                 request.Filter,
85 |                 request.Verbose,
86 |                 cts.Token);
87 |         }
88 |         catch (OperationCanceledException)
89 |         {
90 |             throw new TimeoutException($"Test run timed out after {_configuration.TestTimeout}ms");
91 |         }
92 |     }
93 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Program.cs:
--------------------------------------------------------------------------------

```csharp
 1 | using DotNetFrameworkMCP.Server.Configuration;
 2 | using DotNetFrameworkMCP.Server.Executors;
 3 | using DotNetFrameworkMCP.Server.Services;
 4 | using DotNetFrameworkMCP.Server.Tools;
 5 | using Microsoft.Extensions.Configuration;
 6 | using Microsoft.Extensions.DependencyInjection;
 7 | using Microsoft.Extensions.Hosting;
 8 | using Microsoft.Extensions.Logging;
 9 | 
10 | var host = Host.CreateDefaultBuilder(args)
11 |     .ConfigureAppConfiguration((context, config) =>
12 |     {
13 |         config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
14 |         config.AddEnvironmentVariables(prefix: "MCPSERVER_");
15 |     })
16 |     .ConfigureServices((context, services) =>
17 |     {
18 |         // Configuration
19 |         services.Configure<McpServerConfiguration>(
20 |             context.Configuration.GetSection("McpServer"));
21 | 
22 |         // Register executors
23 |         services.AddSingleton<MSBuildExecutor>();
24 |         services.AddSingleton<DotNetBuildExecutor>();
25 |         services.AddSingleton<VSTestExecutor>();
26 |         services.AddSingleton<DotNetTestExecutor>();
27 |         services.AddSingleton<IExecutorFactory, ExecutorFactory>();
28 | 
29 |         // Register services
30 |         services.AddSingleton<IProcessBasedBuildService, ProcessBasedBuildService>();
31 |         services.AddSingleton<ITestRunnerService, TestRunnerService>();
32 | 
33 |         // Register tool handlers
34 |         services.AddSingleton<IToolHandler, BuildProjectHandler>();
35 |         services.AddSingleton<IToolHandler, RunTestsHandler>();
36 |         
37 |         // Register the TCP MCP server
38 |         services.AddSingleton<TcpMcpServer>();
39 | 
40 |         // Configure logging
41 |         services.AddLogging(builder =>
42 |         {
43 |             builder.ClearProviders();
44 |             
45 |             // Only log to stderr to keep stdout clean for MCP messages
46 |             builder.AddConsole(options =>
47 |             {
48 |                 options.LogToStandardErrorThreshold = LogLevel.Trace;
49 |             });
50 |             
51 |             builder.SetMinimumLevel(LogLevel.Information);
52 |             
53 |             if (context.Configuration.GetValue<bool>("McpServer:EnableDetailedLogging"))
54 |             {
55 |                 builder.SetMinimumLevel(LogLevel.Debug);
56 |             }
57 |         });
58 |     })
59 |     .UseConsoleLifetime()
60 |     .Build();
61 | 
62 | var logger = host.Services.GetRequiredService<ILogger<Program>>();
63 | 
64 | try
65 | {
66 |     logger.LogInformation("Starting .NET Framework MCP Server");
67 |     
68 |     // Create cancellation token that responds to Ctrl+C
69 |     using var cts = new CancellationTokenSource();
70 |     Console.CancelKeyPress += (sender, e) =>
71 |     {
72 |         e.Cancel = true;
73 |         cts.Cancel();
74 |     };
75 | 
76 |     // Parse port from command line arguments
77 |     var port = 3001;
78 |     if (args.Contains("--port"))
79 |     {
80 |         var portIndex = Array.IndexOf(args, "--port");
81 |         if (portIndex + 1 < args.Length && int.TryParse(args[portIndex + 1], out var parsedPort))
82 |         {
83 |             port = parsedPort;
84 |         }
85 |     }
86 | 
87 |     var tcpServer = host.Services.GetRequiredService<TcpMcpServer>();
88 |     logger.LogInformation("Starting TCP MCP Server on port {Port}", port);
89 |     await tcpServer.RunAsync(port, cts.Token);
90 | }
91 | catch (Exception ex)
92 | {
93 |     logger.LogCritical(ex, "Server failed to start");
94 |     Environment.Exit(1);
95 | }
96 | 
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Executors/MSBuildExecutorTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using DotNetFrameworkMCP.Server.Configuration;
  2 | using DotNetFrameworkMCP.Server.Executors;
  3 | using Microsoft.Extensions.Logging;
  4 | using Microsoft.Extensions.Options;
  5 | using Moq;
  6 | using NUnit.Framework;
  7 | 
  8 | namespace DotNetFrameworkMCP.Server.Tests.Executors;
  9 | 
 10 | [TestFixture]
 11 | public class MSBuildExecutorTests
 12 | {
 13 |     private Mock<ILogger<MSBuildExecutor>> _mockLogger;
 14 |     private Mock<IOptions<McpServerConfiguration>> _mockOptions;
 15 |     private McpServerConfiguration _configuration;
 16 |     private MSBuildExecutor _executor;
 17 | 
 18 |     [SetUp]
 19 |     public void SetUp()
 20 |     {
 21 |         _mockLogger = new Mock<ILogger<MSBuildExecutor>>();
 22 |         _mockOptions = new Mock<IOptions<McpServerConfiguration>>();
 23 |         _configuration = new McpServerConfiguration
 24 |         {
 25 |             BuildTimeout = 60000,
 26 |             PreferredVSVersion = "2022"
 27 |         };
 28 |         _mockOptions.Setup(x => x.Value).Returns(_configuration);
 29 |         _executor = new MSBuildExecutor(_mockLogger.Object, _mockOptions.Object);
 30 |     }
 31 | 
 32 |     [Test]
 33 |     public async Task ExecuteBuildAsync_WithNonExistentProject_ReturnsFailedResult()
 34 |     {
 35 |         // Arrange
 36 |         var nonExistentPath = "/path/to/nonexistent/project.csproj";
 37 | 
 38 |         // Act
 39 |         var result = await _executor.ExecuteBuildAsync(nonExistentPath, "Debug", "Any CPU", true);
 40 | 
 41 |         // Assert
 42 |         Assert.That(result.Success, Is.False);
 43 |         Assert.That(result.Errors, Has.Count.EqualTo(1));
 44 |         Assert.That(result.Errors[0].Message, Does.Contain("Project file not found"));
 45 |     }
 46 | 
 47 |     [Test]
 48 |     public async Task ExecuteBuildAsync_WithTimeout_ReturnsFailedResult()
 49 |     {
 50 |         // Arrange
 51 |         _configuration.BuildTimeout = 1; // 1ms timeout to force timeout
 52 |         var tempProjectFile = Path.GetTempFileName();
 53 |         File.WriteAllText(tempProjectFile, "<Project></Project>");
 54 | 
 55 |         try
 56 |         {
 57 |             // Act
 58 |             var result = await _executor.ExecuteBuildAsync(tempProjectFile, "Debug", "Any CPU", true);
 59 |             
 60 |             // Assert
 61 |             // Should return a failed result due to timeout or MSBuild not found
 62 |             Assert.That(result.Success, Is.False);
 63 |         }
 64 |         finally
 65 |         {
 66 |             // Cleanup
 67 |             if (File.Exists(tempProjectFile))
 68 |                 File.Delete(tempProjectFile);
 69 |         }
 70 |     }
 71 | 
 72 |     [Test]
 73 |     public void Constructor_WithValidParameters_CreatesInstance()
 74 |     {
 75 |         // Act & Assert
 76 |         Assert.That(_executor, Is.Not.Null);
 77 |     }
 78 | 
 79 |     [TestCase("Debug", "Any CPU", true)]
 80 |     [TestCase("Release", "x64", false)]
 81 |     [TestCase("Debug", "x86", true)]
 82 |     public async Task ExecuteBuildAsync_WithDifferentConfigurations_HandlesGracefully(
 83 |         string configuration, string platform, bool restore)
 84 |     {
 85 |         // Arrange
 86 |         var tempProjectFile = Path.GetTempFileName();
 87 |         File.WriteAllText(tempProjectFile, "<Project></Project>");
 88 | 
 89 |         try
 90 |         {
 91 |             // Act
 92 |             var result = await _executor.ExecuteBuildAsync(tempProjectFile, configuration, platform, restore);
 93 | 
 94 |             // Assert
 95 |             // Result should be non-null (even if build fails due to no MSBuild)
 96 |             Assert.That(result, Is.Not.Null);
 97 |         }
 98 |         finally
 99 |         {
100 |             // Cleanup
101 |             if (File.Exists(tempProjectFile))
102 |                 File.Delete(tempProjectFile);
103 |         }
104 |     }
105 | }
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Executors/ExecutorFactoryTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using DotNetFrameworkMCP.Server.Configuration;
  2 | using DotNetFrameworkMCP.Server.Executors;
  3 | using Microsoft.Extensions.DependencyInjection;
  4 | using Microsoft.Extensions.Logging;
  5 | using Microsoft.Extensions.Options;
  6 | using Moq;
  7 | using NUnit.Framework;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Tests.Executors;
 10 | 
 11 | [TestFixture]
 12 | public class ExecutorFactoryTests
 13 | {
 14 |     private IServiceProvider _serviceProvider;
 15 |     private Mock<IOptions<McpServerConfiguration>> _mockOptions;
 16 |     private McpServerConfiguration _configuration;
 17 | 
 18 |     [SetUp]
 19 |     public void SetUp()
 20 |     {
 21 |         _configuration = new McpServerConfiguration();
 22 |         _mockOptions = new Mock<IOptions<McpServerConfiguration>>();
 23 |         _mockOptions.Setup(x => x.Value).Returns(_configuration);
 24 | 
 25 |         var services = new ServiceCollection();
 26 |         
 27 |         // Register executors
 28 |         services.AddSingleton<MSBuildExecutor>();
 29 |         services.AddSingleton<DotNetBuildExecutor>();
 30 |         services.AddSingleton<VSTestExecutor>();
 31 |         services.AddSingleton<DotNetTestExecutor>();
 32 |         
 33 |         // Register configuration
 34 |         services.AddSingleton(_mockOptions.Object);
 35 |         
 36 |         // Register loggers
 37 |         services.AddSingleton<ILogger<MSBuildExecutor>>(Mock.Of<ILogger<MSBuildExecutor>>());
 38 |         services.AddSingleton<ILogger<DotNetBuildExecutor>>(Mock.Of<ILogger<DotNetBuildExecutor>>());
 39 |         services.AddSingleton<ILogger<VSTestExecutor>>(Mock.Of<ILogger<VSTestExecutor>>());
 40 |         services.AddSingleton<ILogger<DotNetTestExecutor>>(Mock.Of<ILogger<DotNetTestExecutor>>());
 41 | 
 42 |         _serviceProvider = services.BuildServiceProvider();
 43 |     }
 44 | 
 45 |     [TearDown]
 46 |     public void TearDown()
 47 |     {
 48 |         if (_serviceProvider is IDisposable disposable)
 49 |         {
 50 |             disposable.Dispose();
 51 |         }
 52 |     }
 53 | 
 54 |     [Test]
 55 |     public void CreateBuildExecutor_WhenUseDotNetCliIsFalse_ReturnsMSBuildExecutor()
 56 |     {
 57 |         // Arrange
 58 |         _configuration.UseDotNetCli = false;
 59 |         var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);
 60 | 
 61 |         // Act
 62 |         var executor = factory.CreateBuildExecutor();
 63 | 
 64 |         // Assert
 65 |         Assert.That(executor, Is.TypeOf<MSBuildExecutor>());
 66 |     }
 67 | 
 68 |     [Test]
 69 |     public void CreateBuildExecutor_WhenUseDotNetCliIsTrue_ReturnsDotNetBuildExecutor()
 70 |     {
 71 |         // Arrange
 72 |         _configuration.UseDotNetCli = true;
 73 |         var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);
 74 | 
 75 |         // Act
 76 |         var executor = factory.CreateBuildExecutor();
 77 | 
 78 |         // Assert
 79 |         Assert.That(executor, Is.TypeOf<DotNetBuildExecutor>());
 80 |     }
 81 | 
 82 |     [Test]
 83 |     public void CreateTestExecutor_WhenUseDotNetCliIsFalse_ReturnsVSTestExecutor()
 84 |     {
 85 |         // Arrange
 86 |         _configuration.UseDotNetCli = false;
 87 |         var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);
 88 | 
 89 |         // Act
 90 |         var executor = factory.CreateTestExecutor();
 91 | 
 92 |         // Assert
 93 |         Assert.That(executor, Is.TypeOf<VSTestExecutor>());
 94 |     }
 95 | 
 96 |     [Test]
 97 |     public void CreateTestExecutor_WhenUseDotNetCliIsTrue_ReturnsDotNetTestExecutor()
 98 |     {
 99 |         // Arrange
100 |         _configuration.UseDotNetCli = true;
101 |         var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);
102 | 
103 |         // Act
104 |         var executor = factory.CreateTestExecutor();
105 | 
106 |         // Assert
107 |         Assert.That(executor, Is.TypeOf<DotNetTestExecutor>());
108 |     }
109 | 
110 |     [Test]
111 |     public void Constructor_WithValidParameters_CreatesInstance()
112 |     {
113 |         // Act
114 |         var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);
115 | 
116 |         // Assert
117 |         Assert.That(factory, Is.Not.Null);
118 |     }
119 | }
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Tools/BuildProjectHandlerTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Text.Json;
  2 | using DotNetFrameworkMCP.Server.Configuration;
  3 | using DotNetFrameworkMCP.Server.Models;
  4 | using DotNetFrameworkMCP.Server.Services;
  5 | using DotNetFrameworkMCP.Server.Tools;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Tests.Tools;
 10 | 
 11 | [TestFixture]
 12 | public class BuildProjectHandlerTests
 13 | {
 14 |     private BuildProjectHandler _handler;
 15 |     private ILogger<BuildProjectHandler> _logger;
 16 |     private IOptions<McpServerConfiguration> _configuration;
 17 |     private MockProcessBasedBuildService _buildService;
 18 | 
 19 |     [SetUp]
 20 |     public void Setup()
 21 |     {
 22 |         _logger = new TestLogger<BuildProjectHandler>();
 23 |         _configuration = Options.Create(new McpServerConfiguration
 24 |         {
 25 |             DefaultConfiguration = "Debug",
 26 |             DefaultPlatform = "Any CPU",
 27 |             BuildTimeout = 600000
 28 |         });
 29 |         _buildService = new MockProcessBasedBuildService();
 30 |         _handler = new BuildProjectHandler(_logger, _configuration, _buildService);
 31 |     }
 32 | 
 33 |     [Test]
 34 |     public void GetDefinition_ReturnsCorrectToolDefinition()
 35 |     {
 36 |         var definition = _handler.GetDefinition();
 37 | 
 38 |         Assert.That(definition.Name, Is.EqualTo("build_project"));
 39 |         Assert.That(definition.Description, Is.EqualTo("Build a .NET project or solution"));
 40 |         Assert.That(definition.InputSchema, Is.Not.Null);
 41 |         Assert.That(definition.InputSchema.Type, Is.EqualTo("object"));
 42 |         Assert.That(definition.InputSchema.Properties, Has.Count.EqualTo(4));
 43 |         Assert.That(definition.InputSchema.Properties.ContainsKey("path"), Is.True);
 44 |         Assert.That(definition.InputSchema.Required, Contains.Item("path"));
 45 |     }
 46 | 
 47 |     [Test]
 48 |     public async Task ExecuteAsync_WithValidRequest_ReturnsSuccessResult()
 49 |     {
 50 |         var request = new BuildProjectRequest
 51 |         {
 52 |             Path = "test.csproj",
 53 |             Configuration = "Debug",
 54 |             Platform = "Any CPU",
 55 |             Restore = true
 56 |         };
 57 | 
 58 |         var json = JsonSerializer.Serialize(request);
 59 |         var element = JsonDocument.Parse(json).RootElement;
 60 | 
 61 |         var result = await _handler.ExecuteAsync(element);
 62 | 
 63 |         Assert.That(result, Is.Not.Null);
 64 |         Assert.That(result, Is.TypeOf<BuildResult>());
 65 |         
 66 |         var buildResult = (BuildResult)result;
 67 |         Assert.That(buildResult.Success, Is.True);
 68 |         Assert.That(buildResult.Errors, Is.Empty);
 69 |         Assert.That(buildResult.Warnings, Is.Empty);
 70 |         Assert.That(buildResult.Output, Is.EqualTo("Build succeeded."));
 71 |     }
 72 | 
 73 |     [Test]
 74 |     public void ExecuteAsync_WithInvalidRequest_ThrowsArgumentException()
 75 |     {
 76 |         var invalidJson = "{}";
 77 |         var element = JsonDocument.Parse(invalidJson).RootElement;
 78 | 
 79 |         Assert.ThrowsAsync<ArgumentException>(async () => await _handler.ExecuteAsync(element));
 80 |     }
 81 | 
 82 |     private class TestLogger<T> : ILogger<T>
 83 |     {
 84 |         public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
 85 |         public bool IsEnabled(LogLevel logLevel) => true;
 86 |         public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { }
 87 |     }
 88 | 
 89 |     private class MockProcessBasedBuildService : IProcessBasedBuildService
 90 |     {
 91 |         public Task<Models.BuildResult> BuildProjectAsync(string projectPath, string configuration, string platform, bool restore, CancellationToken cancellationToken = default)
 92 |         {
 93 |             return Task.FromResult(new Models.BuildResult
 94 |             {
 95 |                 Success = true,
 96 |                 Errors = new List<BuildMessage>(),
 97 |                 Warnings = new List<BuildMessage>(),
 98 |                 BuildTime = 2.45,
 99 |                 Output = "Build succeeded."
100 |             });
101 |         }
102 |     }
103 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/DotNetTestExecutor.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using DotNetFrameworkMCP.Server.Configuration;
  2 | using DotNetFrameworkMCP.Server.Models;
  3 | using Microsoft.Extensions.Logging;
  4 | using Microsoft.Extensions.Options;
  5 | 
  6 | namespace DotNetFrameworkMCP.Server.Executors;
  7 | 
  8 | /// <summary>
  9 | /// dotnet test-based test executor
 10 | /// </summary>
 11 | public class DotNetTestExecutor : BaseTestExecutor
 12 | {
 13 |     public DotNetTestExecutor(
 14 |         ILogger<DotNetTestExecutor> logger,
 15 |         IOptions<McpServerConfiguration> configuration)
 16 |         : base(logger, configuration)
 17 |     {
 18 |     }
 19 | 
 20 |     public override async Task<TestResult> ExecuteTestsAsync(
 21 |         string projectPath,
 22 |         string? filter,
 23 |         bool verbose,
 24 |         CancellationToken cancellationToken = default)
 25 |     {
 26 |         var stopwatch = System.Diagnostics.Stopwatch.StartNew();
 27 |         
 28 |         try
 29 |         {
 30 |             // Validate project path
 31 |             if (!File.Exists(projectPath))
 32 |             {
 33 |                 throw new FileNotFoundException($"Test project file not found: {projectPath}");
 34 |             }
 35 | 
 36 |             var arguments = new List<string>
 37 |             {
 38 |                 "test",
 39 |                 $"\"{projectPath}\"",
 40 |                 "--no-build" // Assume project is already built
 41 |             };
 42 | 
 43 |             if (!string.IsNullOrEmpty(filter))
 44 |             {
 45 |                 arguments.Add("--filter");
 46 |                 arguments.Add($"\"{filter}\"");
 47 |             }
 48 | 
 49 |             if (verbose)
 50 |             {
 51 |                 arguments.Add("--verbosity");
 52 |                 arguments.Add("detailed");
 53 |             }
 54 |             else
 55 |             {
 56 |                 arguments.Add("--verbosity");
 57 |                 arguments.Add("normal");
 58 |             }
 59 | 
 60 |             // Add logger for structured output
 61 |             var trxFileName = $"TestResults_{Guid.NewGuid():N}.trx";
 62 |             var trxFilePath = Path.Combine(Path.GetTempPath(), trxFileName);
 63 |             arguments.Add($"--logger");
 64 |             arguments.Add($"trx;LogFileName=\"{trxFilePath}\"");
 65 | 
 66 |             var argumentString = string.Join(" ", arguments);
 67 |             _logger.LogInformation("Running dotnet test: {Arguments}", argumentString);
 68 | 
 69 |             var result = await RunProcessAsync(_configuration.DotNetPath, argumentString, cancellationToken);
 70 |             
 71 |             // Parse results from TRX file
 72 |             var testResult = await ParseTrxFileAsync(trxFilePath, result.Output);
 73 |             
 74 |             // Clean up TRX file
 75 |             try
 76 |             {
 77 |                 if (File.Exists(trxFilePath))
 78 |                     File.Delete(trxFilePath);
 79 |             }
 80 |             catch (Exception ex)
 81 |             {
 82 |                 _logger.LogDebug("Failed to delete TRX file: {Error}", ex.Message);
 83 |             }
 84 | 
 85 |             stopwatch.Stop();
 86 |             testResult.Duration = stopwatch.Elapsed.TotalSeconds;
 87 |             
 88 |             return testResult;
 89 |         }
 90 |         catch (Exception ex)
 91 |         {
 92 |             _logger.LogError(ex, "Error running dotnet test for project: {ProjectPath}", projectPath);
 93 |             stopwatch.Stop();
 94 | 
 95 |             return new TestResult
 96 |             {
 97 |                 TotalTests = 0,
 98 |                 PassedTests = 0,
 99 |                 FailedTests = 0,
100 |                 SkippedTests = 0,
101 |                 Duration = stopwatch.Elapsed.TotalSeconds,
102 |                 TestDetails = new List<TestDetail>
103 |                 {
104 |                     new TestDetail
105 |                     {
106 |                         Name = "Test Execution Error",
107 |                         ClassName = "DotNetTest",
108 |                         Result = "Failed",
109 |                         Duration = 0,
110 |                         ErrorMessage = ex.Message,
111 |                         StackTrace = ex.StackTrace
112 |                     }
113 |                 },
114 |                 Output = $"Dotnet test execution failed: {ex.Message}\n{ex.StackTrace}"
115 |             };
116 |         }
117 |     }
118 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Tools/BuildProjectHandler.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Text.Json;
  2 | using DotNetFrameworkMCP.Server.Configuration;
  3 | using DotNetFrameworkMCP.Server.Models;
  4 | using DotNetFrameworkMCP.Server.Protocol;
  5 | using DotNetFrameworkMCP.Server.Services;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Tools;
 10 | 
 11 | public class BuildProjectHandler : IToolHandler
 12 | {
 13 |     private readonly ILogger<BuildProjectHandler> _logger;
 14 |     private readonly McpServerConfiguration _configuration;
 15 |     private readonly IProcessBasedBuildService _buildService;
 16 | 
 17 |     public BuildProjectHandler(
 18 |         ILogger<BuildProjectHandler> logger,
 19 |         IOptions<McpServerConfiguration> configuration,
 20 |         IProcessBasedBuildService buildService)
 21 |     {
 22 |         _logger = logger;
 23 |         _configuration = configuration.Value;
 24 |         _buildService = buildService;
 25 |     }
 26 | 
 27 |     public string Name => "build_project";
 28 | 
 29 |     public ToolDefinition GetDefinition()
 30 |     {
 31 |         return new ToolDefinition
 32 |         {
 33 |             Name = Name,
 34 |             Description = "Build a .NET project or solution",
 35 |             InputSchema = new JsonSchema
 36 |             {
 37 |                 Type = "object",
 38 |                 Properties = new Dictionary<string, SchemaProperty>
 39 |                 {
 40 |                     ["path"] = new SchemaProperty
 41 |                     {
 42 |                         Type = "string",
 43 |                         Description = "Path to .csproj or .sln file"
 44 |                     },
 45 |                     ["configuration"] = new SchemaProperty
 46 |                     {
 47 |                         Type = "string",
 48 |                         Description = "Build configuration",
 49 |                         Enum = new List<string> { "Debug", "Release" },
 50 |                         Default = "Debug"
 51 |                     },
 52 |                     ["platform"] = new SchemaProperty
 53 |                     {
 54 |                         Type = "string",
 55 |                         Description = "Target platform",
 56 |                         Enum = new List<string> { "Any CPU", "x86", "x64" },
 57 |                         Default = "Any CPU"
 58 |                     },
 59 |                     ["restore"] = new SchemaProperty
 60 |                     {
 61 |                         Type = "boolean",
 62 |                         Description = "Restore NuGet packages",
 63 |                         Default = true
 64 |                     }
 65 |                 },
 66 |                 Required = new List<string> { "path" }
 67 |             }
 68 |         };
 69 |     }
 70 | 
 71 |     public async Task<object> ExecuteAsync(JsonElement arguments)
 72 |     {
 73 |         var request = JsonSerializer.Deserialize<BuildProjectRequest>(arguments.GetRawText());
 74 |         if (request == null || string.IsNullOrEmpty(request.Path))
 75 |         {
 76 |             throw new ArgumentException("Invalid build request");
 77 |         }
 78 | 
 79 |         _logger.LogInformation("Building project: {Path}", request.Path);
 80 | 
 81 |         // Use default values from configuration if not specified
 82 |         var configuration = string.IsNullOrEmpty(request.Configuration) 
 83 |             ? _configuration.DefaultConfiguration 
 84 |             : request.Configuration;
 85 |         
 86 |         var platform = string.IsNullOrEmpty(request.Platform) 
 87 |             ? _configuration.DefaultPlatform 
 88 |             : request.Platform;
 89 | 
 90 |         // Create cancellation token with timeout
 91 |         using var cts = new CancellationTokenSource(_configuration.BuildTimeout);
 92 | 
 93 |         try
 94 |         {
 95 |             _logger.LogDebug("Building project using process-based MSBuild approach");
 96 |             return await _buildService.BuildProjectAsync(
 97 |                 request.Path,
 98 |                 configuration,
 99 |                 platform,
100 |                 request.Restore,
101 |                 cts.Token);
102 |         }
103 |         catch (OperationCanceledException)
104 |         {
105 |             throw new TimeoutException($"Build timed out after {_configuration.BuildTimeout}ms");
106 |         }
107 |     }
108 | }
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Tools/RunTestsHandlerTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Text.Json;
  2 | using DotNetFrameworkMCP.Server.Configuration;
  3 | using DotNetFrameworkMCP.Server.Models;
  4 | using DotNetFrameworkMCP.Server.Services;
  5 | using DotNetFrameworkMCP.Server.Tools;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | using Moq;
  9 | using NUnit.Framework;
 10 | 
 11 | namespace DotNetFrameworkMCP.Server.Tests.Tools;
 12 | 
 13 | [TestFixture]
 14 | public class RunTestsHandlerTests
 15 | {
 16 |     private Mock<ILogger<RunTestsHandler>> _mockLogger;
 17 |     private Mock<ITestRunnerService> _mockTestRunnerService;
 18 |     private IOptions<McpServerConfiguration> _configuration;
 19 |     private RunTestsHandler _handler;
 20 | 
 21 |     [SetUp]
 22 |     public void SetUp()
 23 |     {
 24 |         _mockLogger = new Mock<ILogger<RunTestsHandler>>();
 25 |         _mockTestRunnerService = new Mock<ITestRunnerService>();
 26 |         _configuration = Options.Create(new McpServerConfiguration
 27 |         {
 28 |             TestTimeout = 300000
 29 |         });
 30 | 
 31 |         _handler = new RunTestsHandler(_mockLogger.Object, _configuration, _mockTestRunnerService.Object);
 32 |     }
 33 | 
 34 |     [Test]
 35 |     public void Name_ShouldReturnCorrectName()
 36 |     {
 37 |         Assert.That(_handler.Name, Is.EqualTo("run_tests"));
 38 |     }
 39 | 
 40 |     [Test]
 41 |     public void GetDefinition_ShouldReturnValidDefinition()
 42 |     {
 43 |         var definition = _handler.GetDefinition();
 44 | 
 45 |         Assert.That(definition.Name, Is.EqualTo("run_tests"));
 46 |         Assert.That(definition.Description, Is.EqualTo("Run tests in a .NET test project"));
 47 |         Assert.That(definition.InputSchema.Type, Is.EqualTo("object"));
 48 |         Assert.That(definition.InputSchema.Properties, Contains.Key("path"));
 49 |         Assert.That(definition.InputSchema.Properties["path"].Type, Is.EqualTo("string"));
 50 |         Assert.That(definition.InputSchema.Required, Contains.Item("path"));
 51 |     }
 52 | 
 53 |     [Test]
 54 |     public async Task ExecuteAsync_WithValidRequest_ShouldCallTestRunnerService()
 55 |     {
 56 |         // Arrange
 57 |         var expectedResult = new TestResult
 58 |         {
 59 |             TotalTests = 5,
 60 |             PassedTests = 4,
 61 |             FailedTests = 1,
 62 |             SkippedTests = 0,
 63 |             Duration = 2.5,
 64 |             TestDetails = new List<TestDetail>()
 65 |         };
 66 | 
 67 |         _mockTestRunnerService
 68 |             .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
 69 |             .ReturnsAsync(expectedResult);
 70 | 
 71 |         var request = new RunTestsRequest
 72 |         {
 73 |             Path = "TestProject.csproj",
 74 |             Filter = "Category=Unit",
 75 |             Verbose = true
 76 |         };
 77 | 
 78 |         var jsonElement = JsonSerializer.SerializeToElement(request);
 79 | 
 80 |         // Act
 81 |         var result = await _handler.ExecuteAsync(jsonElement);
 82 | 
 83 |         // Assert
 84 |         Assert.That(result, Is.EqualTo(expectedResult));
 85 |         _mockTestRunnerService.Verify(
 86 |             x => x.RunTestsAsync("TestProject.csproj", "Category=Unit", true, It.IsAny<CancellationToken>()),
 87 |             Times.Once);
 88 |     }
 89 | 
 90 |     [Test]
 91 |     public async Task ExecuteAsync_WithMinimalRequest_ShouldUseDefaults()
 92 |     {
 93 |         // Arrange
 94 |         var expectedResult = new TestResult();
 95 |         _mockTestRunnerService
 96 |             .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
 97 |             .ReturnsAsync(expectedResult);
 98 | 
 99 |         var request = new RunTestsRequest
100 |         {
101 |             Path = "TestProject.csproj"
102 |         };
103 | 
104 |         var jsonElement = JsonSerializer.SerializeToElement(request);
105 | 
106 |         // Act
107 |         var result = await _handler.ExecuteAsync(jsonElement);
108 | 
109 |         // Assert
110 |         _mockTestRunnerService.Verify(
111 |             x => x.RunTestsAsync("TestProject.csproj", null, false, It.IsAny<CancellationToken>()),
112 |             Times.Once);
113 |     }
114 | 
115 |     [Test]
116 |     public void ExecuteAsync_WithNullRequest_ShouldThrowArgumentException()
117 |     {
118 |         // Arrange
119 |         var jsonElement = JsonSerializer.SerializeToElement((object?)null);
120 | 
121 |         // Act & Assert
122 |         Assert.ThrowsAsync<ArgumentException>(() => _handler.ExecuteAsync(jsonElement));
123 |     }
124 | 
125 |     [Test]
126 |     public void ExecuteAsync_WithEmptyPath_ShouldThrowArgumentException()
127 |     {
128 |         // Arrange
129 |         var request = new RunTestsRequest { Path = "" };
130 |         var jsonElement = JsonSerializer.SerializeToElement(request);
131 | 
132 |         // Act & Assert
133 |         Assert.ThrowsAsync<ArgumentException>(() => _handler.ExecuteAsync(jsonElement));
134 |     }
135 | 
136 |     [Test]
137 |     public void ExecuteAsync_WhenServiceTimesOut_ShouldThrowTimeoutException()
138 |     {
139 |         // Arrange
140 |         _mockTestRunnerService
141 |             .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
142 |             .Throws<OperationCanceledException>();
143 | 
144 |         var request = new RunTestsRequest { Path = "TestProject.csproj" };
145 |         var jsonElement = JsonSerializer.SerializeToElement(request);
146 | 
147 |         // Act & Assert
148 |         Assert.ThrowsAsync<TimeoutException>(() => _handler.ExecuteAsync(jsonElement));
149 |     }
150 | }
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Services/TestRunnerServiceTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using DotNetFrameworkMCP.Server.Executors;
  2 | using DotNetFrameworkMCP.Server.Models;
  3 | using DotNetFrameworkMCP.Server.Services;
  4 | using Microsoft.Extensions.Logging;
  5 | using Moq;
  6 | using NUnit.Framework;
  7 | 
  8 | namespace DotNetFrameworkMCP.Server.Tests.Services;
  9 | 
 10 | [TestFixture]
 11 | public class TestRunnerServiceTests
 12 | {
 13 |     private Mock<ILogger<TestRunnerService>> _mockLogger;
 14 |     private Mock<IExecutorFactory> _mockExecutorFactory;
 15 |     private Mock<ITestExecutor> _mockTestExecutor;
 16 |     private TestRunnerService _service;
 17 |     private string _tempProjectFile;
 18 | 
 19 |     [SetUp]
 20 |     public void SetUp()
 21 |     {
 22 |         _mockLogger = new Mock<ILogger<TestRunnerService>>();
 23 |         _mockExecutorFactory = new Mock<IExecutorFactory>();
 24 |         _mockTestExecutor = new Mock<ITestExecutor>();
 25 | 
 26 |         _mockExecutorFactory.Setup(x => x.CreateTestExecutor())
 27 |             .Returns(_mockTestExecutor.Object);
 28 | 
 29 |         _service = new TestRunnerService(_mockLogger.Object, _mockExecutorFactory.Object);
 30 | 
 31 |         // Create a temporary project file for testing
 32 |         _tempProjectFile = Path.GetTempFileName();
 33 |         File.WriteAllText(_tempProjectFile, @"
 34 | <Project Sdk=""Microsoft.NET.Sdk"">
 35 |   <PropertyGroup>
 36 |     <TargetFramework>net48</TargetFramework>
 37 |     <IsPackable>false</IsPackable>
 38 |   </PropertyGroup>
 39 |   <ItemGroup>
 40 |     <PackageReference Include=""Microsoft.NET.Test.Sdk"" Version=""17.0.0"" />
 41 |     <PackageReference Include=""MSTest.TestFramework"" Version=""2.2.7"" />
 42 |     <PackageReference Include=""MSTest.TestAdapter"" Version=""2.2.7"" />
 43 |   </ItemGroup>
 44 | </Project>");
 45 |     }
 46 | 
 47 |     [TearDown]
 48 |     public void TearDown()
 49 |     {
 50 |         if (File.Exists(_tempProjectFile))
 51 |         {
 52 |             File.Delete(_tempProjectFile);
 53 |         }
 54 |     }
 55 | 
 56 |     [Test]
 57 |     public async Task RunTestsAsync_CallsExecutorFactory()
 58 |     {
 59 |         // Arrange
 60 |         var expectedResult = new TestResult
 61 |         {
 62 |             TotalTests = 5,
 63 |             PassedTests = 4,
 64 |             FailedTests = 1,
 65 |             SkippedTests = 0,
 66 |             Duration = 2.5,
 67 |             TestDetails = new List<TestDetail>(),
 68 |             Output = "Test output"
 69 |         };
 70 | 
 71 |         _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
 72 |                 It.IsAny<string>(),
 73 |                 It.IsAny<string>(),
 74 |                 It.IsAny<bool>(),
 75 |                 It.IsAny<CancellationToken>()))
 76 |             .ReturnsAsync(expectedResult);
 77 | 
 78 |         // Act
 79 |         var result = await _service.RunTestsAsync(_tempProjectFile, null, false);
 80 | 
 81 |         // Assert
 82 |         Assert.That(result, Is.EqualTo(expectedResult));
 83 |         _mockExecutorFactory.Verify(x => x.CreateTestExecutor(), Times.Once);
 84 |         _mockTestExecutor.Verify(x => x.ExecuteTestsAsync(
 85 |             _tempProjectFile, null, false, It.IsAny<CancellationToken>()), Times.Once);
 86 |     }
 87 | 
 88 |     [Test]
 89 |     public async Task RunTestsAsync_WithFilter_PassesFilterToExecutor()
 90 |     {
 91 |         // Arrange
 92 |         var filter = "TestCategory=Unit";
 93 |         var expectedResult = new TestResult { TotalTests = 1, PassedTests = 1 };
 94 | 
 95 |         _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
 96 |                 It.IsAny<string>(),
 97 |                 filter,
 98 |                 It.IsAny<bool>(),
 99 |                 It.IsAny<CancellationToken>()))
100 |             .ReturnsAsync(expectedResult);
101 | 
102 |         // Act
103 |         var result = await _service.RunTestsAsync(_tempProjectFile, filter, true);
104 | 
105 |         // Assert
106 |         _mockTestExecutor.Verify(x => x.ExecuteTestsAsync(
107 |             _tempProjectFile, filter, true, It.IsAny<CancellationToken>()), Times.Once);
108 |     }
109 | 
110 |     [Test]
111 |     public async Task RunTestsAsync_WhenExecutorThrows_ReturnsFailedResult()
112 |     {
113 |         // Arrange
114 |         _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
115 |                 It.IsAny<string>(),
116 |                 It.IsAny<string>(),
117 |                 It.IsAny<bool>(),
118 |                 It.IsAny<CancellationToken>()))
119 |             .ThrowsAsync(new InvalidOperationException("Test executor failed"));
120 | 
121 |         // Act
122 |         var result = await _service.RunTestsAsync(_tempProjectFile, null, false);
123 | 
124 |         // Assert
125 |         Assert.That(result.TotalTests, Is.EqualTo(0));
126 |         Assert.That(result.PassedTests, Is.EqualTo(0));
127 |         Assert.That(result.FailedTests, Is.EqualTo(0));
128 |         Assert.That(result.SkippedTests, Is.EqualTo(0));
129 |         Assert.That(result.TestDetails, Has.Count.EqualTo(1));
130 |         Assert.That(result.TestDetails[0].Result, Is.EqualTo("Failed"));
131 |         Assert.That(result.TestDetails[0].ErrorMessage, Does.Contain("Test executor failed"));
132 |     }
133 | 
134 |     [Test]
135 |     public async Task RunTestsAsync_WhenFactoryThrows_ReturnsFailedResult()
136 |     {
137 |         // Arrange
138 |         _mockExecutorFactory.Setup(x => x.CreateTestExecutor())
139 |             .Throws(new InvalidOperationException("Factory failed"));
140 | 
141 |         // Act
142 |         var result = await _service.RunTestsAsync(_tempProjectFile, null, false);
143 | 
144 |         // Assert
145 |         Assert.That(result.TotalTests, Is.EqualTo(0));
146 |         Assert.That(result.TestDetails, Has.Count.EqualTo(1));
147 |         Assert.That(result.TestDetails[0].Result, Is.EqualTo("Failed"));
148 |         Assert.That(result.Output, Does.Contain("Test service failed"));
149 |     }
150 | }
```

--------------------------------------------------------------------------------
/tests/DotNetFrameworkMCP.Server.Tests/Services/ProcessBasedBuildServiceTests.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using DotNetFrameworkMCP.Server.Executors;
  2 | using DotNetFrameworkMCP.Server.Models;
  3 | using DotNetFrameworkMCP.Server.Services;
  4 | using Microsoft.Extensions.Logging;
  5 | using Moq;
  6 | using NUnit.Framework;
  7 | 
  8 | namespace DotNetFrameworkMCP.Server.Tests.Services;
  9 | 
 10 | [TestFixture]
 11 | public class ProcessBasedBuildServiceTests
 12 | {
 13 |     private Mock<ILogger<ProcessBasedBuildService>> _mockLogger;
 14 |     private Mock<IExecutorFactory> _mockExecutorFactory;
 15 |     private Mock<IBuildExecutor> _mockBuildExecutor;
 16 |     private ProcessBasedBuildService _service;
 17 |     private string _tempProjectFile;
 18 | 
 19 |     [SetUp]
 20 |     public void SetUp()
 21 |     {
 22 |         _mockLogger = new Mock<ILogger<ProcessBasedBuildService>>();
 23 |         _mockExecutorFactory = new Mock<IExecutorFactory>();
 24 |         _mockBuildExecutor = new Mock<IBuildExecutor>();
 25 | 
 26 |         _mockExecutorFactory.Setup(x => x.CreateBuildExecutor())
 27 |             .Returns(_mockBuildExecutor.Object);
 28 | 
 29 |         _service = new ProcessBasedBuildService(_mockLogger.Object, _mockExecutorFactory.Object);
 30 | 
 31 |         // Create a temporary project file for testing
 32 |         _tempProjectFile = Path.GetTempFileName();
 33 |         File.WriteAllText(_tempProjectFile, @"
 34 | <Project Sdk=""Microsoft.NET.Sdk"">
 35 |   <PropertyGroup>
 36 |     <TargetFramework>net48</TargetFramework>
 37 |   </PropertyGroup>
 38 | </Project>");
 39 |     }
 40 | 
 41 |     [TearDown]
 42 |     public void TearDown()
 43 |     {
 44 |         if (File.Exists(_tempProjectFile))
 45 |         {
 46 |             File.Delete(_tempProjectFile);
 47 |         }
 48 |     }
 49 | 
 50 |     [Test]
 51 |     public async Task BuildProjectAsync_CallsExecutorFactory()
 52 |     {
 53 |         // Arrange
 54 |         var expectedResult = new BuildResult
 55 |         {
 56 |             Success = true,
 57 |             BuildTime = 5.2,
 58 |             Errors = new List<BuildMessage>(),
 59 |             Warnings = new List<BuildMessage>(),
 60 |             Output = "Build succeeded"
 61 |         };
 62 | 
 63 |         _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
 64 |                 It.IsAny<string>(),
 65 |                 It.IsAny<string>(),
 66 |                 It.IsAny<string>(),
 67 |                 It.IsAny<bool>(),
 68 |                 It.IsAny<CancellationToken>()))
 69 |             .ReturnsAsync(expectedResult);
 70 | 
 71 |         // Act
 72 |         var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);
 73 | 
 74 |         // Assert
 75 |         Assert.That(result, Is.EqualTo(expectedResult));
 76 |         _mockExecutorFactory.Verify(x => x.CreateBuildExecutor(), Times.Once);
 77 |         _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
 78 |             _tempProjectFile, "Debug", "Any CPU", true, It.IsAny<CancellationToken>()), Times.Once);
 79 |     }
 80 | 
 81 |     [Test]
 82 |     public async Task BuildProjectAsync_WithDifferentParameters_PassesToExecutor()
 83 |     {
 84 |         // Arrange
 85 |         var expectedResult = new BuildResult { Success = true };
 86 |         var configuration = "Release";
 87 |         var platform = "x64";
 88 |         var restore = false;
 89 | 
 90 |         _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
 91 |                 It.IsAny<string>(),
 92 |                 configuration,
 93 |                 platform,
 94 |                 restore,
 95 |                 It.IsAny<CancellationToken>()))
 96 |             .ReturnsAsync(expectedResult);
 97 | 
 98 |         // Act
 99 |         var result = await _service.BuildProjectAsync(_tempProjectFile, configuration, platform, restore);
100 | 
101 |         // Assert
102 |         _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
103 |             _tempProjectFile, configuration, platform, restore, It.IsAny<CancellationToken>()), Times.Once);
104 |     }
105 | 
106 |     [Test]
107 |     public async Task BuildProjectAsync_WhenExecutorThrows_ReturnsFailedResult()
108 |     {
109 |         // Arrange
110 |         _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
111 |                 It.IsAny<string>(),
112 |                 It.IsAny<string>(),
113 |                 It.IsAny<string>(),
114 |                 It.IsAny<bool>(),
115 |                 It.IsAny<CancellationToken>()))
116 |             .ThrowsAsync(new InvalidOperationException("Build executor failed"));
117 | 
118 |         // Act
119 |         var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);
120 | 
121 |         // Assert
122 |         Assert.That(result.Success, Is.False);
123 |         Assert.That(result.Errors, Has.Count.EqualTo(1));
124 |         Assert.That(result.Errors[0].Message, Does.Contain("Build executor failed"));
125 |         Assert.That(result.Output, Does.Contain("Build service failed"));
126 |     }
127 | 
128 |     [Test]
129 |     public async Task BuildProjectAsync_WhenFactoryThrows_ReturnsFailedResult()
130 |     {
131 |         // Arrange
132 |         _mockExecutorFactory.Setup(x => x.CreateBuildExecutor())
133 |             .Throws(new InvalidOperationException("Factory failed"));
134 | 
135 |         // Act
136 |         var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);
137 | 
138 |         // Assert
139 |         Assert.That(result.Success, Is.False);
140 |         Assert.That(result.Errors, Has.Count.EqualTo(1));
141 |         Assert.That(result.Errors[0].Message, Does.Contain("Factory failed"));
142 |         Assert.That(result.BuildTime, Is.EqualTo(0));
143 |     }
144 | 
145 |     [Test]
146 |     public async Task BuildProjectAsync_WithCancellationToken_PassesToExecutor()
147 |     {
148 |         // Arrange
149 |         var expectedResult = new BuildResult { Success = true };
150 |         var cancellationTokenSource = new CancellationTokenSource();
151 | 
152 |         _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
153 |                 It.IsAny<string>(),
154 |                 It.IsAny<string>(),
155 |                 It.IsAny<string>(),
156 |                 It.IsAny<bool>(),
157 |                 cancellationTokenSource.Token))
158 |             .ReturnsAsync(expectedResult);
159 | 
160 |         // Act
161 |         var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true, cancellationTokenSource.Token);
162 | 
163 |         // Assert
164 |         _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
165 |             _tempProjectFile, "Debug", "Any CPU", true, cancellationTokenSource.Token), Times.Once);
166 |     }
167 | }
```

--------------------------------------------------------------------------------
/ProjectPlan.md:
--------------------------------------------------------------------------------

```markdown
  1 | # .NET Framework MCP Service Project Plan
  2 | 
  3 | ## Project Overview
  4 | 
  5 | Build a Model Context Protocol (MCP) service that enables Claude Code to build, test, and run .NET Framework projects. The service will provide a standardized interface for interacting with MSBuild, test runners, and other .NET tooling.
  6 | 
  7 | ## Core Requirements
  8 | 
  9 | ### 1. Build Operations
 10 | - Trigger MSBuild for solutions and projects
 11 | - Support multiple build configurations (Debug/Release)
 12 | - Support multiple platforms (Any CPU, x86, x64)
 13 | - Handle NuGet package restoration
 14 | - Parse and return structured build output (errors, warnings, success status)
 15 | 
 16 | ### 2. Test Operations
 17 | - Discover test projects and test methods
 18 | - Execute all tests or specific test selections
 19 | - Support multiple test frameworks (MSTest, NUnit, xUnit)
 20 | - Parse and return test results with pass/fail counts and error details
 21 | 
 22 | ### 3. Run Operations
 23 | - Execute console applications with arguments
 24 | - Capture and return console output
 25 | - Handle process termination and timeouts
 26 | 
 27 | ### 4. Project Analysis
 28 | - List projects in a solution
 29 | - Show project dependencies
 30 | - Display project properties and configurations
 31 | - List NuGet packages and versions
 32 | 
 33 | ## Technical Architecture
 34 | 
 35 | ### Technology Stack
 36 | - **Language**: C# (.NET 6+ for the MCP service itself)
 37 | - **MCP SDK**: Use official MCP SDK for C# (if available) or implement protocol directly
 38 | - **Dependencies**:
 39 |   - Microsoft.Build for MSBuild integration
 40 |   - Microsoft.Build.Locator for finding MSBuild installations
 41 |   - Test framework APIs for test discovery/execution
 42 | 
 43 | ### MCP Methods to Implement
 44 | 
 45 | ```
 46 | tools:
 47 |   - name: build_project
 48 |     description: Build a .NET project or solution
 49 |     inputSchema:
 50 |       type: object
 51 |       properties:
 52 |         path: { type: string, description: "Path to .csproj or .sln file" }
 53 |         configuration: { type: string, enum: ["Debug", "Release"], default: "Debug" }
 54 |         platform: { type: string, enum: ["Any CPU", "x86", "x64"], default: "Any CPU" }
 55 |         restore: { type: boolean, default: true, description: "Restore NuGet packages" }
 56 | 
 57 |   - name: run_tests
 58 |     description: Run tests in a .NET test project
 59 |     inputSchema:
 60 |       type: object
 61 |       properties:
 62 |         path: { type: string, description: "Path to test project" }
 63 |         filter: { type: string, description: "Test filter expression" }
 64 |         verbose: { type: boolean, default: false }
 65 | 
 66 |   - name: run_project
 67 |     description: Execute a .NET console application
 68 |     inputSchema:
 69 |       type: object
 70 |       properties:
 71 |         path: { type: string, description: "Path to project" }
 72 |         args: { type: array, items: { type: string } }
 73 |         workingDirectory: { type: string }
 74 | 
 75 |   - name: analyze_solution
 76 |     description: Get information about a solution structure
 77 |     inputSchema:
 78 |       type: object
 79 |       properties:
 80 |         path: { type: string, description: "Path to .sln file" }
 81 | 
 82 |   - name: list_packages
 83 |     description: List NuGet packages in a project
 84 |     inputSchema:
 85 |       type: object
 86 |       properties:
 87 |         path: { type: string, description: "Path to project" }
 88 | ```
 89 | 
 90 | ## Implementation Phases
 91 | 
 92 | ### Phase 1: Core Infrastructure (Week 1) ✅ COMPLETED
 93 | - [x] Set up C# project structure
 94 | - [x] Implement MCP protocol handling with JSON-RPC support
 95 | - [x] Create basic server lifecycle (start, stop, health check)
 96 | - [x] Implement logging framework with configurable verbosity
 97 | - [x] Add configuration management with environment variable support
 98 | - [x] Switch test framework to NUnit
 99 | - [x] Create initial unit tests
100 | 
101 | ### Phase 2: Build Functionality (Week 2) ✅ COMPLETED
102 | - [x] Implement MSBuild locator logic with Visual Studio version selection
103 | - [x] Create build_project method
104 | - [x] Add build output parsing with intelligent truncation
105 | - [x] Implement error/warning extraction
106 | - [x] Add NuGet restore functionality
107 | - [x] Add TCP server support for cross-platform communication
108 | - [x] Create WSL-to-Windows bridge for Claude Code integration
109 | - [x] Implement build cancellation and timeout handling
110 | - [x] Add MCP token limit compliance (25k token limit)
111 | 
112 | ### Phase 3: Test Runner Integration (Week 3) ✅ COMPLETED
113 | - [x] Implement test discovery logic
114 | - [x] Add support for MSTest runner
115 | - [x] Add support for NUnit runner  
116 | - [x] Add support for xUnit runner
117 | - [x] Create test result parsing with TRX file support
118 | - [x] Implement test filtering
119 | - [x] Add comprehensive error message and stack trace extraction
120 | - [x] Implement test adapter discovery and integration
121 | - [x] Add solution-based building for proper test configuration
122 | 
123 | ### Phase 4: Project Execution (Week 4)
124 | - [ ] Implement run_project method
125 | - [ ] Add process management
126 | - [ ] Implement output capture
127 | - [ ] Add timeout handling
128 | - [ ] Handle process termination
129 | - [ ] Write unit tests for execution operations
130 | 
131 | ### Phase 5: Analysis Features (Week 5)
132 | - [ ] Implement solution analysis
133 | - [ ] Add project dependency mapping
134 | - [ ] Create package listing functionality
135 | - [ ] Add project property inspection
136 | - [ ] Write unit tests for analysis operations
137 | 
138 | ### Phase 6: Polish & Documentation (Week 6)
139 | - [ ] Comprehensive error handling
140 | - [ ] Performance optimization
141 | - [ ] Add integration tests
142 | - [ ] Write user documentation
143 | - [ ] Create example usage scenarios
144 | - [ ] Package for distribution
145 | 
146 | ## Key Implementation Details
147 | 
148 | ### MSBuild Integration
149 | ```csharp
150 | // Use Microsoft.Build.Locator to find MSBuild
151 | MSBuildLocator.RegisterDefaults();
152 | 
153 | // Use Microsoft.Build API for building
154 | var projectCollection = new ProjectCollection();
155 | var project = projectCollection.LoadProject(projectPath);
156 | ```
157 | 
158 | ### Output Parsing Strategy
159 | - Use MSBuild loggers to capture structured output
160 | - Implement custom logger for JSON-formatted results
161 | - Parse compiler error format: `file(line,col): error CODE: message`
162 | 
163 | ### Test Runner Integration
164 | - Use VSTest.Console.exe for universal test execution
165 | - Parse TRX files for structured results
166 | - Support test filtering using standard syntax
167 | 
168 | ### Error Handling
169 | - Graceful handling of missing MSBuild installations
170 | - Clear error messages for missing dependencies
171 | - Timeout handling for long-running operations
172 | - Process cleanup on service shutdown
173 | 
174 | ## Configuration Schema
175 | 
176 | ```json
177 | {
178 |   "McpServer": {
179 |     "MsBuildPath": "auto",
180 |     "DefaultConfiguration": "Debug",
181 |     "DefaultPlatform": "Any CPU",
182 |     "TestTimeout": 300000,
183 |     "BuildTimeout": 1200000,
184 |     "EnableDetailedLogging": false,
185 |     "PreferredVSVersion": "2022"
186 |   }
187 | }
188 | ```
189 | 
190 | ## Testing Strategy
191 | 
192 | ### Unit Tests
193 | - Mock MSBuild API calls
194 | - Test output parsing logic
195 | - Verify error handling paths
196 | - Test configuration management
197 | 
198 | ### Integration Tests
199 | - Use sample .NET projects
200 | - Test full build/test/run cycles
201 | - Verify cross-framework compatibility
202 | - Test error scenarios (missing files, bad syntax)
203 | 
204 | ## Distribution Plan
205 | 
206 | 1. **NuGet Package**: Primary distribution as a .NET tool
207 | 2. **GitHub Releases**: Compiled binaries with installation script
208 | 3. **Docker Image**: Containerized version with MSBuild pre-installed
209 | 
210 | ## Success Criteria
211 | 
212 | - Successfully builds complex multi-project solutions
213 | - Accurately reports build errors and warnings
214 | - Runs tests from all major test frameworks
215 | - Provides clear, actionable error messages
216 | - Performs operations within reasonable time limits
217 | - Maintains stability during long-running operations
218 | 
219 | ## Future Enhancements
220 | 
221 | ### Security & Authentication
222 | - API key or token-based authentication
223 | - Role-based access control for different operations
224 | - Audit logging for security compliance
225 | - Network access restrictions and whitelisting
226 | 
227 | ### Extended Platform Support
228 | - Support for .NET Core/.NET 5+ projects
229 | - Web project launching with browser integration
230 | - Support for F# and VB.NET projects
231 | 
232 | ### Advanced Development Features
233 | - Code coverage reporting
234 | - Incremental build support
235 | - Watch mode for continuous building/testing
236 | - Integration with code analyzers
237 | - Performance profiling and diagnostics
238 | 
239 | ## Resources & References
240 | 
241 | - [MCP Specification](https://modelcontextprotocol.io/docs)
242 | - [MSBuild API Documentation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.build)
243 | - [VSTest Documentation](https://docs.microsoft.com/en-us/visualstudio/test/vstest-console-options)
244 | - [NuGet API Reference](https://docs.microsoft.com/en-us/nuget/api/overview)
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/BaseTestExecutor.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Diagnostics;
  2 | using System.Text;
  3 | using System.Xml.Linq;
  4 | using DotNetFrameworkMCP.Server.Configuration;
  5 | using DotNetFrameworkMCP.Server.Models;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Executors;
 10 | 
 11 | /// <summary>
 12 | /// Base class for test executors with common functionality
 13 | /// </summary>
 14 | public abstract class BaseTestExecutor : ITestExecutor
 15 | {
 16 |     protected readonly ILogger _logger;
 17 |     protected readonly McpServerConfiguration _configuration;
 18 | 
 19 |     protected BaseTestExecutor(ILogger logger, IOptions<McpServerConfiguration> configuration)
 20 |     {
 21 |         _logger = logger;
 22 |         _configuration = configuration.Value;
 23 |     }
 24 | 
 25 |     public abstract Task<TestResult> ExecuteTestsAsync(
 26 |         string projectPath,
 27 |         string? filter,
 28 |         bool verbose,
 29 |         CancellationToken cancellationToken = default);
 30 | 
 31 |     protected async Task<(string Output, int ExitCode)> RunProcessAsync(
 32 |         string fileName,
 33 |         string arguments,
 34 |         CancellationToken cancellationToken)
 35 |     {
 36 |         _logger.LogDebug("Running: {FileName} {Arguments}", fileName, arguments);
 37 | 
 38 |         using var process = new Process();
 39 |         process.StartInfo.FileName = fileName;
 40 |         process.StartInfo.Arguments = arguments;
 41 |         process.StartInfo.UseShellExecute = false;
 42 |         process.StartInfo.RedirectStandardOutput = true;
 43 |         process.StartInfo.RedirectStandardError = true;
 44 |         process.StartInfo.CreateNoWindow = true;
 45 | 
 46 |         var output = new StringBuilder();
 47 |         process.OutputDataReceived += (sender, e) =>
 48 |         {
 49 |             if (e.Data != null)
 50 |             {
 51 |                 output.AppendLine(e.Data);
 52 |             }
 53 |         };
 54 | 
 55 |         process.ErrorDataReceived += (sender, e) =>
 56 |         {
 57 |             if (e.Data != null)
 58 |             {
 59 |                 output.AppendLine(e.Data);
 60 |             }
 61 |         };
 62 | 
 63 |         process.Start();
 64 |         process.BeginOutputReadLine();
 65 |         process.BeginErrorReadLine();
 66 | 
 67 |         // Wait for completion with timeout and cancellation support
 68 |         var timeoutMs = _configuration.TestTimeout;
 69 |         using var timeoutCts = new CancellationTokenSource(timeoutMs);
 70 |         using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
 71 | 
 72 |         try
 73 |         {
 74 |             await process.WaitForExitAsync(combinedCts.Token);
 75 |         }
 76 |         catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
 77 |         {
 78 |             _logger.LogWarning("Test execution timed out after {TimeoutMs}ms, killing process", timeoutMs);
 79 |             process.Kill();
 80 |             throw new TimeoutException($"Test execution timed out after {timeoutMs}ms");
 81 |         }
 82 |         catch (OperationCanceledException)
 83 |         {
 84 |             _logger.LogInformation("Test execution cancelled by user, killing process");
 85 |             process.Kill();
 86 |             throw;
 87 |         }
 88 | 
 89 |         return (output.ToString(), process.ExitCode);
 90 |     }
 91 | 
 92 |     protected async Task<TestResult> ParseTrxFileAsync(string trxFilePath, string consoleOutput)
 93 |     {
 94 |         var testDetails = new List<TestDetail>();
 95 | 
 96 |         try
 97 |         {
 98 |             if (File.Exists(trxFilePath))
 99 |             {
100 |                 var trxContent = await File.ReadAllTextAsync(trxFilePath);
101 |                 _logger.LogDebug("Parsing TRX file: {TrxFilePath}", trxFilePath);
102 | 
103 |                 var doc = XDocument.Parse(trxContent);
104 |                 var ns = doc.Root?.GetDefaultNamespace();
105 | 
106 |                 if (ns != null)
107 |                 {
108 |                     // Parse test results
109 |                     var unitTestResults = doc.Descendants(ns + "UnitTestResult");
110 | 
111 |                     foreach (var result in unitTestResults)
112 |                     {
113 |                         var testId = result.Attribute("testId")?.Value;
114 |                         var testName = result.Attribute("testName")?.Value ?? "Unknown Test";
115 |                         var outcome = result.Attribute("outcome")?.Value ?? "Unknown";
116 |                         var duration = result.Attribute("duration")?.Value;
117 | 
118 |                         // Try to find the test definition to get class information
119 |                         var className = "Unknown";
120 |                         if (!string.IsNullOrEmpty(testId))
121 |                         {
122 |                             var testDefinition = doc.Descendants(ns + "UnitTest")
123 |                                 .FirstOrDefault(t => t.Attribute("id")?.Value == testId);
124 | 
125 |                             if (testDefinition != null)
126 |                             {
127 |                                 var testMethod = testDefinition.Descendants(ns + "TestMethod").FirstOrDefault();
128 |                                 className = testMethod?.Attribute("className")?.Value ?? "Unknown";
129 |                             }
130 |                         }
131 | 
132 |                         // Parse duration
133 |                         var durationSeconds = 0.0;
134 |                         if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var durationTimeSpan))
135 |                         {
136 |                             durationSeconds = durationTimeSpan.TotalSeconds;
137 |                         }
138 | 
139 |                         // Get error message and stack trace for failed tests
140 |                         string? errorMessage = null;
141 |                         string? stackTrace = null;
142 | 
143 |                         if (outcome == "Failed")
144 |                         {
145 |                             var output = result.Element(ns + "Output");
146 |                             var errorInfo = output?.Element(ns + "ErrorInfo");
147 |                             errorMessage = errorInfo?.Element(ns + "Message")?.Value;
148 |                             stackTrace = errorInfo?.Element(ns + "StackTrace")?.Value;
149 |                         }
150 | 
151 |                         testDetails.Add(new TestDetail
152 |                         {
153 |                             Name = testName,
154 |                             ClassName = className,
155 |                             Result = outcome,
156 |                             Duration = durationSeconds,
157 |                             ErrorMessage = errorMessage,
158 |                             StackTrace = stackTrace
159 |                         });
160 |                     }
161 |                 }
162 |             }
163 | 
164 |             // If no tests were parsed from TRX, fall back to console output parsing
165 |             if (testDetails.Count == 0)
166 |             {
167 |                 _logger.LogWarning("No tests found in TRX file, falling back to console output parsing");
168 |                 ParseConsoleOutput(consoleOutput, testDetails);
169 |             }
170 |         }
171 |         catch (Exception ex)
172 |         {
173 |             _logger.LogError(ex, "Error parsing TRX file: {TrxFilePath}", trxFilePath);
174 |             // Fall back to console output parsing
175 |             ParseConsoleOutput(consoleOutput, testDetails);
176 |         }
177 | 
178 |         return new TestResult
179 |         {
180 |             TotalTests = testDetails.Count,
181 |             PassedTests = testDetails.Count(t => t.Result == "Passed"),
182 |             FailedTests = testDetails.Count(t => t.Result == "Failed"),
183 |             SkippedTests = testDetails.Count(t => t.Result == "Skipped"),
184 |             TestDetails = testDetails,
185 |             Output = consoleOutput
186 |         };
187 |     }
188 | 
189 |     private void ParseConsoleOutput(string output, List<TestDetail> testDetails)
190 |     {
191 |         if (string.IsNullOrEmpty(output))
192 |             return;
193 | 
194 |         var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
195 |         
196 |         foreach (var line in lines)
197 |         {
198 |             // Simple pattern matching for common test output formats
199 |             if (line.Contains("Passed") || line.Contains("Failed") || line.Contains("Skipped"))
200 |             {
201 |                 var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
202 |                 if (parts.Length >= 2)
203 |                 {
204 |                     var result = parts.FirstOrDefault(p => p == "Passed" || p == "Failed" || p == "Skipped");
205 |                     if (result != null)
206 |                     {
207 |                         testDetails.Add(new TestDetail
208 |                         {
209 |                             Name = line.Trim(),
210 |                             ClassName = "ParsedFromConsole",
211 |                             Result = result,
212 |                             Duration = 0,
213 |                             ErrorMessage = result == "Failed" ? "Failed (parsed from console output)" : null,
214 |                             StackTrace = null
215 |                         });
216 |                     }
217 |                 }
218 |             }
219 |         }
220 |     }
221 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Services/TcpMcpServer.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Net;
  2 | using System.Net.Sockets;
  3 | using System.Text;
  4 | using System.Text.Json;
  5 | using DotNetFrameworkMCP.Server.Configuration;
  6 | using DotNetFrameworkMCP.Server.Protocol;
  7 | using DotNetFrameworkMCP.Server.Tools;
  8 | using Microsoft.Extensions.Logging;
  9 | using Microsoft.Extensions.Options;
 10 | 
 11 | namespace DotNetFrameworkMCP.Server.Services;
 12 | 
 13 | public class TcpMcpServer
 14 | {
 15 |     private readonly ILogger<TcpMcpServer> _logger;
 16 |     private readonly McpServerConfiguration _configuration;
 17 |     private readonly Dictionary<string, IToolHandler> _toolHandlers;
 18 |     private readonly JsonSerializerOptions _jsonOptions;
 19 |     private TcpListener? _listener;
 20 | 
 21 |     public TcpMcpServer(
 22 |         ILogger<TcpMcpServer> logger,
 23 |         IOptions<McpServerConfiguration> configuration,
 24 |         IEnumerable<IToolHandler> toolHandlers)
 25 |     {
 26 |         _logger = logger;
 27 |         _configuration = configuration.Value;
 28 |         _toolHandlers = toolHandlers.ToDictionary(h => h.Name);
 29 |         _jsonOptions = new JsonSerializerOptions
 30 |         {
 31 |             PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 32 |             WriteIndented = false
 33 |         };
 34 |     }
 35 | 
 36 |     public async Task RunAsync(int port = 3001, CancellationToken cancellationToken = default)
 37 |     {
 38 |         _listener = new TcpListener(IPAddress.Any, port);
 39 |         _listener.Start();
 40 |         
 41 |         _logger.LogInformation("TCP MCP Server listening on port {Port}", port);
 42 |         
 43 |         // Register cancellation callback to stop the listener
 44 |         using var registration = cancellationToken.Register(() =>
 45 |         {
 46 |             _logger.LogInformation("Cancellation requested, stopping TCP listener");
 47 |             _listener?.Stop();
 48 |         });
 49 | 
 50 |         try
 51 |         {
 52 |             while (!cancellationToken.IsCancellationRequested)
 53 |             {
 54 |                 var client = await _listener.AcceptTcpClientAsync().WaitAsync(cancellationToken);
 55 |                 _logger.LogInformation("Client connected from {RemoteEndPoint}", client.Client.RemoteEndPoint);
 56 |                 
 57 |                 // Handle each client in a separate task
 58 |                 _ = Task.Run(async () => await HandleClientAsync(client, cancellationToken), cancellationToken);
 59 |             }
 60 |         }
 61 |         catch (OperationCanceledException)
 62 |         {
 63 |             // Expected when cancellation occurs
 64 |             _logger.LogInformation("TCP server shutdown requested");
 65 |         }
 66 |         catch (ObjectDisposedException)
 67 |         {
 68 |             // Expected when cancellation occurs
 69 |         }
 70 |         finally
 71 |         {
 72 |             _listener?.Stop();
 73 |             _listener = null;
 74 |             _logger.LogInformation("TCP MCP Server stopped");
 75 |         }
 76 |     }
 77 | 
 78 |     private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
 79 |     {
 80 |         try
 81 |         {
 82 |             using (client)
 83 |             {
 84 |                 var stream = client.GetStream();
 85 |                 var buffer = new byte[4096];
 86 |                 var messageBuffer = new List<byte>();
 87 | 
 88 |                 while (!cancellationToken.IsCancellationRequested && client.Connected)
 89 |                 {
 90 |                     var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
 91 |                     if (bytesRead == 0)
 92 |                         break;
 93 | 
 94 |                     messageBuffer.AddRange(buffer.Take(bytesRead));
 95 | 
 96 |                     // Process complete messages (look for newline delimiters)
 97 |                     while (true)
 98 |                     {
 99 |                         var newlineIndex = messageBuffer.IndexOf((byte)'\n');
100 |                         if (newlineIndex == -1)
101 |                             break;
102 | 
103 |                         var messageBytes = messageBuffer.Take(newlineIndex).ToArray();
104 |                         messageBuffer.RemoveRange(0, newlineIndex + 1);
105 | 
106 |                         if (messageBytes.Length > 0)
107 |                         {
108 |                             var response = await ProcessMessageAsync(messageBytes);
109 |                             if (response != null)
110 |                             {
111 |                                 var responseJson = JsonSerializer.Serialize(response, _jsonOptions);
112 |                                 var responseBytes = Encoding.UTF8.GetBytes(responseJson + "\n");
113 |                                 await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken);
114 |                                 await stream.FlushAsync(cancellationToken);
115 |                             }
116 |                         }
117 |                     }
118 |                 }
119 |             }
120 |         }
121 |         catch (Exception ex)
122 |         {
123 |             _logger.LogError(ex, "Error handling client");
124 |         }
125 |     }
126 | 
127 |     private async Task<McpMessage?> ProcessMessageAsync(byte[] messageBytes)
128 |     {
129 |         try
130 |         {
131 |             var message = JsonSerializer.Deserialize<McpMessage>(messageBytes, _jsonOptions);
132 |             if (message == null)
133 |             {
134 |                 return CreateErrorResponse(null, McpErrorCodes.ParseError, "Failed to parse message");
135 |             }
136 | 
137 |             _logger.LogDebug("Received message: {Method}", message.Method);
138 | 
139 |             // Handle different MCP methods
140 |             switch (message.Method)
141 |             {
142 |                 case "initialize":
143 |                     return await HandleInitializeAsync(message);
144 |                 
145 |                 case "tools/list":
146 |                     return await HandleToolsListAsync(message);
147 |                 
148 |                 case "tools/call":
149 |                     return await HandleToolCallAsync(message);
150 |                 
151 |                 case "notifications/cancelled":
152 |                     // Handle cancellation notification from client
153 |                     _logger.LogInformation("Received cancellation notification from client");
154 |                     return null; // Notifications don't require responses
155 |                 
156 |                 case null when message.Id != null:
157 |                     // This is likely a response to a request we made
158 |                     return null;
159 |                 
160 |                 default:
161 |                     _logger.LogWarning("Unknown method received: {Method}", message.Method);
162 |                     return CreateErrorResponse(
163 |                         message.Id,
164 |                         McpErrorCodes.MethodNotFound,
165 |                         $"Method '{message.Method}' not found");
166 |             }
167 |         }
168 |         catch (JsonException ex)
169 |         {
170 |             _logger.LogError(ex, "Failed to parse JSON message");
171 |             return CreateErrorResponse(null, McpErrorCodes.ParseError, "Invalid JSON");
172 |         }
173 |         catch (Exception ex)
174 |         {
175 |             _logger.LogError(ex, "Error processing message");
176 |             return CreateErrorResponse(null, McpErrorCodes.InternalError, ex.Message);
177 |         }
178 |     }
179 | 
180 |     private Task<McpMessage> HandleInitializeAsync(McpMessage request)
181 |     {
182 |         var response = new McpMessage
183 |         {
184 |             Id = request.Id,
185 |             Result = new
186 |             {
187 |                 protocolVersion = "2025-06-18",
188 |                 capabilities = new
189 |                 {
190 |                     tools = new { }
191 |                 },
192 |                 serverInfo = new
193 |                 {
194 |                     name = "dotnet-framework-mcp-tcp",
195 |                     version = "1.0.0"
196 |                 }
197 |             }
198 |         };
199 | 
200 |         return Task.FromResult(response);
201 |     }
202 | 
203 |     private Task<McpMessage> HandleToolsListAsync(McpMessage request)
204 |     {
205 |         var tools = _toolHandlers.Values.Select(h => h.GetDefinition()).ToList();
206 |         
207 |         var response = new McpMessage
208 |         {
209 |             Id = request.Id,
210 |             Result = new { tools }
211 |         };
212 | 
213 |         return Task.FromResult(response);
214 |     }
215 | 
216 |     private async Task<McpMessage> HandleToolCallAsync(McpMessage request)
217 |     {
218 |         try
219 |         {
220 |             var toolCallParams = JsonSerializer.Deserialize<ToolCallParams>(
221 |                 JsonSerializer.Serialize(request.Params),
222 |                 _jsonOptions);
223 | 
224 |             if (toolCallParams == null || string.IsNullOrEmpty(toolCallParams.Name))
225 |             {
226 |                 return CreateErrorResponse(
227 |                     request.Id,
228 |                     McpErrorCodes.InvalidParams,
229 |                     "Invalid tool call parameters");
230 |             }
231 | 
232 |             if (!_toolHandlers.TryGetValue(toolCallParams.Name, out var handler))
233 |             {
234 |                 return CreateErrorResponse(
235 |                     request.Id,
236 |                     McpErrorCodes.InvalidParams,
237 |                     $"Tool '{toolCallParams.Name}' not found");
238 |             }
239 | 
240 |             var result = await handler.ExecuteAsync(toolCallParams.Arguments);
241 | 
242 |             return new McpMessage
243 |             {
244 |                 Id = request.Id,
245 |                 Result = new
246 |                 {
247 |                     content = new[]
248 |                     {
249 |                         new
250 |                         {
251 |                             type = "text",
252 |                             text = JsonSerializer.Serialize(result, _jsonOptions)
253 |                         }
254 |                     }
255 |                 }
256 |             };
257 |         }
258 |         catch (Exception ex)
259 |         {
260 |             _logger.LogError(ex, "Error executing tool");
261 |             return CreateErrorResponse(
262 |                 request.Id,
263 |                 McpErrorCodes.InternalError,
264 |                 $"Tool execution failed: {ex.Message}");
265 |         }
266 |     }
267 | 
268 |     private McpMessage CreateErrorResponse(object? id, int code, string message)
269 |     {
270 |         return new McpMessage
271 |         {
272 |             Id = id,
273 |             Error = new McpError
274 |             {
275 |                 Code = code,
276 |                 Message = message
277 |             }
278 |         };
279 |     }
280 | 
281 |     private class ToolCallParams
282 |     {
283 |         public string Name { get; set; } = string.Empty;
284 |         public JsonElement Arguments { get; set; }
285 |     }
286 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/DotNetBuildExecutor.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Diagnostics;
  2 | using System.Text;
  3 | using System.Text.RegularExpressions;
  4 | using DotNetFrameworkMCP.Server.Configuration;
  5 | using DotNetFrameworkMCP.Server.Models;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Executors;
 10 | 
 11 | /// <summary>
 12 | /// dotnet CLI-based build executor
 13 | /// </summary>
 14 | public class DotNetBuildExecutor : IBuildExecutor
 15 | {
 16 |     private readonly ILogger<DotNetBuildExecutor> _logger;
 17 |     private readonly McpServerConfiguration _configuration;
 18 | 
 19 |     public DotNetBuildExecutor(
 20 |         ILogger<DotNetBuildExecutor> logger,
 21 |         IOptions<McpServerConfiguration> configuration)
 22 |     {
 23 |         _logger = logger;
 24 |         _configuration = configuration.Value;
 25 |     }
 26 | 
 27 |     public async Task<BuildResult> ExecuteBuildAsync(
 28 |         string projectPath,
 29 |         string configuration,
 30 |         string platform,
 31 |         bool restore,
 32 |         CancellationToken cancellationToken = default)
 33 |     {
 34 |         var stopwatch = Stopwatch.StartNew();
 35 |         var errors = new List<BuildMessage>();
 36 |         var warnings = new List<BuildMessage>();
 37 | 
 38 |         try
 39 |         {
 40 |             // Validate project path
 41 |             if (!File.Exists(projectPath))
 42 |             {
 43 |                 throw new FileNotFoundException($"Project file not found: {projectPath}");
 44 |             }
 45 | 
 46 |             _logger.LogInformation("Using dotnet CLI for build");
 47 | 
 48 |             // Build the project using dotnet CLI
 49 |             var result = await RunDotNetBuildAsync(projectPath, configuration, platform, restore, cancellationToken);
 50 |             
 51 |             // Parse the output for errors and warnings
 52 |             ParseBuildOutput(result.Output, errors, warnings);
 53 | 
 54 |             stopwatch.Stop();
 55 | 
 56 |             return new BuildResult
 57 |             {
 58 |                 Success = result.ExitCode == 0,
 59 |                 Errors = errors,
 60 |                 Warnings = warnings,
 61 |                 BuildTime = stopwatch.Elapsed.TotalSeconds,
 62 |                 Output = TruncateOutput(result.Output, result.ExitCode != 0)
 63 |             };
 64 |         }
 65 |         catch (Exception ex)
 66 |         {
 67 |             _logger.LogError(ex, "Build failed for project: {ProjectPath}", projectPath);
 68 |             stopwatch.Stop();
 69 | 
 70 |             return new BuildResult
 71 |             {
 72 |                 Success = false,
 73 |                 Errors = new List<BuildMessage>
 74 |                 {
 75 |                     new BuildMessage
 76 |                     {
 77 |                         Message = ex.Message,
 78 |                         File = projectPath
 79 |                     }
 80 |                 },
 81 |                 Warnings = warnings,
 82 |                 BuildTime = stopwatch.Elapsed.TotalSeconds,
 83 |                 Output = TruncateOutput($"Build failed with exception: {ex.Message}\n{ex.StackTrace}", true)
 84 |             };
 85 |         }
 86 |     }
 87 | 
 88 |     private async Task<(int ExitCode, string Output)> RunDotNetBuildAsync(
 89 |         string projectPath,
 90 |         string configuration,
 91 |         string platform,
 92 |         bool restore,
 93 |         CancellationToken cancellationToken)
 94 |     {
 95 |         var arguments = new List<string>
 96 |         {
 97 |             "build",
 98 |             $"\"{projectPath}\"",
 99 |             $"--configuration", configuration,
100 |             "--verbosity", "normal"
101 |         };
102 | 
103 |         // Platform is typically handled differently in dotnet CLI
104 |         // For .NET Framework projects, it's often part of the runtime identifier
105 |         if (!string.IsNullOrEmpty(platform) && platform != "Any CPU")
106 |         {
107 |             arguments.Add($"-p:Platform=\"{platform}\"");
108 |         }
109 | 
110 |         if (!restore)
111 |         {
112 |             arguments.Add("--no-restore");
113 |         }
114 | 
115 |         var argumentString = string.Join(" ", arguments);
116 |         _logger.LogDebug("Running: {DotNetPath} {Arguments}", _configuration.DotNetPath, argumentString);
117 | 
118 |         var psi = new ProcessStartInfo
119 |         {
120 |             FileName = _configuration.DotNetPath,
121 |             Arguments = argumentString,
122 |             UseShellExecute = false,
123 |             RedirectStandardOutput = true,
124 |             RedirectStandardError = true,
125 |             CreateNoWindow = true,
126 |             WorkingDirectory = Path.GetDirectoryName(projectPath) ?? Environment.CurrentDirectory
127 |         };
128 | 
129 |         var output = new StringBuilder();
130 | 
131 |         using var process = new Process { StartInfo = psi };
132 |         
133 |         process.OutputDataReceived += (sender, e) =>
134 |         {
135 |             if (e.Data != null)
136 |             {
137 |                 output.AppendLine(e.Data);
138 |             }
139 |         };
140 | 
141 |         process.ErrorDataReceived += (sender, e) =>
142 |         {
143 |             if (e.Data != null)
144 |             {
145 |                 output.AppendLine(e.Data);
146 |             }
147 |         };
148 | 
149 |         try
150 |         {
151 |             process.Start();
152 |         }
153 |         catch (Exception ex)
154 |         {
155 |             throw new InvalidOperationException($"Failed to start dotnet CLI. Make sure .NET SDK is installed and '{_configuration.DotNetPath}' is in PATH.", ex);
156 |         }
157 | 
158 |         process.BeginOutputReadLine();
159 |         process.BeginErrorReadLine();
160 | 
161 |         // Wait for completion with timeout and cancellation support
162 |         var timeoutMs = _configuration.BuildTimeout;
163 |         using var timeoutCts = new CancellationTokenSource(timeoutMs);
164 |         using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
165 |         
166 |         try
167 |         {
168 |             await process.WaitForExitAsync(combinedCts.Token);
169 |         }
170 |         catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
171 |         {
172 |             _logger.LogWarning("Build timed out after {TimeoutMs}ms, killing process", timeoutMs);
173 |             process.Kill();
174 |             throw new TimeoutException($"Build timed out after {timeoutMs}ms");
175 |         }
176 |         catch (OperationCanceledException)
177 |         {
178 |             _logger.LogInformation("Build cancelled by user, killing process");
179 |             process.Kill();
180 |             throw;
181 |         }
182 | 
183 |         return (process.ExitCode, output.ToString());
184 |     }
185 | 
186 |     private void ParseBuildOutput(string output, List<BuildMessage> errors, List<BuildMessage> warnings)
187 |     {
188 |         if (string.IsNullOrEmpty(output))
189 |             return;
190 | 
191 |         var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
192 | 
193 |         // Regex patterns for dotnet build error and warning messages
194 |         var errorPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
195 |         var warningPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+warning\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
196 |         var generalErrorPattern = new Regex(@"^(.+?):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
197 | 
198 |         foreach (var line in lines)
199 |         {
200 |             var trimmedLine = line.Trim();
201 |             
202 |             // Try specific error pattern first (file with line/column)
203 |             var errorMatch = errorPattern.Match(trimmedLine);
204 |             if (errorMatch.Success)
205 |             {
206 |                 errors.Add(new BuildMessage
207 |                 {
208 |                     File = errorMatch.Groups[1].Value,
209 |                     Line = int.TryParse(errorMatch.Groups[2].Value, out var errorLine) ? errorLine : 0,
210 |                     Column = int.TryParse(errorMatch.Groups[3].Value, out var errorCol) ? errorCol : 0,
211 |                     Code = errorMatch.Groups[4].Value,
212 |                     Message = errorMatch.Groups[5].Value
213 |                 });
214 |                 continue;
215 |             }
216 | 
217 |             // Try warning pattern
218 |             var warningMatch = warningPattern.Match(trimmedLine);
219 |             if (warningMatch.Success)
220 |             {
221 |                 warnings.Add(new BuildMessage
222 |                 {
223 |                     File = warningMatch.Groups[1].Value,
224 |                     Line = int.TryParse(warningMatch.Groups[2].Value, out var warningLine) ? warningLine : 0,
225 |                     Column = int.TryParse(warningMatch.Groups[3].Value, out var warningCol) ? warningCol : 0,
226 |                     Code = warningMatch.Groups[4].Value,
227 |                     Message = warningMatch.Groups[5].Value
228 |                 });
229 |                 continue;
230 |             }
231 | 
232 |             // Try general error pattern (no line/column)
233 |             var generalErrorMatch = generalErrorPattern.Match(trimmedLine);
234 |             if (generalErrorMatch.Success)
235 |             {
236 |                 errors.Add(new BuildMessage
237 |                 {
238 |                     File = generalErrorMatch.Groups[1].Value,
239 |                     Code = generalErrorMatch.Groups[2].Value,
240 |                     Message = generalErrorMatch.Groups[3].Value
241 |                 });
242 |             }
243 |         }
244 |     }
245 | 
246 |     private string TruncateOutput(string output, bool isFailed)
247 |     {
248 |         const int maxChars = 15000; // Conservative limit to stay under 25k tokens
249 |         
250 |         if (string.IsNullOrEmpty(output) || output.Length <= maxChars)
251 |         {
252 |             return output;
253 |         }
254 | 
255 |         if (isFailed)
256 |         {
257 |             // For failed builds, prioritize the end of the output (where errors typically appear)
258 |             var lines = output.Split('\n');
259 |             var importantLines = new List<string>();
260 |             var currentLength = 0;
261 |             
262 |             // Add summary line if present
263 |             for (int i = 0; i < Math.Min(10, lines.Length); i++)
264 |             {
265 |                 if (lines[i].Contains("Build FAILED") || lines[i].Contains("error") || lines[i].Contains("Error"))
266 |                 {
267 |                     importantLines.Add(lines[i]);
268 |                     currentLength += lines[i].Length + 1;
269 |                     break;
270 |                 }
271 |             }
272 |             
273 |             // Add errors from the end
274 |             for (int i = lines.Length - 1; i >= 0 && currentLength < maxChars - 100; i--)
275 |             {
276 |                 var line = lines[i];
277 |                 if (currentLength + line.Length + 1 > maxChars - 100) break;
278 |                 
279 |                 if (line.Contains("error") || line.Contains("Error") || 
280 |                     line.Contains("warning") || line.Contains("Warning") ||
281 |                     line.Contains("Build FAILED") || line.Contains("Time Elapsed"))
282 |                 {
283 |                     importantLines.Insert(importantLines.Count == 0 ? 0 : 1, line);
284 |                     currentLength += line.Length + 1;
285 |                 }
286 |             }
287 |             
288 |             var result = string.Join("\n", importantLines);
289 |             if (result.Length < maxChars - 200)
290 |             {
291 |                 // Add some context from the end
292 |                 var remaining = maxChars - result.Length - 100;
293 |                 var endPortion = output.Substring(Math.Max(0, output.Length - remaining));
294 |                 result += "\n...\n" + endPortion;
295 |             }
296 |             
297 |             return $"[Output truncated - showing errors and summary]\n{result}";
298 |         }
299 |         else
300 |         {
301 |             // For successful builds, show beginning and end
302 |             var halfMax = maxChars / 2 - 50;
303 |             var start = output.Substring(0, Math.Min(halfMax, output.Length));
304 |             var end = output.Length > halfMax ? output.Substring(output.Length - halfMax) : "";
305 |             
306 |             return start + "\n\n[... middle portion truncated ...]\n\n" + end;
307 |         }
308 |     }
309 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/VSTestExecutor.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Text;
  2 | using DotNetFrameworkMCP.Server.Configuration;
  3 | using DotNetFrameworkMCP.Server.Models;
  4 | using Microsoft.Extensions.Logging;
  5 | using Microsoft.Extensions.Options;
  6 | 
  7 | namespace DotNetFrameworkMCP.Server.Executors;
  8 | 
  9 | /// <summary>
 10 | /// VSTest.Console.exe-based test executor
 11 | /// </summary>
 12 | public class VSTestExecutor : BaseTestExecutor
 13 | {
 14 |     public VSTestExecutor(
 15 |         ILogger<VSTestExecutor> logger,
 16 |         IOptions<McpServerConfiguration> configuration)
 17 |         : base(logger, configuration)
 18 |     {
 19 |     }
 20 | 
 21 |     public override async Task<TestResult> ExecuteTestsAsync(
 22 |         string projectPath,
 23 |         string? filter,
 24 |         bool verbose,
 25 |         CancellationToken cancellationToken = default)
 26 |     {
 27 |         var stopwatch = System.Diagnostics.Stopwatch.StartNew();
 28 | 
 29 |         try
 30 |         {
 31 |             // Validate project path
 32 |             if (!File.Exists(projectPath))
 33 |             {
 34 |                 throw new FileNotFoundException($"Test project file not found: {projectPath}");
 35 |             }
 36 | 
 37 |             var vstestPath = FindVSTestConsoleExecutable();
 38 |             if (string.IsNullOrEmpty(vstestPath))
 39 |             {
 40 |                 throw new InvalidOperationException("VSTest.Console.exe not found. Please install Visual Studio or Build Tools.");
 41 |             }
 42 | 
 43 |             // Find test assembly
 44 |             var testAssembly = FindTestAssembly(projectPath);
 45 |             if (string.IsNullOrEmpty(testAssembly))
 46 |             {
 47 |                 return new TestResult
 48 |                 {
 49 |                     TotalTests = 0,
 50 |                     PassedTests = 0,
 51 |                     FailedTests = 0,
 52 |                     SkippedTests = 0,
 53 |                     Duration = 0,
 54 |                     TestDetails = new List<TestDetail>
 55 |                     {
 56 |                         new TestDetail
 57 |                         {
 58 |                             Name = "Assembly Not Found",
 59 |                             ClassName = "VSTest",
 60 |                             Result = "Failed",
 61 |                             Duration = 0,
 62 |                             ErrorMessage = $"No test assembly found for project {Path.GetFileNameWithoutExtension(projectPath)}",
 63 |                             StackTrace = null
 64 |                         }
 65 |                     },
 66 |                     Output = $"No test assemblies found for project {projectPath} in bin directories"
 67 |                 };
 68 |             }
 69 | 
 70 |             _logger.LogInformation("Running tests from assembly: {TestAssembly}", testAssembly);
 71 | 
 72 |             var args = new StringBuilder($"\"{testAssembly}\"");
 73 | 
 74 |             if (!string.IsNullOrEmpty(filter))
 75 |             {
 76 |                 args.Append($" /TestCaseFilter:\"{filter}\"");
 77 |             }
 78 | 
 79 |             // Always use detailed console output to capture error messages
 80 |             args.Append(" /logger:console;verbosity=detailed");
 81 | 
 82 |             // Try to find test adapters for better framework support
 83 |             var testAdapterPath = FindTestAdapterPath(projectPath);
 84 |             if (!string.IsNullOrEmpty(testAdapterPath))
 85 |             {
 86 |                 args.Append($" /TestAdapterPath:\"{testAdapterPath}\"");
 87 |                 _logger.LogDebug("Using test adapter path: {TestAdapterPath}", testAdapterPath);
 88 |             }
 89 | 
 90 |             // Add TRX logger for structured output
 91 |             var trxFileName = $"TestResults_{Guid.NewGuid():N}.trx";
 92 |             var trxFilePath = Path.Combine(Path.GetTempPath(), trxFileName);
 93 |             args.Append($" /logger:trx;LogFileName=\"{trxFilePath}\"");
 94 | 
 95 |             var result = await RunProcessAsync(vstestPath, args.ToString(), cancellationToken);
 96 |             
 97 |             // Parse results from TRX file
 98 |             var testResult = await ParseTrxFileAsync(trxFilePath, result.Output);
 99 |             
100 |             // Clean up TRX file
101 |             try
102 |             {
103 |                 if (File.Exists(trxFilePath))
104 |                     File.Delete(trxFilePath);
105 |             }
106 |             catch (Exception ex)
107 |             {
108 |                 _logger.LogDebug("Failed to delete TRX file: {Error}", ex.Message);
109 |             }
110 | 
111 |             stopwatch.Stop();
112 |             testResult.Duration = stopwatch.Elapsed.TotalSeconds;
113 |             
114 |             return testResult;
115 |         }
116 |         catch (Exception ex)
117 |         {
118 |             _logger.LogError(ex, "Error running VSTest for project: {ProjectPath}", projectPath);
119 |             stopwatch.Stop();
120 | 
121 |             return new TestResult
122 |             {
123 |                 TotalTests = 0,
124 |                 PassedTests = 0,
125 |                 FailedTests = 0,
126 |                 SkippedTests = 0,
127 |                 Duration = stopwatch.Elapsed.TotalSeconds,
128 |                 TestDetails = new List<TestDetail>
129 |                 {
130 |                     new TestDetail
131 |                     {
132 |                         Name = "Test Execution Error",
133 |                         ClassName = "VSTest",
134 |                         Result = "Failed",
135 |                         Duration = 0,
136 |                         ErrorMessage = ex.Message,
137 |                         StackTrace = ex.StackTrace
138 |                     }
139 |                 },
140 |                 Output = $"VSTest execution failed: {ex.Message}\n{ex.StackTrace}"
141 |             };
142 |         }
143 |     }
144 | 
145 |     private string? FindVSTestConsoleExecutable()
146 |     {
147 |         // Check environment variable first
148 |         var envPath = Environment.GetEnvironmentVariable("VSTEST_CONSOLE_PATH");
149 |         if (!string.IsNullOrEmpty(envPath) && File.Exists(envPath))
150 |         {
151 |             return envPath;
152 |         }
153 | 
154 |         // Get preferred VS version from configuration
155 |         var preferredVersion = _configuration.PreferredVSVersion?.ToLower() ?? "2022";
156 |         
157 |         var possiblePaths = new List<string>();
158 | 
159 |         // Add paths based on preferred version first
160 |         if (preferredVersion == "2022" || preferredVersion == "auto")
161 |         {
162 |             possiblePaths.AddRange(new[]
163 |             {
164 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
165 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
166 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
167 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
168 |                 @"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
169 |                 @"C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
170 |                 @"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
171 |                 @"C:\Program Files\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
172 |             });
173 |         }
174 | 
175 |         if (preferredVersion == "2019" || preferredVersion == "auto")
176 |         {
177 |             possiblePaths.AddRange(new[]
178 |             {
179 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
180 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
181 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
182 |                 @"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
183 |             });
184 |         }
185 | 
186 |         var foundPath = possiblePaths.FirstOrDefault(File.Exists);
187 |         if (foundPath != null)
188 |         {
189 |             var version = foundPath.Contains("2022") ? "2022" : foundPath.Contains("2019") ? "2019" : "Unknown";
190 |             _logger.LogInformation("Found VSTest.Console.exe version {Version} at: {Path}", version, foundPath);
191 |         }
192 |         else
193 |         {
194 |             _logger.LogWarning("VSTest.Console.exe not found in standard locations");
195 |         }
196 | 
197 |         return foundPath;
198 |     }
199 | 
200 |     private string? FindTestAssembly(string projectPath)
201 |     {
202 |         var projectDir = Path.GetDirectoryName(projectPath);
203 |         var projectName = Path.GetFileNameWithoutExtension(projectPath);
204 |         
205 |         // Look for test assemblies in bin directories
206 |         var possiblePaths = new[]
207 |         {
208 |             Path.Combine(projectDir!, "bin", "Debug", $"{projectName}.dll"),
209 |             Path.Combine(projectDir!, "bin", "Release", $"{projectName}.dll"),
210 |             Path.Combine(projectDir!, "bin", "Debug", "net48", $"{projectName}.dll"),
211 |             Path.Combine(projectDir!, "bin", "Release", "net48", $"{projectName}.dll"),
212 |             Path.Combine(projectDir!, "bin", "Debug", "net472", $"{projectName}.dll"),
213 |             Path.Combine(projectDir!, "bin", "Release", "net472", $"{projectName}.dll")
214 |         };
215 | 
216 |         var testAssembly = possiblePaths.FirstOrDefault(File.Exists);
217 |         
218 |         if (string.IsNullOrEmpty(testAssembly))
219 |         {
220 |             // Fallback: search recursively
221 |             var assemblyFiles = Directory.GetFiles(Path.Combine(projectDir!, "bin"), "*.dll", SearchOption.AllDirectories)
222 |                 .Where(f => Path.GetFileName(f).Equals($"{projectName}.dll", StringComparison.OrdinalIgnoreCase))
223 |                 .ToList();
224 |             
225 |             testAssembly = assemblyFiles.FirstOrDefault();
226 |         }
227 | 
228 |         return testAssembly;
229 |     }
230 | 
231 |     private string? FindTestAdapterPath(string projectPath)
232 |     {
233 |         try
234 |         {
235 |             var projectDir = Path.GetDirectoryName(projectPath);
236 |             if (string.IsNullOrEmpty(projectDir))
237 |                 return null;
238 | 
239 |             // Look for test adapters in packages folder (packages.config style)
240 |             var currentDir = new DirectoryInfo(projectDir);
241 |             while (currentDir != null)
242 |             {
243 |                 var packagesDir = Path.Combine(currentDir.FullName, "packages");
244 |                 if (Directory.Exists(packagesDir))
245 |                 {
246 |                     _logger.LogDebug("Searching for test adapters in packages directory: {PackagesDir}", packagesDir);
247 | 
248 |                     // Look for NUnit test adapter (prioritize NUnit3TestAdapter)
249 |                     var nunitAdapterPatterns = new[] { "NUnit3TestAdapter.*", "NUnitTestAdapter.*" };
250 |                     foreach (var pattern in nunitAdapterPatterns)
251 |                     {
252 |                         var nunitAdapterDirs = Directory.GetDirectories(packagesDir, pattern, SearchOption.TopDirectoryOnly);
253 |                         foreach (var adapterDir in nunitAdapterDirs)
254 |                         {
255 |                             var possiblePaths = new[]
256 |                             {
257 |                                 Path.Combine(adapterDir, "build"),
258 |                                 Path.Combine(adapterDir, "build", "net35"),
259 |                                 Path.Combine(adapterDir, "build", "net40"),
260 |                                 adapterDir
261 |                             };
262 | 
263 |                             foreach (var path in possiblePaths)
264 |                             {
265 |                                 if (Directory.Exists(path))
266 |                                 {
267 |                                     var adapterDlls = Directory.GetFiles(path, "*TestAdapter*.dll", SearchOption.AllDirectories);
268 |                                     if (adapterDlls.Length > 0)
269 |                                     {
270 |                                         _logger.LogInformation("Found NUnit test adapter at: {Path}", path);
271 |                                         return path;
272 |                                     }
273 |                                 }
274 |                             }
275 |                         }
276 |                     }
277 | 
278 |                     // Look for xUnit test adapter as fallback
279 |                     var xunitAdapterDirs = Directory.GetDirectories(packagesDir, "xunit.runner.visualstudio.*", SearchOption.TopDirectoryOnly);
280 |                     foreach (var adapterDir in xunitAdapterDirs)
281 |                     {
282 |                         var buildPath = Path.Combine(adapterDir, "build");
283 |                         if (Directory.Exists(buildPath))
284 |                         {
285 |                             _logger.LogInformation("Found xUnit test adapter at: {Path}", buildPath);
286 |                             return buildPath;
287 |                         }
288 |                     }
289 | 
290 |                     break; // Only check the first packages directory found
291 |                 }
292 | 
293 |                 currentDir = currentDir.Parent;
294 |             }
295 | 
296 |             return null;
297 |         }
298 |         catch (Exception ex)
299 |         {
300 |             _logger.LogWarning(ex, "Error searching for test adapter path for project: {ProjectPath}", projectPath);
301 |             return null;
302 |         }
303 |     }
304 | }
```

--------------------------------------------------------------------------------
/src/DotNetFrameworkMCP.Server/Executors/MSBuildExecutor.cs:
--------------------------------------------------------------------------------

```csharp
  1 | using System.Diagnostics;
  2 | using System.Text;
  3 | using System.Text.RegularExpressions;
  4 | using DotNetFrameworkMCP.Server.Configuration;
  5 | using DotNetFrameworkMCP.Server.Models;
  6 | using Microsoft.Extensions.Logging;
  7 | using Microsoft.Extensions.Options;
  8 | 
  9 | namespace DotNetFrameworkMCP.Server.Executors;
 10 | 
 11 | /// <summary>
 12 | /// MSBuild-based build executor
 13 | /// </summary>
 14 | public class MSBuildExecutor : IBuildExecutor
 15 | {
 16 |     private readonly ILogger<MSBuildExecutor> _logger;
 17 |     private readonly McpServerConfiguration _configuration;
 18 | 
 19 |     public MSBuildExecutor(
 20 |         ILogger<MSBuildExecutor> logger,
 21 |         IOptions<McpServerConfiguration> configuration)
 22 |     {
 23 |         _logger = logger;
 24 |         _configuration = configuration.Value;
 25 |     }
 26 | 
 27 |     public async Task<BuildResult> ExecuteBuildAsync(
 28 |         string projectPath,
 29 |         string configuration,
 30 |         string platform,
 31 |         bool restore,
 32 |         CancellationToken cancellationToken = default)
 33 |     {
 34 |         var stopwatch = Stopwatch.StartNew();
 35 |         var errors = new List<BuildMessage>();
 36 |         var warnings = new List<BuildMessage>();
 37 | 
 38 |         try
 39 |         {
 40 |             // Validate project path
 41 |             if (!File.Exists(projectPath))
 42 |             {
 43 |                 throw new FileNotFoundException($"Project file not found: {projectPath}");
 44 |             }
 45 | 
 46 |             // Find MSBuild.exe
 47 |             var msbuildPath = FindMSBuildExecutable();
 48 |             if (string.IsNullOrEmpty(msbuildPath))
 49 |             {
 50 |                 _logger.LogError("Could not find MSBuild.exe in any standard locations");
 51 |                 throw new InvalidOperationException("Could not find MSBuild.exe. Please install Visual Studio or Build Tools for Visual Studio, or set MSBUILD_EXE_PATH environment variable.");
 52 |             }
 53 | 
 54 |             _logger.LogInformation("Using MSBuild.exe: {MSBuildPath}", msbuildPath);
 55 | 
 56 |             // Build the project using MSBuild.exe process
 57 |             var result = await RunMSBuildAsync(msbuildPath, projectPath, configuration, platform, restore, cancellationToken);
 58 |             
 59 |             // Parse the output for errors and warnings
 60 |             ParseBuildOutput(result.Output, errors, warnings);
 61 | 
 62 |             stopwatch.Stop();
 63 | 
 64 |             return new BuildResult
 65 |             {
 66 |                 Success = result.ExitCode == 0,
 67 |                 Errors = errors,
 68 |                 Warnings = warnings,
 69 |                 BuildTime = stopwatch.Elapsed.TotalSeconds,
 70 |                 Output = TruncateOutput(result.Output, result.ExitCode != 0)
 71 |             };
 72 |         }
 73 |         catch (Exception ex)
 74 |         {
 75 |             _logger.LogError(ex, "Build failed for project: {ProjectPath}", projectPath);
 76 |             stopwatch.Stop();
 77 | 
 78 |             return new BuildResult
 79 |             {
 80 |                 Success = false,
 81 |                 Errors = new List<BuildMessage>
 82 |                 {
 83 |                     new BuildMessage
 84 |                     {
 85 |                         Message = ex.Message,
 86 |                         File = projectPath
 87 |                     }
 88 |                 },
 89 |                 Warnings = warnings,
 90 |                 BuildTime = stopwatch.Elapsed.TotalSeconds,
 91 |                 Output = TruncateOutput($"Build failed with exception: {ex.Message}\n{ex.StackTrace}", true)
 92 |             };
 93 |         }
 94 |     }
 95 | 
 96 |     private string? FindMSBuildExecutable()
 97 |     {
 98 |         // Check environment variable first
 99 |         var envPath = Environment.GetEnvironmentVariable("MSBUILD_EXE_PATH");
100 |         if (!string.IsNullOrEmpty(envPath) && File.Exists(envPath))
101 |         {
102 |             return envPath;
103 |         }
104 | 
105 |         // Get preferred VS version from configuration
106 |         var preferredVersion = _configuration.PreferredVSVersion?.ToLower() ?? "2022";
107 |         
108 |         // Look for MSBuild.exe in standard Visual Studio locations
109 |         var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
110 |         var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
111 | 
112 |         var possiblePaths = new List<string>();
113 | 
114 |         // Add paths based on preferred version first
115 |         if (preferredVersion == "2022" || preferredVersion == "auto")
116 |         {
117 |             possiblePaths.AddRange(new[]
118 |             {
119 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
120 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
121 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
122 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
123 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
124 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
125 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
126 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
127 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
128 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
129 |             });
130 |         }
131 | 
132 |         if (preferredVersion == "2019" || preferredVersion == "auto")
133 |         {
134 |             possiblePaths.AddRange(new[]
135 |             {
136 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"),
137 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
138 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"),
139 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
140 |             });
141 |         }
142 | 
143 |         // If not auto mode and preferred version is not 2022 or 2019, add all versions as fallback
144 |         if (preferredVersion != "auto" && preferredVersion != "2022" && preferredVersion != "2019")
145 |         {
146 |             _logger.LogWarning("Unknown PreferredVSVersion '{Version}', falling back to auto detection", preferredVersion);
147 |             possiblePaths.AddRange(new[]
148 |             {
149 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
150 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
151 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
152 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
153 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
154 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
155 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
156 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
157 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
158 |                 Path.Combine(programFiles, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
159 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"),
160 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
161 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"),
162 |                 Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
163 |             });
164 |         }
165 | 
166 |         // Add legacy paths as final fallback
167 |         possiblePaths.AddRange(new[]
168 |         {
169 |             Path.Combine(programFilesX86, @"MSBuild\14.0\Bin\MSBuild.exe"),
170 |             Path.Combine(programFilesX86, @"MSBuild\15.0\Bin\MSBuild.exe"),
171 |             @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe",
172 |             @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
173 |         });
174 | 
175 |         var foundPath = possiblePaths.FirstOrDefault(File.Exists);
176 |         if (foundPath != null)
177 |         {
178 |             var version = foundPath.Contains("2022") ? "2022" : foundPath.Contains("2019") ? "2019" : "Legacy";
179 |             _logger.LogInformation("Found MSBuild.exe version {Version} at: {Path}", version, foundPath);
180 |         }
181 | 
182 |         return foundPath;
183 |     }
184 | 
185 |     private async Task<(int ExitCode, string Output)> RunMSBuildAsync(
186 |         string msbuildPath,
187 |         string projectPath,
188 |         string configuration,
189 |         string platform,
190 |         bool restore,
191 |         CancellationToken cancellationToken)
192 |     {
193 |         var arguments = new List<string>
194 |         {
195 |             $"\"{projectPath}\"",
196 |             $"/p:Configuration={configuration}",
197 |             $"/p:Platform=\"{platform}\"",
198 |             "/v:normal", // Normal verbosity
199 |             "/nologo"
200 |         };
201 | 
202 |         if (restore)
203 |         {
204 |             arguments.Add("/restore");
205 |         }
206 | 
207 |         var argumentString = string.Join(" ", arguments);
208 |         _logger.LogDebug("Running: {MSBuildPath} {Arguments}", msbuildPath, argumentString);
209 | 
210 |         var psi = new ProcessStartInfo
211 |         {
212 |             FileName = msbuildPath,
213 |             Arguments = argumentString,
214 |             UseShellExecute = false,
215 |             RedirectStandardOutput = true,
216 |             RedirectStandardError = true,
217 |             CreateNoWindow = true,
218 |             WorkingDirectory = Path.GetDirectoryName(projectPath) ?? Environment.CurrentDirectory
219 |         };
220 | 
221 |         var output = new StringBuilder();
222 | 
223 |         using var process = new Process { StartInfo = psi };
224 |         
225 |         process.OutputDataReceived += (sender, e) =>
226 |         {
227 |             if (e.Data != null)
228 |             {
229 |                 output.AppendLine(e.Data);
230 |             }
231 |         };
232 | 
233 |         process.ErrorDataReceived += (sender, e) =>
234 |         {
235 |             if (e.Data != null)
236 |             {
237 |                 output.AppendLine(e.Data);
238 |             }
239 |         };
240 | 
241 |         process.Start();
242 |         process.BeginOutputReadLine();
243 |         process.BeginErrorReadLine();
244 | 
245 |         // Wait for completion with timeout and cancellation support
246 |         var timeoutMs = _configuration.BuildTimeout;
247 |         using var timeoutCts = new CancellationTokenSource(timeoutMs);
248 |         using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
249 |         
250 |         try
251 |         {
252 |             await process.WaitForExitAsync(combinedCts.Token);
253 |         }
254 |         catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
255 |         {
256 |             _logger.LogWarning("Build timed out after {TimeoutMs}ms, killing process", timeoutMs);
257 |             process.Kill();
258 |             throw new TimeoutException($"Build timed out after {timeoutMs}ms");
259 |         }
260 |         catch (OperationCanceledException)
261 |         {
262 |             _logger.LogInformation("Build cancelled by user, killing process");
263 |             process.Kill();
264 |             throw;
265 |         }
266 | 
267 |         return (process.ExitCode, output.ToString());
268 |     }
269 | 
270 |     private void ParseBuildOutput(string output, List<BuildMessage> errors, List<BuildMessage> warnings)
271 |     {
272 |         if (string.IsNullOrEmpty(output))
273 |             return;
274 | 
275 |         var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
276 | 
277 |         // Regex patterns for MSBuild error and warning messages
278 |         var errorPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
279 |         var warningPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+warning\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
280 |         var generalErrorPattern = new Regex(@"^(.+?):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
281 | 
282 |         foreach (var line in lines)
283 |         {
284 |             var trimmedLine = line.Trim();
285 |             
286 |             // Try specific error pattern first (file with line/column)
287 |             var errorMatch = errorPattern.Match(trimmedLine);
288 |             if (errorMatch.Success)
289 |             {
290 |                 errors.Add(new BuildMessage
291 |                 {
292 |                     File = errorMatch.Groups[1].Value,
293 |                     Line = int.TryParse(errorMatch.Groups[2].Value, out var errorLine) ? errorLine : 0,
294 |                     Column = int.TryParse(errorMatch.Groups[3].Value, out var errorCol) ? errorCol : 0,
295 |                     Code = errorMatch.Groups[4].Value,
296 |                     Message = errorMatch.Groups[5].Value
297 |                 });
298 |                 continue;
299 |             }
300 | 
301 |             // Try warning pattern
302 |             var warningMatch = warningPattern.Match(trimmedLine);
303 |             if (warningMatch.Success)
304 |             {
305 |                 warnings.Add(new BuildMessage
306 |                 {
307 |                     File = warningMatch.Groups[1].Value,
308 |                     Line = int.TryParse(warningMatch.Groups[2].Value, out var warningLine) ? warningLine : 0,
309 |                     Column = int.TryParse(warningMatch.Groups[3].Value, out var warningCol) ? warningCol : 0,
310 |                     Code = warningMatch.Groups[4].Value,
311 |                     Message = warningMatch.Groups[5].Value
312 |                 });
313 |                 continue;
314 |             }
315 | 
316 |             // Try general error pattern (no line/column)
317 |             var generalErrorMatch = generalErrorPattern.Match(trimmedLine);
318 |             if (generalErrorMatch.Success)
319 |             {
320 |                 errors.Add(new BuildMessage
321 |                 {
322 |                     File = generalErrorMatch.Groups[1].Value,
323 |                     Code = generalErrorMatch.Groups[2].Value,
324 |                     Message = generalErrorMatch.Groups[3].Value
325 |                 });
326 |             }
327 |         }
328 |     }
329 | 
330 |     private string TruncateOutput(string output, bool isFailed)
331 |     {
332 |         const int maxChars = 15000; // Conservative limit to stay under 25k tokens
333 |         
334 |         if (string.IsNullOrEmpty(output) || output.Length <= maxChars)
335 |         {
336 |             return output;
337 |         }
338 | 
339 |         if (isFailed)
340 |         {
341 |             // For failed builds, prioritize the end of the output (where errors typically appear)
342 |             var lines = output.Split('\n');
343 |             var importantLines = new List<string>();
344 |             var currentLength = 0;
345 |             
346 |             // Add summary line if present
347 |             for (int i = 0; i < Math.Min(10, lines.Length); i++)
348 |             {
349 |                 if (lines[i].Contains("Build FAILED") || lines[i].Contains("error") || lines[i].Contains("Error"))
350 |                 {
351 |                     importantLines.Add(lines[i]);
352 |                     currentLength += lines[i].Length + 1;
353 |                     break;
354 |                 }
355 |             }
356 |             
357 |             // Add errors from the end
358 |             for (int i = lines.Length - 1; i >= 0 && currentLength < maxChars - 100; i--)
359 |             {
360 |                 var line = lines[i];
361 |                 if (currentLength + line.Length + 1 > maxChars - 100) break;
362 |                 
363 |                 if (line.Contains("error") || line.Contains("Error") || 
364 |                     line.Contains("warning") || line.Contains("Warning") ||
365 |                     line.Contains("Build FAILED") || line.Contains("Time Elapsed"))
366 |                 {
367 |                     importantLines.Insert(importantLines.Count == 0 ? 0 : 1, line);
368 |                     currentLength += line.Length + 1;
369 |                 }
370 |             }
371 |             
372 |             var result = string.Join("\n", importantLines);
373 |             if (result.Length < maxChars - 200)
374 |             {
375 |                 // Add some context from the end
376 |                 var remaining = maxChars - result.Length - 100;
377 |                 var endPortion = output.Substring(Math.Max(0, output.Length - remaining));
378 |                 result += "\n...\n" + endPortion;
379 |             }
380 |             
381 |             return $"[Output truncated - showing errors and summary]\n{result}";
382 |         }
383 |         else
384 |         {
385 |             // For successful builds, show beginning and end
386 |             var halfMax = maxChars / 2 - 50;
387 |             var start = output.Substring(0, Math.Min(halfMax, output.Length));
388 |             var end = output.Length > halfMax ? output.Substring(output.Length - halfMax) : "";
389 |             
390 |             return start + "\n\n[... middle portion truncated ...]\n\n" + end;
391 |         }
392 |     }
393 | }
```