#
tokens: 30818/50000 44/44 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore

# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Mono auto generated files
mono_crash.*

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# Visual Studio 2017 auto generated files
Generated\ Files/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET
project.lock.json
project.fragment.lock.json
artifacts/

# ASP.NET Scaffolding
ScaffoldingReadMe.txt

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp

# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp

# Visual Studio 6 technical files
*.ncb
*.aps

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# CodeRush personal settings
.cr/personal

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
*.binlog

# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
.mfractor/

# Local History for Visual Studio
.localhistory/

# Visual Studio History (VSHistory) files
.vshistory/

# BeatPulse healthcheck temp database
healthchecksdb

# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd

# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

# Local History for Visual Studio Code
.history/

# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp

# JetBrains Rider
*.sln.iml
.idea/

# macOS
.DS_Store

# Published output
publish/
```

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

```markdown
# Claude Development Guidelines

## Project Information

This is a .NET Framework MCP (Model Context Protocol) server for Windows that enables building, testing, and running .NET Framework projects remotely.

## Testing Framework

**Use NUnit for all tests** - This project uses NUnit, not xUnit or MSTest.

### Test Structure:
```csharp
using NUnit.Framework;

[TestFixture]
public class MyServiceTests
{
    [SetUp]
    public void SetUp()
    {
        // Initialize test dependencies
    }

    [TearDown] 
    public void TearDown()
    {
        // Cleanup resources
    }

    [Test]
    public void MyMethod_WithValidInput_ReturnsExpectedResult()
    {
        // Arrange
        // Act
        // Assert
        Assert.That(result, Is.EqualTo(expected));
    }

    [TestCase("value1", "expected1")]
    [TestCase("value2", "expected2")]
    public void MyMethod_WithDifferentInputs_ReturnsExpectedResults(string input, string expected)
    {
        // Test implementation
    }
}
```

### NUnit Assertions:
- Use `Assert.That(actual, Is.EqualTo(expected))` instead of `Assert.Equal()`
- Use `Assert.That(collection, Has.Count.EqualTo(3))` for collections
- Use `Assert.That(text, Does.Contain("substring"))` for string checks
- Use `Times.Once` with Moq for verifications

## Build Commands

### Development:
```bash
# Build the server
dotnet build src/DotNetFrameworkMCP.Server

# Run tests
dotnet test

# Run with dotnet CLI enabled
set MCPSERVER__UseDotNetCli=true
dotnet run --project src/DotNetFrameworkMCP.Server -- --port 3001
```

### IMPORTANT: Pre-Commit Workflow
**ALWAYS build and test before committing!**

```bash
# 1. Build to ensure no compilation errors
dotnet build

# 2. Run tests to ensure functionality works
dotnet test

# 3. Only commit if both succeed
git add -A
git commit -m "Your commit message"
```

If build or tests fail, fix the issues before committing. Never commit broken code.

### Production:
```bash
# Build self-contained executable
build-on-windows.bat

# Run the compiled executable
run-tcp-server.bat
```

## Architecture Notes

- Uses Strategy pattern for build/test executors
- MSBuild vs dotnet CLI selection via configuration
- Factory pattern for executor creation
- Services delegate to executors, no conditional logic
- All executors implement `IBuildExecutor` or `ITestExecutor`

## Configuration

Key settings in `appsettings.json`:
- `UseDotNetCli`: Switch between MSBuild and dotnet CLI
- `PreferredVSVersion`: VS version preference for MSBuild
- `BuildTimeout`: Build operation timeout
- `TestTimeout`: Test operation timeout
```

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

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

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

```json
{
  "mcpServers": {
    "dotnet-framework": {
      "command": "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework/wsl-mcp-bridge.sh",
      "env": {
        "MCP_DEBUG": "true"
      }
    }
  }
}
```

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

```csharp
using System.Text.Json;
using DotNetFrameworkMCP.Server.Protocol;

namespace DotNetFrameworkMCP.Server.Tools;

public interface IToolHandler
{
    string Name { get; }
    ToolDefinition GetDefinition();
    Task<object> ExecuteAsync(JsonElement arguments);
}
```

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

```json
{
  "mcpServers": {
    "dotnet-framework": {
      "command": "dotnet",
      "args": [
        "run",
        "--project",
        "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework/src/DotNetFrameworkMCP.Server"
      ],
      "env": {
        "MCPSERVER_EnableDetailedLogging": "true"
      }
    }
  }
}
```

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

```
@echo off
echo Testing dotnet CLI support...
echo.
echo Starting server with dotnet CLI enabled...

REM Build the server first
cd src\DotNetFrameworkMCP.Server
dotnet build

REM Run with dotnet CLI enabled
set MCPSERVER__UseDotNetCli=true
set MCPSERVER__EnableDetailedLogging=true

echo.
echo Running server with dotnet CLI support enabled...
dotnet run -- --port 3001
```

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

```
@echo off
echo Starting .NET Framework MCP Server in TCP mode...
echo Server will listen on port 3001
echo Press Ctrl+C to stop
echo.

cd /d "%~dp0"

if exist "publish\DotNetFrameworkMCP.Server.exe" (
    publish\DotNetFrameworkMCP.Server.exe --port 3001
) else (
    echo ERROR: Server executable not found!
    echo Please run build-on-windows.bat first to build the project.
    pause
    exit /b 1
)
```

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

```json
{
  "McpServer": {
    "MsBuildPath": "auto",
    "DefaultConfiguration": "Debug",
    "DefaultPlatform": "Any CPU",
    "TestTimeout": 300000,
    "BuildTimeout": 1200000,
    "EnableDetailedLogging": true,
    "PreferredVSVersion": "2022",
    "UseDotNetCli": false,
    "DotNetPath": "dotnet"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "DotNetFrameworkMCP.Server": "Debug"
    }
  }
}
```

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

```
@echo off
echo Starting .NET Framework MCP Server in TCP mode...
echo Server will listen on port 3001
echo Press Ctrl+C to stop
echo.

cd /d "%~dp0"

if exist "publish\DotNetFrameworkMCP.Server.exe" (
    echo Using compiled executable...
    publish\DotNetFrameworkMCP.Server.exe --port 3001
) else (
    echo Using dotnet run (building if needed)...
    dotnet run --project "src\DotNetFrameworkMCP.Server" -- --port 3001
)

pause
```

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

```bash
#!/bin/bash

# MCP Bridge Script for WSL
# This script connects to the Windows TCP MCP server from WSL

# Configuration
WINDOWS_HOST="localhost"  # Or use the actual Windows IP if needed
MCP_PORT="3001"

# Check if netcat is available
if ! command -v nc &> /dev/null; then
    echo "Error: netcat (nc) is required but not installed." >&2
    echo "Install it with: sudo apt install netcat-openbsd" >&2
    exit 1
fi

# Connect to the Windows MCP server via TCP
exec nc "$WINDOWS_HOST" "$MCP_PORT"
```

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

```json
// Example MCP messages to test the server

// 1. Initialize the server
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}

// 2. List available tools
{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}

// 3. Call the build_project tool
{"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:
--------------------------------------------------------------------------------

```
@echo off
echo Building .NET Framework MCP Server...
echo.

cd /d "%~dp0"

echo Restoring packages...
dotnet restore

echo.
echo Building Release configuration...
dotnet build -c Release

echo.
echo Publishing self-contained executable...
dotnet publish src\DotNetFrameworkMCP.Server -c Release -r win-x64 --self-contained -o publish

echo.
echo Build complete! 
echo Executable location: publish\DotNetFrameworkMCP.Server.exe
echo.
echo You can now run the server with:
echo   publish\DotNetFrameworkMCP.Server.exe --tcp --port 3001
echo.
pause
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Models;

public class RunProjectRequest
{
    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;

    [JsonPropertyName("args")]
    public List<string>? Args { get; set; }

    [JsonPropertyName("workingDirectory")]
    public string? WorkingDirectory { get; set; }
}

public class RunResult
{
    [JsonPropertyName("exitCode")]
    public int ExitCode { get; set; }

    [JsonPropertyName("output")]
    public string Output { get; set; } = string.Empty;

    [JsonPropertyName("error")]
    public string Error { get; set; } = string.Empty;

    [JsonPropertyName("duration")]
    public double Duration { get; set; }
}
```

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

```csharp
namespace DotNetFrameworkMCP.Server.Configuration;

public class McpServerConfiguration
{
    public string MsBuildPath { get; set; } = "auto";
    public string DefaultConfiguration { get; set; } = "Debug";
    public string DefaultPlatform { get; set; } = "Any CPU";
    public int TestTimeout { get; set; } = 300000;
    public int BuildTimeout { get; set; } = 1200000; // 20 minutes for large solutions
    public bool EnableDetailedLogging { get; set; } = false;
    public string PreferredVSVersion { get; set; } = "2022"; // Options: "2022", "2019", "auto"
    public bool UseDotNetCli { get; set; } = false; // Use dotnet CLI instead of MSBuild
    public string DotNetPath { get; set; } = "dotnet"; // Path to dotnet CLI executable
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Models;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// Interface for test execution strategies
/// </summary>
public interface ITestExecutor
{
    /// <summary>
    /// Executes tests for the specified project
    /// </summary>
    /// <param name="projectPath">Path to the test project</param>
    /// <param name="filter">Optional test filter expression</param>
    /// <param name="verbose">Whether to enable verbose output</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Test result with total, passed, failed, skipped counts and details</returns>
    Task<TestResult> ExecuteTestsAsync(
        string projectPath,
        string? filter,
        bool verbose,
        CancellationToken cancellationToken = default);
}
```

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

```bash
#!/bin/bash

echo "Testing TCP MCP Server..."

# Start the server in background
cd "/mnt/c/Users/work-tower/Projects/Open/MCP For .Net Framework"
dotnet run --project src/DotNetFrameworkMCP.Server -- --port 3001 &
SERVER_PID=$!

# Wait a moment for server to start
sleep 2

echo "Server started with PID $SERVER_PID"
echo "Testing connection..."

# Test the server
{
    echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}'
    sleep 0.1
    echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'
    sleep 0.1
    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}}}'
    sleep 1
} | nc localhost 3001

echo "Test completed. Stopping server..."
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null
echo "Server stopped."
```

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

```csharp
using DotNetFrameworkMCP.Server.Models;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// Interface for build execution strategies
/// </summary>
public interface IBuildExecutor
{
    /// <summary>
    /// Executes a build for the specified project
    /// </summary>
    /// <param name="projectPath">Path to the project or solution file</param>
    /// <param name="configuration">Build configuration (e.g., Debug, Release)</param>
    /// <param name="platform">Target platform (e.g., Any CPU, x86, x64)</param>
    /// <param name="restore">Whether to restore NuGet packages</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Build result with success status, errors, warnings, and output</returns>
    Task<BuildResult> ExecuteBuildAsync(
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken = default);
}
```

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

```
#!/usr/bin/env pwsh

Write-Host "Building .NET Framework MCP Server..." -ForegroundColor Green
Write-Host ""

# Change to script directory
Set-Location $PSScriptRoot

Write-Host "Restoring packages..." -ForegroundColor Yellow
dotnet restore

Write-Host ""
Write-Host "Building Release configuration..." -ForegroundColor Yellow
dotnet build -c Release

if ($LASTEXITCODE -ne 0) {
    Write-Host "Build failed!" -ForegroundColor Red
    exit 1
}

Write-Host ""
Write-Host "Publishing self-contained executable..." -ForegroundColor Yellow
dotnet publish src\DotNetFrameworkMCP.Server -c Release -r win-x64 --self-contained -o publish

if ($LASTEXITCODE -ne 0) {
    Write-Host "Publish failed!" -ForegroundColor Red
    exit 1
}

Write-Host ""
Write-Host "Build complete!" -ForegroundColor Green
Write-Host "Executable location: publish\DotNetFrameworkMCP.Server.exe" -ForegroundColor Cyan
Write-Host ""
Write-Host "You can now run the server with:" -ForegroundColor Yellow
Write-Host "  .\publish\DotNetFrameworkMCP.Server.exe --tcp --port 3001" -ForegroundColor White
Write-Host ""
```

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

```csharp
using DotNetFrameworkMCP.Server.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// Factory for creating build and test executors based on configuration
/// </summary>
public interface IExecutorFactory
{
    IBuildExecutor CreateBuildExecutor();
    ITestExecutor CreateTestExecutor();
}

public class ExecutorFactory : IExecutorFactory
{
    private readonly IServiceProvider _serviceProvider;
    private readonly McpServerConfiguration _configuration;

    public ExecutorFactory(IServiceProvider serviceProvider, IOptions<McpServerConfiguration> configuration)
    {
        _serviceProvider = serviceProvider;
        _configuration = configuration.Value;
    }

    public IBuildExecutor CreateBuildExecutor()
    {
        return _configuration.UseDotNetCli
            ? _serviceProvider.GetRequiredService<DotNetBuildExecutor>()
            : _serviceProvider.GetRequiredService<MSBuildExecutor>();
    }

    public ITestExecutor CreateTestExecutor()
    {
        return _configuration.UseDotNetCli
            ? _serviceProvider.GetRequiredService<DotNetTestExecutor>()
            : _serviceProvider.GetRequiredService<VSTestExecutor>();
    }
}
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Protocol;

public class ToolDefinition
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("description")]
    public string Description { get; set; } = string.Empty;

    [JsonPropertyName("inputSchema")]
    public JsonSchema InputSchema { get; set; } = new();
}

public class JsonSchema
{
    [JsonPropertyName("type")]
    public string Type { get; set; } = "object";

    [JsonPropertyName("properties")]
    public Dictionary<string, SchemaProperty> Properties { get; set; } = new();

    [JsonPropertyName("required")]
    public List<string> Required { get; set; } = new();
}

public class SchemaProperty
{
    [JsonPropertyName("type")]
    public string Type { get; set; } = string.Empty;

    [JsonPropertyName("description")]
    public string? Description { get; set; }

    [JsonPropertyName("enum")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public List<string>? Enum { get; set; }

    [JsonPropertyName("default")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public object? Default { get; set; }

    [JsonPropertyName("items")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public SchemaProperty? Items { get; set; }
}
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Models;

public class BuildProjectRequest
{
    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;

    [JsonPropertyName("configuration")]
    public string Configuration { get; set; } = "Debug";

    [JsonPropertyName("platform")]
    public string Platform { get; set; } = "Any CPU";

    [JsonPropertyName("restore")]
    public bool Restore { get; set; } = true;
}

public class BuildResult
{
    [JsonPropertyName("success")]
    public bool Success { get; set; }

    [JsonPropertyName("errors")]
    public List<BuildMessage> Errors { get; set; } = new();

    [JsonPropertyName("warnings")]
    public List<BuildMessage> Warnings { get; set; } = new();

    [JsonPropertyName("buildTime")]
    public double BuildTime { get; set; }

    [JsonPropertyName("output")]
    public string Output { get; set; } = string.Empty;
}

public class BuildMessage
{
    [JsonPropertyName("file")]
    public string? File { get; set; }

    [JsonPropertyName("line")]
    public int Line { get; set; }

    [JsonPropertyName("column")]
    public int Column { get; set; }

    [JsonPropertyName("code")]
    public string? Code { get; set; }

    [JsonPropertyName("message")]
    public string Message { get; set; } = string.Empty;

    [JsonPropertyName("project")]
    public string? Project { get; set; }
}
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Protocol;

public class McpMessage
{
    [JsonPropertyName("jsonrpc")]
    public string JsonRpc { get; set; } = "2.0";

    [JsonPropertyName("id")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public object? Id { get; set; }

    [JsonPropertyName("method")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Method { get; set; }

    [JsonPropertyName("params")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public object? Params { get; set; }

    [JsonPropertyName("result")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public object? Result { get; set; }

    [JsonPropertyName("error")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public McpError? Error { get; set; }
}

public class McpError
{
    [JsonPropertyName("code")]
    public int Code { get; set; }

    [JsonPropertyName("message")]
    public string Message { get; set; } = string.Empty;

    [JsonPropertyName("data")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public object? Data { get; set; }
}

public static class McpErrorCodes
{
    public const int ParseError = -32700;
    public const int InvalidRequest = -32600;
    public const int MethodNotFound = -32601;
    public const int InvalidParams = -32602;
    public const int InternalError = -32603;
}
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Models;

public class RunTestsRequest
{
    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;

    [JsonPropertyName("filter")]
    public string? Filter { get; set; }

    [JsonPropertyName("verbose")]
    public bool Verbose { get; set; } = false;
}

public class TestResult
{
    [JsonPropertyName("totalTests")]
    public int TotalTests { get; set; }

    [JsonPropertyName("passedTests")]
    public int PassedTests { get; set; }

    [JsonPropertyName("failedTests")]
    public int FailedTests { get; set; }

    [JsonPropertyName("skippedTests")]
    public int SkippedTests { get; set; }

    [JsonPropertyName("duration")]
    public double Duration { get; set; }

    [JsonPropertyName("testDetails")]
    public List<TestDetail> TestDetails { get; set; } = new();

    [JsonPropertyName("output")]
    public string Output { get; set; } = string.Empty;
}

public class TestDetail
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("className")]
    public string ClassName { get; set; } = string.Empty;

    [JsonPropertyName("result")]
    public string Result { get; set; } = string.Empty;

    [JsonPropertyName("duration")]
    public double Duration { get; set; }

    [JsonPropertyName("errorMessage")]
    public string? ErrorMessage { get; set; }

    [JsonPropertyName("stackTrace")]
    public string? StackTrace { get; set; }
}
```

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

```csharp
using System.Text.Json.Serialization;

namespace DotNetFrameworkMCP.Server.Models;

public class AnalyzeSolutionRequest
{
    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;
}

public class SolutionAnalysis
{
    [JsonPropertyName("solutionName")]
    public string SolutionName { get; set; } = string.Empty;

    [JsonPropertyName("projects")]
    public List<ProjectInfo> Projects { get; set; } = new();

    [JsonPropertyName("totalProjects")]
    public int TotalProjects { get; set; }
}

public class ProjectInfo
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;

    [JsonPropertyName("type")]
    public string Type { get; set; } = string.Empty;

    [JsonPropertyName("targetFramework")]
    public string TargetFramework { get; set; } = string.Empty;

    [JsonPropertyName("outputType")]
    public string OutputType { get; set; } = string.Empty;

    [JsonPropertyName("dependencies")]
    public List<string> Dependencies { get; set; } = new();

    [JsonPropertyName("packages")]
    public List<PackageInfo> Packages { get; set; } = new();
}

public class ListPackagesRequest
{
    [JsonPropertyName("path")]
    public string Path { get; set; } = string.Empty;
}

public class PackageInfo
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("version")]
    public string Version { get; set; } = string.Empty;
}
```

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

```
@echo off
echo Starting .NET Framework MCP Server with Visual Studio 2022 MSBuild...
echo Server will listen on port 3001
echo Press Ctrl+C to stop
echo.

cd /d "%~dp0"

REM Set MSBuild path to VS2022 (adjust path as needed)
setlocal EnableDelayedExpansion

set "paths[0]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin"
set "paths[1]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin"
set "paths[2]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin"
set "paths[3]=C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin"
set "paths[4]=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin"
set "paths[5]=C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin"
set "paths[6]=C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin"
set "paths[7]=C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin"
set "paths[8]=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin"
set "paths[9]=C:\Program Files\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin"

set "MSBUILD_PATH="
for /L %%i in (0,1,9) do (
    call set "currentPath=%%paths[%%i]%%"
    if exist "!currentPath!" (
        set "MSBUILD_PATH=!currentPath!"
        goto :found
    )
)

:found
if defined MSBUILD_PATH (
    echo Using MSBuild from: %MSBUILD_PATH%
) else (
    echo Error: MSBuild path not found.
    pause
)

echo.

REM Run DotNetFramework MCP Server
if exist "publish\DotNetFrameworkMCP.Server.exe" (
    echo Using compiled executable...
    "publish\DotNetFrameworkMCP.Server.exe" --port 3001
) else (
    echo Using dotnet run, building if needed...
    dotnet run --project "src\DotNetFrameworkMCP.Server" -- --port 3001
)

pause
```

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

```csharp
using DotNetFrameworkMCP.Server.Executors;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;

namespace DotNetFrameworkMCP.Server.Services;

public interface IProcessBasedBuildService
{
    Task<Models.BuildResult> BuildProjectAsync(string projectPath, string configuration, string platform, bool restore, CancellationToken cancellationToken = default);
}

public class ProcessBasedBuildService : IProcessBasedBuildService
{
    private readonly ILogger<ProcessBasedBuildService> _logger;
    private readonly IExecutorFactory _executorFactory;

    public ProcessBasedBuildService(
        ILogger<ProcessBasedBuildService> logger,
        IExecutorFactory executorFactory)
    {
        _logger = logger;
        _executorFactory = executorFactory;
    }

    public async Task<Models.BuildResult> BuildProjectAsync(
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation("Starting build for project: {ProjectPath}", projectPath);
            
            var buildExecutor = _executorFactory.CreateBuildExecutor();
            return await buildExecutor.ExecuteBuildAsync(projectPath, configuration, platform, restore, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to create build executor or execute build for project: {ProjectPath}", projectPath);
            
            return new Models.BuildResult
            {
                Success = false,
                Errors = new List<BuildMessage>
                {
                    new BuildMessage
                    {
                        Message = ex.Message,
                        File = projectPath
                    }
                },
                Warnings = new List<BuildMessage>(),
                BuildTime = 0,
                Output = $"Build service failed: {ex.Message}"
            };
        }
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Executors;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;

namespace DotNetFrameworkMCP.Server.Services;

public interface ITestRunnerService
{
    Task<TestResult> RunTestsAsync(string projectPath, string? filter, bool verbose, CancellationToken cancellationToken = default);
}

public class TestRunnerService : ITestRunnerService
{
    private readonly ILogger<TestRunnerService> _logger;
    private readonly IExecutorFactory _executorFactory;

    public TestRunnerService(
        ILogger<TestRunnerService> logger,
        IExecutorFactory executorFactory)
    {
        _logger = logger;
        _executorFactory = executorFactory;
    }

    public async Task<TestResult> RunTestsAsync(
        string projectPath,
        string? filter,
        bool verbose,
        CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogInformation("Starting test run for project: {ProjectPath}", projectPath);
            
            var testExecutor = _executorFactory.CreateTestExecutor();
            return await testExecutor.ExecuteTestsAsync(projectPath, filter, verbose, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to create test executor or execute tests for project: {ProjectPath}", projectPath);
            
            return new TestResult
            {
                TotalTests = 0,
                PassedTests = 0,
                FailedTests = 0,
                SkippedTests = 0,
                Duration = 0,
                TestDetails = new List<TestDetail>
                {
                    new TestDetail
                    {
                        Name = "Test Execution Error",
                        ClassName = "System",
                        Result = "Failed",
                        Duration = 0,
                        ErrorMessage = ex.Message,
                        StackTrace = ex.StackTrace
                    }
                },
                Output = $"Test service failed: {ex.Message}"
            };
        }
    }
}
```

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

```csharp
using System.Text.Json;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Protocol;
using DotNetFrameworkMCP.Server.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Tools;

public class RunTestsHandler : IToolHandler
{
    private readonly ILogger<RunTestsHandler> _logger;
    private readonly McpServerConfiguration _configuration;
    private readonly ITestRunnerService _testRunnerService;

    public RunTestsHandler(
        ILogger<RunTestsHandler> logger,
        IOptions<McpServerConfiguration> configuration,
        ITestRunnerService testRunnerService)
    {
        _logger = logger;
        _configuration = configuration.Value;
        _testRunnerService = testRunnerService;
    }

    public string Name => "run_tests";

    public ToolDefinition GetDefinition()
    {
        return new ToolDefinition
        {
            Name = Name,
            Description = "Run tests in a .NET test project",
            InputSchema = new JsonSchema
            {
                Type = "object",
                Properties = new Dictionary<string, SchemaProperty>
                {
                    ["path"] = new SchemaProperty
                    {
                        Type = "string",
                        Description = "Path to test project (.csproj file)"
                    },
                    ["filter"] = new SchemaProperty
                    {
                        Type = "string",
                        Description = "Test filter expression (optional)"
                    },
                    ["verbose"] = new SchemaProperty
                    {
                        Type = "boolean",
                        Description = "Enable verbose output",
                        Default = false
                    }
                },
                Required = new List<string> { "path" }
            }
        };
    }

    public async Task<object> ExecuteAsync(JsonElement arguments)
    {
        var request = JsonSerializer.Deserialize<RunTestsRequest>(arguments.GetRawText());
        if (request == null || string.IsNullOrEmpty(request.Path))
        {
            throw new ArgumentException("Invalid test run request");
        }

        _logger.LogInformation("Running tests for project: {Path}", request.Path);

        if (!string.IsNullOrEmpty(request.Filter))
        {
            _logger.LogInformation("Using test filter: {Filter}", request.Filter);
        }

        // Create cancellation token with timeout
        using var cts = new CancellationTokenSource(_configuration.TestTimeout);

        try
        {
            return await _testRunnerService.RunTestsAsync(
                request.Path,
                request.Filter,
                request.Verbose,
                cts.Token);
        }
        catch (OperationCanceledException)
        {
            throw new TimeoutException($"Test run timed out after {_configuration.TestTimeout}ms");
        }
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Executors;
using DotNetFrameworkMCP.Server.Services;
using DotNetFrameworkMCP.Server.Tools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
        config.AddEnvironmentVariables(prefix: "MCPSERVER_");
    })
    .ConfigureServices((context, services) =>
    {
        // Configuration
        services.Configure<McpServerConfiguration>(
            context.Configuration.GetSection("McpServer"));

        // Register executors
        services.AddSingleton<MSBuildExecutor>();
        services.AddSingleton<DotNetBuildExecutor>();
        services.AddSingleton<VSTestExecutor>();
        services.AddSingleton<DotNetTestExecutor>();
        services.AddSingleton<IExecutorFactory, ExecutorFactory>();

        // Register services
        services.AddSingleton<IProcessBasedBuildService, ProcessBasedBuildService>();
        services.AddSingleton<ITestRunnerService, TestRunnerService>();

        // Register tool handlers
        services.AddSingleton<IToolHandler, BuildProjectHandler>();
        services.AddSingleton<IToolHandler, RunTestsHandler>();
        
        // Register the TCP MCP server
        services.AddSingleton<TcpMcpServer>();

        // Configure logging
        services.AddLogging(builder =>
        {
            builder.ClearProviders();
            
            // Only log to stderr to keep stdout clean for MCP messages
            builder.AddConsole(options =>
            {
                options.LogToStandardErrorThreshold = LogLevel.Trace;
            });
            
            builder.SetMinimumLevel(LogLevel.Information);
            
            if (context.Configuration.GetValue<bool>("McpServer:EnableDetailedLogging"))
            {
                builder.SetMinimumLevel(LogLevel.Debug);
            }
        });
    })
    .UseConsoleLifetime()
    .Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();

try
{
    logger.LogInformation("Starting .NET Framework MCP Server");
    
    // Create cancellation token that responds to Ctrl+C
    using var cts = new CancellationTokenSource();
    Console.CancelKeyPress += (sender, e) =>
    {
        e.Cancel = true;
        cts.Cancel();
    };

    // Parse port from command line arguments
    var port = 3001;
    if (args.Contains("--port"))
    {
        var portIndex = Array.IndexOf(args, "--port");
        if (portIndex + 1 < args.Length && int.TryParse(args[portIndex + 1], out var parsedPort))
        {
            port = parsedPort;
        }
    }

    var tcpServer = host.Services.GetRequiredService<TcpMcpServer>();
    logger.LogInformation("Starting TCP MCP Server on port {Port}", port);
    await tcpServer.RunAsync(port, cts.Token);
}
catch (Exception ex)
{
    logger.LogCritical(ex, "Server failed to start");
    Environment.Exit(1);
}

```

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

```csharp
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Executors;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;

namespace DotNetFrameworkMCP.Server.Tests.Executors;

[TestFixture]
public class MSBuildExecutorTests
{
    private Mock<ILogger<MSBuildExecutor>> _mockLogger;
    private Mock<IOptions<McpServerConfiguration>> _mockOptions;
    private McpServerConfiguration _configuration;
    private MSBuildExecutor _executor;

    [SetUp]
    public void SetUp()
    {
        _mockLogger = new Mock<ILogger<MSBuildExecutor>>();
        _mockOptions = new Mock<IOptions<McpServerConfiguration>>();
        _configuration = new McpServerConfiguration
        {
            BuildTimeout = 60000,
            PreferredVSVersion = "2022"
        };
        _mockOptions.Setup(x => x.Value).Returns(_configuration);
        _executor = new MSBuildExecutor(_mockLogger.Object, _mockOptions.Object);
    }

    [Test]
    public async Task ExecuteBuildAsync_WithNonExistentProject_ReturnsFailedResult()
    {
        // Arrange
        var nonExistentPath = "/path/to/nonexistent/project.csproj";

        // Act
        var result = await _executor.ExecuteBuildAsync(nonExistentPath, "Debug", "Any CPU", true);

        // Assert
        Assert.That(result.Success, Is.False);
        Assert.That(result.Errors, Has.Count.EqualTo(1));
        Assert.That(result.Errors[0].Message, Does.Contain("Project file not found"));
    }

    [Test]
    public async Task ExecuteBuildAsync_WithTimeout_ReturnsFailedResult()
    {
        // Arrange
        _configuration.BuildTimeout = 1; // 1ms timeout to force timeout
        var tempProjectFile = Path.GetTempFileName();
        File.WriteAllText(tempProjectFile, "<Project></Project>");

        try
        {
            // Act
            var result = await _executor.ExecuteBuildAsync(tempProjectFile, "Debug", "Any CPU", true);
            
            // Assert
            // Should return a failed result due to timeout or MSBuild not found
            Assert.That(result.Success, Is.False);
        }
        finally
        {
            // Cleanup
            if (File.Exists(tempProjectFile))
                File.Delete(tempProjectFile);
        }
    }

    [Test]
    public void Constructor_WithValidParameters_CreatesInstance()
    {
        // Act & Assert
        Assert.That(_executor, Is.Not.Null);
    }

    [TestCase("Debug", "Any CPU", true)]
    [TestCase("Release", "x64", false)]
    [TestCase("Debug", "x86", true)]
    public async Task ExecuteBuildAsync_WithDifferentConfigurations_HandlesGracefully(
        string configuration, string platform, bool restore)
    {
        // Arrange
        var tempProjectFile = Path.GetTempFileName();
        File.WriteAllText(tempProjectFile, "<Project></Project>");

        try
        {
            // Act
            var result = await _executor.ExecuteBuildAsync(tempProjectFile, configuration, platform, restore);

            // Assert
            // Result should be non-null (even if build fails due to no MSBuild)
            Assert.That(result, Is.Not.Null);
        }
        finally
        {
            // Cleanup
            if (File.Exists(tempProjectFile))
                File.Delete(tempProjectFile);
        }
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Executors;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;

namespace DotNetFrameworkMCP.Server.Tests.Executors;

[TestFixture]
public class ExecutorFactoryTests
{
    private IServiceProvider _serviceProvider;
    private Mock<IOptions<McpServerConfiguration>> _mockOptions;
    private McpServerConfiguration _configuration;

    [SetUp]
    public void SetUp()
    {
        _configuration = new McpServerConfiguration();
        _mockOptions = new Mock<IOptions<McpServerConfiguration>>();
        _mockOptions.Setup(x => x.Value).Returns(_configuration);

        var services = new ServiceCollection();
        
        // Register executors
        services.AddSingleton<MSBuildExecutor>();
        services.AddSingleton<DotNetBuildExecutor>();
        services.AddSingleton<VSTestExecutor>();
        services.AddSingleton<DotNetTestExecutor>();
        
        // Register configuration
        services.AddSingleton(_mockOptions.Object);
        
        // Register loggers
        services.AddSingleton<ILogger<MSBuildExecutor>>(Mock.Of<ILogger<MSBuildExecutor>>());
        services.AddSingleton<ILogger<DotNetBuildExecutor>>(Mock.Of<ILogger<DotNetBuildExecutor>>());
        services.AddSingleton<ILogger<VSTestExecutor>>(Mock.Of<ILogger<VSTestExecutor>>());
        services.AddSingleton<ILogger<DotNetTestExecutor>>(Mock.Of<ILogger<DotNetTestExecutor>>());

        _serviceProvider = services.BuildServiceProvider();
    }

    [TearDown]
    public void TearDown()
    {
        if (_serviceProvider is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }

    [Test]
    public void CreateBuildExecutor_WhenUseDotNetCliIsFalse_ReturnsMSBuildExecutor()
    {
        // Arrange
        _configuration.UseDotNetCli = false;
        var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);

        // Act
        var executor = factory.CreateBuildExecutor();

        // Assert
        Assert.That(executor, Is.TypeOf<MSBuildExecutor>());
    }

    [Test]
    public void CreateBuildExecutor_WhenUseDotNetCliIsTrue_ReturnsDotNetBuildExecutor()
    {
        // Arrange
        _configuration.UseDotNetCli = true;
        var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);

        // Act
        var executor = factory.CreateBuildExecutor();

        // Assert
        Assert.That(executor, Is.TypeOf<DotNetBuildExecutor>());
    }

    [Test]
    public void CreateTestExecutor_WhenUseDotNetCliIsFalse_ReturnsVSTestExecutor()
    {
        // Arrange
        _configuration.UseDotNetCli = false;
        var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);

        // Act
        var executor = factory.CreateTestExecutor();

        // Assert
        Assert.That(executor, Is.TypeOf<VSTestExecutor>());
    }

    [Test]
    public void CreateTestExecutor_WhenUseDotNetCliIsTrue_ReturnsDotNetTestExecutor()
    {
        // Arrange
        _configuration.UseDotNetCli = true;
        var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);

        // Act
        var executor = factory.CreateTestExecutor();

        // Assert
        Assert.That(executor, Is.TypeOf<DotNetTestExecutor>());
    }

    [Test]
    public void Constructor_WithValidParameters_CreatesInstance()
    {
        // Act
        var factory = new ExecutorFactory(_serviceProvider, _mockOptions.Object);

        // Assert
        Assert.That(factory, Is.Not.Null);
    }
}
```

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

```csharp
using System.Text.Json;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Services;
using DotNetFrameworkMCP.Server.Tools;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Tests.Tools;

[TestFixture]
public class BuildProjectHandlerTests
{
    private BuildProjectHandler _handler;
    private ILogger<BuildProjectHandler> _logger;
    private IOptions<McpServerConfiguration> _configuration;
    private MockProcessBasedBuildService _buildService;

    [SetUp]
    public void Setup()
    {
        _logger = new TestLogger<BuildProjectHandler>();
        _configuration = Options.Create(new McpServerConfiguration
        {
            DefaultConfiguration = "Debug",
            DefaultPlatform = "Any CPU",
            BuildTimeout = 600000
        });
        _buildService = new MockProcessBasedBuildService();
        _handler = new BuildProjectHandler(_logger, _configuration, _buildService);
    }

    [Test]
    public void GetDefinition_ReturnsCorrectToolDefinition()
    {
        var definition = _handler.GetDefinition();

        Assert.That(definition.Name, Is.EqualTo("build_project"));
        Assert.That(definition.Description, Is.EqualTo("Build a .NET project or solution"));
        Assert.That(definition.InputSchema, Is.Not.Null);
        Assert.That(definition.InputSchema.Type, Is.EqualTo("object"));
        Assert.That(definition.InputSchema.Properties, Has.Count.EqualTo(4));
        Assert.That(definition.InputSchema.Properties.ContainsKey("path"), Is.True);
        Assert.That(definition.InputSchema.Required, Contains.Item("path"));
    }

    [Test]
    public async Task ExecuteAsync_WithValidRequest_ReturnsSuccessResult()
    {
        var request = new BuildProjectRequest
        {
            Path = "test.csproj",
            Configuration = "Debug",
            Platform = "Any CPU",
            Restore = true
        };

        var json = JsonSerializer.Serialize(request);
        var element = JsonDocument.Parse(json).RootElement;

        var result = await _handler.ExecuteAsync(element);

        Assert.That(result, Is.Not.Null);
        Assert.That(result, Is.TypeOf<BuildResult>());
        
        var buildResult = (BuildResult)result;
        Assert.That(buildResult.Success, Is.True);
        Assert.That(buildResult.Errors, Is.Empty);
        Assert.That(buildResult.Warnings, Is.Empty);
        Assert.That(buildResult.Output, Is.EqualTo("Build succeeded."));
    }

    [Test]
    public void ExecuteAsync_WithInvalidRequest_ThrowsArgumentException()
    {
        var invalidJson = "{}";
        var element = JsonDocument.Parse(invalidJson).RootElement;

        Assert.ThrowsAsync<ArgumentException>(async () => await _handler.ExecuteAsync(element));
    }

    private class TestLogger<T> : ILogger<T>
    {
        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
        public bool IsEnabled(LogLevel logLevel) => true;
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { }
    }

    private class MockProcessBasedBuildService : IProcessBasedBuildService
    {
        public Task<Models.BuildResult> BuildProjectAsync(string projectPath, string configuration, string platform, bool restore, CancellationToken cancellationToken = default)
        {
            return Task.FromResult(new Models.BuildResult
            {
                Success = true,
                Errors = new List<BuildMessage>(),
                Warnings = new List<BuildMessage>(),
                BuildTime = 2.45,
                Output = "Build succeeded."
            });
        }
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// dotnet test-based test executor
/// </summary>
public class DotNetTestExecutor : BaseTestExecutor
{
    public DotNetTestExecutor(
        ILogger<DotNetTestExecutor> logger,
        IOptions<McpServerConfiguration> configuration)
        : base(logger, configuration)
    {
    }

    public override async Task<TestResult> ExecuteTestsAsync(
        string projectPath,
        string? filter,
        bool verbose,
        CancellationToken cancellationToken = default)
    {
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        
        try
        {
            // Validate project path
            if (!File.Exists(projectPath))
            {
                throw new FileNotFoundException($"Test project file not found: {projectPath}");
            }

            var arguments = new List<string>
            {
                "test",
                $"\"{projectPath}\"",
                "--no-build" // Assume project is already built
            };

            if (!string.IsNullOrEmpty(filter))
            {
                arguments.Add("--filter");
                arguments.Add($"\"{filter}\"");
            }

            if (verbose)
            {
                arguments.Add("--verbosity");
                arguments.Add("detailed");
            }
            else
            {
                arguments.Add("--verbosity");
                arguments.Add("normal");
            }

            // Add logger for structured output
            var trxFileName = $"TestResults_{Guid.NewGuid():N}.trx";
            var trxFilePath = Path.Combine(Path.GetTempPath(), trxFileName);
            arguments.Add($"--logger");
            arguments.Add($"trx;LogFileName=\"{trxFilePath}\"");

            var argumentString = string.Join(" ", arguments);
            _logger.LogInformation("Running dotnet test: {Arguments}", argumentString);

            var result = await RunProcessAsync(_configuration.DotNetPath, argumentString, cancellationToken);
            
            // Parse results from TRX file
            var testResult = await ParseTrxFileAsync(trxFilePath, result.Output);
            
            // Clean up TRX file
            try
            {
                if (File.Exists(trxFilePath))
                    File.Delete(trxFilePath);
            }
            catch (Exception ex)
            {
                _logger.LogDebug("Failed to delete TRX file: {Error}", ex.Message);
            }

            stopwatch.Stop();
            testResult.Duration = stopwatch.Elapsed.TotalSeconds;
            
            return testResult;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error running dotnet test for project: {ProjectPath}", projectPath);
            stopwatch.Stop();

            return new TestResult
            {
                TotalTests = 0,
                PassedTests = 0,
                FailedTests = 0,
                SkippedTests = 0,
                Duration = stopwatch.Elapsed.TotalSeconds,
                TestDetails = new List<TestDetail>
                {
                    new TestDetail
                    {
                        Name = "Test Execution Error",
                        ClassName = "DotNetTest",
                        Result = "Failed",
                        Duration = 0,
                        ErrorMessage = ex.Message,
                        StackTrace = ex.StackTrace
                    }
                },
                Output = $"Dotnet test execution failed: {ex.Message}\n{ex.StackTrace}"
            };
        }
    }
}
```

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

```csharp
using System.Text.Json;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Protocol;
using DotNetFrameworkMCP.Server.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Tools;

public class BuildProjectHandler : IToolHandler
{
    private readonly ILogger<BuildProjectHandler> _logger;
    private readonly McpServerConfiguration _configuration;
    private readonly IProcessBasedBuildService _buildService;

    public BuildProjectHandler(
        ILogger<BuildProjectHandler> logger,
        IOptions<McpServerConfiguration> configuration,
        IProcessBasedBuildService buildService)
    {
        _logger = logger;
        _configuration = configuration.Value;
        _buildService = buildService;
    }

    public string Name => "build_project";

    public ToolDefinition GetDefinition()
    {
        return new ToolDefinition
        {
            Name = Name,
            Description = "Build a .NET project or solution",
            InputSchema = new JsonSchema
            {
                Type = "object",
                Properties = new Dictionary<string, SchemaProperty>
                {
                    ["path"] = new SchemaProperty
                    {
                        Type = "string",
                        Description = "Path to .csproj or .sln file"
                    },
                    ["configuration"] = new SchemaProperty
                    {
                        Type = "string",
                        Description = "Build configuration",
                        Enum = new List<string> { "Debug", "Release" },
                        Default = "Debug"
                    },
                    ["platform"] = new SchemaProperty
                    {
                        Type = "string",
                        Description = "Target platform",
                        Enum = new List<string> { "Any CPU", "x86", "x64" },
                        Default = "Any CPU"
                    },
                    ["restore"] = new SchemaProperty
                    {
                        Type = "boolean",
                        Description = "Restore NuGet packages",
                        Default = true
                    }
                },
                Required = new List<string> { "path" }
            }
        };
    }

    public async Task<object> ExecuteAsync(JsonElement arguments)
    {
        var request = JsonSerializer.Deserialize<BuildProjectRequest>(arguments.GetRawText());
        if (request == null || string.IsNullOrEmpty(request.Path))
        {
            throw new ArgumentException("Invalid build request");
        }

        _logger.LogInformation("Building project: {Path}", request.Path);

        // Use default values from configuration if not specified
        var configuration = string.IsNullOrEmpty(request.Configuration) 
            ? _configuration.DefaultConfiguration 
            : request.Configuration;
        
        var platform = string.IsNullOrEmpty(request.Platform) 
            ? _configuration.DefaultPlatform 
            : request.Platform;

        // Create cancellation token with timeout
        using var cts = new CancellationTokenSource(_configuration.BuildTimeout);

        try
        {
            _logger.LogDebug("Building project using process-based MSBuild approach");
            return await _buildService.BuildProjectAsync(
                request.Path,
                configuration,
                platform,
                request.Restore,
                cts.Token);
        }
        catch (OperationCanceledException)
        {
            throw new TimeoutException($"Build timed out after {_configuration.BuildTimeout}ms");
        }
    }
}
```

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

```csharp
using System.Text.Json;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Services;
using DotNetFrameworkMCP.Server.Tools;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;

namespace DotNetFrameworkMCP.Server.Tests.Tools;

[TestFixture]
public class RunTestsHandlerTests
{
    private Mock<ILogger<RunTestsHandler>> _mockLogger;
    private Mock<ITestRunnerService> _mockTestRunnerService;
    private IOptions<McpServerConfiguration> _configuration;
    private RunTestsHandler _handler;

    [SetUp]
    public void SetUp()
    {
        _mockLogger = new Mock<ILogger<RunTestsHandler>>();
        _mockTestRunnerService = new Mock<ITestRunnerService>();
        _configuration = Options.Create(new McpServerConfiguration
        {
            TestTimeout = 300000
        });

        _handler = new RunTestsHandler(_mockLogger.Object, _configuration, _mockTestRunnerService.Object);
    }

    [Test]
    public void Name_ShouldReturnCorrectName()
    {
        Assert.That(_handler.Name, Is.EqualTo("run_tests"));
    }

    [Test]
    public void GetDefinition_ShouldReturnValidDefinition()
    {
        var definition = _handler.GetDefinition();

        Assert.That(definition.Name, Is.EqualTo("run_tests"));
        Assert.That(definition.Description, Is.EqualTo("Run tests in a .NET test project"));
        Assert.That(definition.InputSchema.Type, Is.EqualTo("object"));
        Assert.That(definition.InputSchema.Properties, Contains.Key("path"));
        Assert.That(definition.InputSchema.Properties["path"].Type, Is.EqualTo("string"));
        Assert.That(definition.InputSchema.Required, Contains.Item("path"));
    }

    [Test]
    public async Task ExecuteAsync_WithValidRequest_ShouldCallTestRunnerService()
    {
        // Arrange
        var expectedResult = new TestResult
        {
            TotalTests = 5,
            PassedTests = 4,
            FailedTests = 1,
            SkippedTests = 0,
            Duration = 2.5,
            TestDetails = new List<TestDetail>()
        };

        _mockTestRunnerService
            .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        var request = new RunTestsRequest
        {
            Path = "TestProject.csproj",
            Filter = "Category=Unit",
            Verbose = true
        };

        var jsonElement = JsonSerializer.SerializeToElement(request);

        // Act
        var result = await _handler.ExecuteAsync(jsonElement);

        // Assert
        Assert.That(result, Is.EqualTo(expectedResult));
        _mockTestRunnerService.Verify(
            x => x.RunTestsAsync("TestProject.csproj", "Category=Unit", true, It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Test]
    public async Task ExecuteAsync_WithMinimalRequest_ShouldUseDefaults()
    {
        // Arrange
        var expectedResult = new TestResult();
        _mockTestRunnerService
            .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        var request = new RunTestsRequest
        {
            Path = "TestProject.csproj"
        };

        var jsonElement = JsonSerializer.SerializeToElement(request);

        // Act
        var result = await _handler.ExecuteAsync(jsonElement);

        // Assert
        _mockTestRunnerService.Verify(
            x => x.RunTestsAsync("TestProject.csproj", null, false, It.IsAny<CancellationToken>()),
            Times.Once);
    }

    [Test]
    public void ExecuteAsync_WithNullRequest_ShouldThrowArgumentException()
    {
        // Arrange
        var jsonElement = JsonSerializer.SerializeToElement((object?)null);

        // Act & Assert
        Assert.ThrowsAsync<ArgumentException>(() => _handler.ExecuteAsync(jsonElement));
    }

    [Test]
    public void ExecuteAsync_WithEmptyPath_ShouldThrowArgumentException()
    {
        // Arrange
        var request = new RunTestsRequest { Path = "" };
        var jsonElement = JsonSerializer.SerializeToElement(request);

        // Act & Assert
        Assert.ThrowsAsync<ArgumentException>(() => _handler.ExecuteAsync(jsonElement));
    }

    [Test]
    public void ExecuteAsync_WhenServiceTimesOut_ShouldThrowTimeoutException()
    {
        // Arrange
        _mockTestRunnerService
            .Setup(x => x.RunTestsAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
            .Throws<OperationCanceledException>();

        var request = new RunTestsRequest { Path = "TestProject.csproj" };
        var jsonElement = JsonSerializer.SerializeToElement(request);

        // Act & Assert
        Assert.ThrowsAsync<TimeoutException>(() => _handler.ExecuteAsync(jsonElement));
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Executors;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Services;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;

namespace DotNetFrameworkMCP.Server.Tests.Services;

[TestFixture]
public class TestRunnerServiceTests
{
    private Mock<ILogger<TestRunnerService>> _mockLogger;
    private Mock<IExecutorFactory> _mockExecutorFactory;
    private Mock<ITestExecutor> _mockTestExecutor;
    private TestRunnerService _service;
    private string _tempProjectFile;

    [SetUp]
    public void SetUp()
    {
        _mockLogger = new Mock<ILogger<TestRunnerService>>();
        _mockExecutorFactory = new Mock<IExecutorFactory>();
        _mockTestExecutor = new Mock<ITestExecutor>();

        _mockExecutorFactory.Setup(x => x.CreateTestExecutor())
            .Returns(_mockTestExecutor.Object);

        _service = new TestRunnerService(_mockLogger.Object, _mockExecutorFactory.Object);

        // Create a temporary project file for testing
        _tempProjectFile = Path.GetTempFileName();
        File.WriteAllText(_tempProjectFile, @"
<Project Sdk=""Microsoft.NET.Sdk"">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include=""Microsoft.NET.Test.Sdk"" Version=""17.0.0"" />
    <PackageReference Include=""MSTest.TestFramework"" Version=""2.2.7"" />
    <PackageReference Include=""MSTest.TestAdapter"" Version=""2.2.7"" />
  </ItemGroup>
</Project>");
    }

    [TearDown]
    public void TearDown()
    {
        if (File.Exists(_tempProjectFile))
        {
            File.Delete(_tempProjectFile);
        }
    }

    [Test]
    public async Task RunTestsAsync_CallsExecutorFactory()
    {
        // Arrange
        var expectedResult = new TestResult
        {
            TotalTests = 5,
            PassedTests = 4,
            FailedTests = 1,
            SkippedTests = 0,
            Duration = 2.5,
            TestDetails = new List<TestDetail>(),
            Output = "Test output"
        };

        _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        // Act
        var result = await _service.RunTestsAsync(_tempProjectFile, null, false);

        // Assert
        Assert.That(result, Is.EqualTo(expectedResult));
        _mockExecutorFactory.Verify(x => x.CreateTestExecutor(), Times.Once);
        _mockTestExecutor.Verify(x => x.ExecuteTestsAsync(
            _tempProjectFile, null, false, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Test]
    public async Task RunTestsAsync_WithFilter_PassesFilterToExecutor()
    {
        // Arrange
        var filter = "TestCategory=Unit";
        var expectedResult = new TestResult { TotalTests = 1, PassedTests = 1 };

        _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
                It.IsAny<string>(),
                filter,
                It.IsAny<bool>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        // Act
        var result = await _service.RunTestsAsync(_tempProjectFile, filter, true);

        // Assert
        _mockTestExecutor.Verify(x => x.ExecuteTestsAsync(
            _tempProjectFile, filter, true, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Test]
    public async Task RunTestsAsync_WhenExecutorThrows_ReturnsFailedResult()
    {
        // Arrange
        _mockTestExecutor.Setup(x => x.ExecuteTestsAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<CancellationToken>()))
            .ThrowsAsync(new InvalidOperationException("Test executor failed"));

        // Act
        var result = await _service.RunTestsAsync(_tempProjectFile, null, false);

        // Assert
        Assert.That(result.TotalTests, Is.EqualTo(0));
        Assert.That(result.PassedTests, Is.EqualTo(0));
        Assert.That(result.FailedTests, Is.EqualTo(0));
        Assert.That(result.SkippedTests, Is.EqualTo(0));
        Assert.That(result.TestDetails, Has.Count.EqualTo(1));
        Assert.That(result.TestDetails[0].Result, Is.EqualTo("Failed"));
        Assert.That(result.TestDetails[0].ErrorMessage, Does.Contain("Test executor failed"));
    }

    [Test]
    public async Task RunTestsAsync_WhenFactoryThrows_ReturnsFailedResult()
    {
        // Arrange
        _mockExecutorFactory.Setup(x => x.CreateTestExecutor())
            .Throws(new InvalidOperationException("Factory failed"));

        // Act
        var result = await _service.RunTestsAsync(_tempProjectFile, null, false);

        // Assert
        Assert.That(result.TotalTests, Is.EqualTo(0));
        Assert.That(result.TestDetails, Has.Count.EqualTo(1));
        Assert.That(result.TestDetails[0].Result, Is.EqualTo("Failed"));
        Assert.That(result.Output, Does.Contain("Test service failed"));
    }
}
```

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

```csharp
using DotNetFrameworkMCP.Server.Executors;
using DotNetFrameworkMCP.Server.Models;
using DotNetFrameworkMCP.Server.Services;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;

namespace DotNetFrameworkMCP.Server.Tests.Services;

[TestFixture]
public class ProcessBasedBuildServiceTests
{
    private Mock<ILogger<ProcessBasedBuildService>> _mockLogger;
    private Mock<IExecutorFactory> _mockExecutorFactory;
    private Mock<IBuildExecutor> _mockBuildExecutor;
    private ProcessBasedBuildService _service;
    private string _tempProjectFile;

    [SetUp]
    public void SetUp()
    {
        _mockLogger = new Mock<ILogger<ProcessBasedBuildService>>();
        _mockExecutorFactory = new Mock<IExecutorFactory>();
        _mockBuildExecutor = new Mock<IBuildExecutor>();

        _mockExecutorFactory.Setup(x => x.CreateBuildExecutor())
            .Returns(_mockBuildExecutor.Object);

        _service = new ProcessBasedBuildService(_mockLogger.Object, _mockExecutorFactory.Object);

        // Create a temporary project file for testing
        _tempProjectFile = Path.GetTempFileName();
        File.WriteAllText(_tempProjectFile, @"
<Project Sdk=""Microsoft.NET.Sdk"">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
  </PropertyGroup>
</Project>");
    }

    [TearDown]
    public void TearDown()
    {
        if (File.Exists(_tempProjectFile))
        {
            File.Delete(_tempProjectFile);
        }
    }

    [Test]
    public async Task BuildProjectAsync_CallsExecutorFactory()
    {
        // Arrange
        var expectedResult = new BuildResult
        {
            Success = true,
            BuildTime = 5.2,
            Errors = new List<BuildMessage>(),
            Warnings = new List<BuildMessage>(),
            Output = "Build succeeded"
        };

        _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        // Act
        var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);

        // Assert
        Assert.That(result, Is.EqualTo(expectedResult));
        _mockExecutorFactory.Verify(x => x.CreateBuildExecutor(), Times.Once);
        _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
            _tempProjectFile, "Debug", "Any CPU", true, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Test]
    public async Task BuildProjectAsync_WithDifferentParameters_PassesToExecutor()
    {
        // Arrange
        var expectedResult = new BuildResult { Success = true };
        var configuration = "Release";
        var platform = "x64";
        var restore = false;

        _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
                It.IsAny<string>(),
                configuration,
                platform,
                restore,
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(expectedResult);

        // Act
        var result = await _service.BuildProjectAsync(_tempProjectFile, configuration, platform, restore);

        // Assert
        _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
            _tempProjectFile, configuration, platform, restore, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Test]
    public async Task BuildProjectAsync_WhenExecutorThrows_ReturnsFailedResult()
    {
        // Arrange
        _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<bool>(),
                It.IsAny<CancellationToken>()))
            .ThrowsAsync(new InvalidOperationException("Build executor failed"));

        // Act
        var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);

        // Assert
        Assert.That(result.Success, Is.False);
        Assert.That(result.Errors, Has.Count.EqualTo(1));
        Assert.That(result.Errors[0].Message, Does.Contain("Build executor failed"));
        Assert.That(result.Output, Does.Contain("Build service failed"));
    }

    [Test]
    public async Task BuildProjectAsync_WhenFactoryThrows_ReturnsFailedResult()
    {
        // Arrange
        _mockExecutorFactory.Setup(x => x.CreateBuildExecutor())
            .Throws(new InvalidOperationException("Factory failed"));

        // Act
        var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true);

        // Assert
        Assert.That(result.Success, Is.False);
        Assert.That(result.Errors, Has.Count.EqualTo(1));
        Assert.That(result.Errors[0].Message, Does.Contain("Factory failed"));
        Assert.That(result.BuildTime, Is.EqualTo(0));
    }

    [Test]
    public async Task BuildProjectAsync_WithCancellationToken_PassesToExecutor()
    {
        // Arrange
        var expectedResult = new BuildResult { Success = true };
        var cancellationTokenSource = new CancellationTokenSource();

        _mockBuildExecutor.Setup(x => x.ExecuteBuildAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<bool>(),
                cancellationTokenSource.Token))
            .ReturnsAsync(expectedResult);

        // Act
        var result = await _service.BuildProjectAsync(_tempProjectFile, "Debug", "Any CPU", true, cancellationTokenSource.Token);

        // Assert
        _mockBuildExecutor.Verify(x => x.ExecuteBuildAsync(
            _tempProjectFile, "Debug", "Any CPU", true, cancellationTokenSource.Token), Times.Once);
    }
}
```

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

```markdown
# .NET Framework MCP Service Project Plan

## Project Overview

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.

## Core Requirements

### 1. Build Operations
- Trigger MSBuild for solutions and projects
- Support multiple build configurations (Debug/Release)
- Support multiple platforms (Any CPU, x86, x64)
- Handle NuGet package restoration
- Parse and return structured build output (errors, warnings, success status)

### 2. Test Operations
- Discover test projects and test methods
- Execute all tests or specific test selections
- Support multiple test frameworks (MSTest, NUnit, xUnit)
- Parse and return test results with pass/fail counts and error details

### 3. Run Operations
- Execute console applications with arguments
- Capture and return console output
- Handle process termination and timeouts

### 4. Project Analysis
- List projects in a solution
- Show project dependencies
- Display project properties and configurations
- List NuGet packages and versions

## Technical Architecture

### Technology Stack
- **Language**: C# (.NET 6+ for the MCP service itself)
- **MCP SDK**: Use official MCP SDK for C# (if available) or implement protocol directly
- **Dependencies**:
  - Microsoft.Build for MSBuild integration
  - Microsoft.Build.Locator for finding MSBuild installations
  - Test framework APIs for test discovery/execution

### MCP Methods to Implement

```
tools:
  - name: build_project
    description: Build a .NET project or solution
    inputSchema:
      type: object
      properties:
        path: { type: string, description: "Path to .csproj or .sln file" }
        configuration: { type: string, enum: ["Debug", "Release"], default: "Debug" }
        platform: { type: string, enum: ["Any CPU", "x86", "x64"], default: "Any CPU" }
        restore: { type: boolean, default: true, description: "Restore NuGet packages" }

  - name: run_tests
    description: Run tests in a .NET test project
    inputSchema:
      type: object
      properties:
        path: { type: string, description: "Path to test project" }
        filter: { type: string, description: "Test filter expression" }
        verbose: { type: boolean, default: false }

  - name: run_project
    description: Execute a .NET console application
    inputSchema:
      type: object
      properties:
        path: { type: string, description: "Path to project" }
        args: { type: array, items: { type: string } }
        workingDirectory: { type: string }

  - name: analyze_solution
    description: Get information about a solution structure
    inputSchema:
      type: object
      properties:
        path: { type: string, description: "Path to .sln file" }

  - name: list_packages
    description: List NuGet packages in a project
    inputSchema:
      type: object
      properties:
        path: { type: string, description: "Path to project" }
```

## Implementation Phases

### Phase 1: Core Infrastructure (Week 1) ✅ COMPLETED
- [x] Set up C# project structure
- [x] Implement MCP protocol handling with JSON-RPC support
- [x] Create basic server lifecycle (start, stop, health check)
- [x] Implement logging framework with configurable verbosity
- [x] Add configuration management with environment variable support
- [x] Switch test framework to NUnit
- [x] Create initial unit tests

### Phase 2: Build Functionality (Week 2) ✅ COMPLETED
- [x] Implement MSBuild locator logic with Visual Studio version selection
- [x] Create build_project method
- [x] Add build output parsing with intelligent truncation
- [x] Implement error/warning extraction
- [x] Add NuGet restore functionality
- [x] Add TCP server support for cross-platform communication
- [x] Create WSL-to-Windows bridge for Claude Code integration
- [x] Implement build cancellation and timeout handling
- [x] Add MCP token limit compliance (25k token limit)

### Phase 3: Test Runner Integration (Week 3) ✅ COMPLETED
- [x] Implement test discovery logic
- [x] Add support for MSTest runner
- [x] Add support for NUnit runner  
- [x] Add support for xUnit runner
- [x] Create test result parsing with TRX file support
- [x] Implement test filtering
- [x] Add comprehensive error message and stack trace extraction
- [x] Implement test adapter discovery and integration
- [x] Add solution-based building for proper test configuration

### Phase 4: Project Execution (Week 4)
- [ ] Implement run_project method
- [ ] Add process management
- [ ] Implement output capture
- [ ] Add timeout handling
- [ ] Handle process termination
- [ ] Write unit tests for execution operations

### Phase 5: Analysis Features (Week 5)
- [ ] Implement solution analysis
- [ ] Add project dependency mapping
- [ ] Create package listing functionality
- [ ] Add project property inspection
- [ ] Write unit tests for analysis operations

### Phase 6: Polish & Documentation (Week 6)
- [ ] Comprehensive error handling
- [ ] Performance optimization
- [ ] Add integration tests
- [ ] Write user documentation
- [ ] Create example usage scenarios
- [ ] Package for distribution

## Key Implementation Details

### MSBuild Integration
```csharp
// Use Microsoft.Build.Locator to find MSBuild
MSBuildLocator.RegisterDefaults();

// Use Microsoft.Build API for building
var projectCollection = new ProjectCollection();
var project = projectCollection.LoadProject(projectPath);
```

### Output Parsing Strategy
- Use MSBuild loggers to capture structured output
- Implement custom logger for JSON-formatted results
- Parse compiler error format: `file(line,col): error CODE: message`

### Test Runner Integration
- Use VSTest.Console.exe for universal test execution
- Parse TRX files for structured results
- Support test filtering using standard syntax

### Error Handling
- Graceful handling of missing MSBuild installations
- Clear error messages for missing dependencies
- Timeout handling for long-running operations
- Process cleanup on service shutdown

## Configuration Schema

```json
{
  "McpServer": {
    "MsBuildPath": "auto",
    "DefaultConfiguration": "Debug",
    "DefaultPlatform": "Any CPU",
    "TestTimeout": 300000,
    "BuildTimeout": 1200000,
    "EnableDetailedLogging": false,
    "PreferredVSVersion": "2022"
  }
}
```

## Testing Strategy

### Unit Tests
- Mock MSBuild API calls
- Test output parsing logic
- Verify error handling paths
- Test configuration management

### Integration Tests
- Use sample .NET projects
- Test full build/test/run cycles
- Verify cross-framework compatibility
- Test error scenarios (missing files, bad syntax)

## Distribution Plan

1. **NuGet Package**: Primary distribution as a .NET tool
2. **GitHub Releases**: Compiled binaries with installation script
3. **Docker Image**: Containerized version with MSBuild pre-installed

## Success Criteria

- Successfully builds complex multi-project solutions
- Accurately reports build errors and warnings
- Runs tests from all major test frameworks
- Provides clear, actionable error messages
- Performs operations within reasonable time limits
- Maintains stability during long-running operations

## Future Enhancements

### Security & Authentication
- API key or token-based authentication
- Role-based access control for different operations
- Audit logging for security compliance
- Network access restrictions and whitelisting

### Extended Platform Support
- Support for .NET Core/.NET 5+ projects
- Web project launching with browser integration
- Support for F# and VB.NET projects

### Advanced Development Features
- Code coverage reporting
- Incremental build support
- Watch mode for continuous building/testing
- Integration with code analyzers
- Performance profiling and diagnostics

## Resources & References

- [MCP Specification](https://modelcontextprotocol.io/docs)
- [MSBuild API Documentation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.build)
- [VSTest Documentation](https://docs.microsoft.com/en-us/visualstudio/test/vstest-console-options)
- [NuGet API Reference](https://docs.microsoft.com/en-us/nuget/api/overview)
```

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

```csharp
using System.Diagnostics;
using System.Text;
using System.Xml.Linq;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// Base class for test executors with common functionality
/// </summary>
public abstract class BaseTestExecutor : ITestExecutor
{
    protected readonly ILogger _logger;
    protected readonly McpServerConfiguration _configuration;

    protected BaseTestExecutor(ILogger logger, IOptions<McpServerConfiguration> configuration)
    {
        _logger = logger;
        _configuration = configuration.Value;
    }

    public abstract Task<TestResult> ExecuteTestsAsync(
        string projectPath,
        string? filter,
        bool verbose,
        CancellationToken cancellationToken = default);

    protected async Task<(string Output, int ExitCode)> RunProcessAsync(
        string fileName,
        string arguments,
        CancellationToken cancellationToken)
    {
        _logger.LogDebug("Running: {FileName} {Arguments}", fileName, arguments);

        using var process = new Process();
        process.StartInfo.FileName = fileName;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.CreateNoWindow = true;

        var output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        // Wait for completion with timeout and cancellation support
        var timeoutMs = _configuration.TestTimeout;
        using var timeoutCts = new CancellationTokenSource(timeoutMs);
        using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);

        try
        {
            await process.WaitForExitAsync(combinedCts.Token);
        }
        catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
        {
            _logger.LogWarning("Test execution timed out after {TimeoutMs}ms, killing process", timeoutMs);
            process.Kill();
            throw new TimeoutException($"Test execution timed out after {timeoutMs}ms");
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Test execution cancelled by user, killing process");
            process.Kill();
            throw;
        }

        return (output.ToString(), process.ExitCode);
    }

    protected async Task<TestResult> ParseTrxFileAsync(string trxFilePath, string consoleOutput)
    {
        var testDetails = new List<TestDetail>();

        try
        {
            if (File.Exists(trxFilePath))
            {
                var trxContent = await File.ReadAllTextAsync(trxFilePath);
                _logger.LogDebug("Parsing TRX file: {TrxFilePath}", trxFilePath);

                var doc = XDocument.Parse(trxContent);
                var ns = doc.Root?.GetDefaultNamespace();

                if (ns != null)
                {
                    // Parse test results
                    var unitTestResults = doc.Descendants(ns + "UnitTestResult");

                    foreach (var result in unitTestResults)
                    {
                        var testId = result.Attribute("testId")?.Value;
                        var testName = result.Attribute("testName")?.Value ?? "Unknown Test";
                        var outcome = result.Attribute("outcome")?.Value ?? "Unknown";
                        var duration = result.Attribute("duration")?.Value;

                        // Try to find the test definition to get class information
                        var className = "Unknown";
                        if (!string.IsNullOrEmpty(testId))
                        {
                            var testDefinition = doc.Descendants(ns + "UnitTest")
                                .FirstOrDefault(t => t.Attribute("id")?.Value == testId);

                            if (testDefinition != null)
                            {
                                var testMethod = testDefinition.Descendants(ns + "TestMethod").FirstOrDefault();
                                className = testMethod?.Attribute("className")?.Value ?? "Unknown";
                            }
                        }

                        // Parse duration
                        var durationSeconds = 0.0;
                        if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var durationTimeSpan))
                        {
                            durationSeconds = durationTimeSpan.TotalSeconds;
                        }

                        // Get error message and stack trace for failed tests
                        string? errorMessage = null;
                        string? stackTrace = null;

                        if (outcome == "Failed")
                        {
                            var output = result.Element(ns + "Output");
                            var errorInfo = output?.Element(ns + "ErrorInfo");
                            errorMessage = errorInfo?.Element(ns + "Message")?.Value;
                            stackTrace = errorInfo?.Element(ns + "StackTrace")?.Value;
                        }

                        testDetails.Add(new TestDetail
                        {
                            Name = testName,
                            ClassName = className,
                            Result = outcome,
                            Duration = durationSeconds,
                            ErrorMessage = errorMessage,
                            StackTrace = stackTrace
                        });
                    }
                }
            }

            // If no tests were parsed from TRX, fall back to console output parsing
            if (testDetails.Count == 0)
            {
                _logger.LogWarning("No tests found in TRX file, falling back to console output parsing");
                ParseConsoleOutput(consoleOutput, testDetails);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error parsing TRX file: {TrxFilePath}", trxFilePath);
            // Fall back to console output parsing
            ParseConsoleOutput(consoleOutput, testDetails);
        }

        return new TestResult
        {
            TotalTests = testDetails.Count,
            PassedTests = testDetails.Count(t => t.Result == "Passed"),
            FailedTests = testDetails.Count(t => t.Result == "Failed"),
            SkippedTests = testDetails.Count(t => t.Result == "Skipped"),
            TestDetails = testDetails,
            Output = consoleOutput
        };
    }

    private void ParseConsoleOutput(string output, List<TestDetail> testDetails)
    {
        if (string.IsNullOrEmpty(output))
            return;

        var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
        
        foreach (var line in lines)
        {
            // Simple pattern matching for common test output formats
            if (line.Contains("Passed") || line.Contains("Failed") || line.Contains("Skipped"))
            {
                var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length >= 2)
                {
                    var result = parts.FirstOrDefault(p => p == "Passed" || p == "Failed" || p == "Skipped");
                    if (result != null)
                    {
                        testDetails.Add(new TestDetail
                        {
                            Name = line.Trim(),
                            ClassName = "ParsedFromConsole",
                            Result = result,
                            Duration = 0,
                            ErrorMessage = result == "Failed" ? "Failed (parsed from console output)" : null,
                            StackTrace = null
                        });
                    }
                }
            }
        }
    }
}
```

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

```csharp
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Protocol;
using DotNetFrameworkMCP.Server.Tools;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Services;

public class TcpMcpServer
{
    private readonly ILogger<TcpMcpServer> _logger;
    private readonly McpServerConfiguration _configuration;
    private readonly Dictionary<string, IToolHandler> _toolHandlers;
    private readonly JsonSerializerOptions _jsonOptions;
    private TcpListener? _listener;

    public TcpMcpServer(
        ILogger<TcpMcpServer> logger,
        IOptions<McpServerConfiguration> configuration,
        IEnumerable<IToolHandler> toolHandlers)
    {
        _logger = logger;
        _configuration = configuration.Value;
        _toolHandlers = toolHandlers.ToDictionary(h => h.Name);
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        };
    }

    public async Task RunAsync(int port = 3001, CancellationToken cancellationToken = default)
    {
        _listener = new TcpListener(IPAddress.Any, port);
        _listener.Start();
        
        _logger.LogInformation("TCP MCP Server listening on port {Port}", port);
        
        // Register cancellation callback to stop the listener
        using var registration = cancellationToken.Register(() =>
        {
            _logger.LogInformation("Cancellation requested, stopping TCP listener");
            _listener?.Stop();
        });

        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                var client = await _listener.AcceptTcpClientAsync().WaitAsync(cancellationToken);
                _logger.LogInformation("Client connected from {RemoteEndPoint}", client.Client.RemoteEndPoint);
                
                // Handle each client in a separate task
                _ = Task.Run(async () => await HandleClientAsync(client, cancellationToken), cancellationToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Expected when cancellation occurs
            _logger.LogInformation("TCP server shutdown requested");
        }
        catch (ObjectDisposedException)
        {
            // Expected when cancellation occurs
        }
        finally
        {
            _listener?.Stop();
            _listener = null;
            _logger.LogInformation("TCP MCP Server stopped");
        }
    }

    private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken)
    {
        try
        {
            using (client)
            {
                var stream = client.GetStream();
                var buffer = new byte[4096];
                var messageBuffer = new List<byte>();

                while (!cancellationToken.IsCancellationRequested && client.Connected)
                {
                    var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
                    if (bytesRead == 0)
                        break;

                    messageBuffer.AddRange(buffer.Take(bytesRead));

                    // Process complete messages (look for newline delimiters)
                    while (true)
                    {
                        var newlineIndex = messageBuffer.IndexOf((byte)'\n');
                        if (newlineIndex == -1)
                            break;

                        var messageBytes = messageBuffer.Take(newlineIndex).ToArray();
                        messageBuffer.RemoveRange(0, newlineIndex + 1);

                        if (messageBytes.Length > 0)
                        {
                            var response = await ProcessMessageAsync(messageBytes);
                            if (response != null)
                            {
                                var responseJson = JsonSerializer.Serialize(response, _jsonOptions);
                                var responseBytes = Encoding.UTF8.GetBytes(responseJson + "\n");
                                await stream.WriteAsync(responseBytes, 0, responseBytes.Length, cancellationToken);
                                await stream.FlushAsync(cancellationToken);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error handling client");
        }
    }

    private async Task<McpMessage?> ProcessMessageAsync(byte[] messageBytes)
    {
        try
        {
            var message = JsonSerializer.Deserialize<McpMessage>(messageBytes, _jsonOptions);
            if (message == null)
            {
                return CreateErrorResponse(null, McpErrorCodes.ParseError, "Failed to parse message");
            }

            _logger.LogDebug("Received message: {Method}", message.Method);

            // Handle different MCP methods
            switch (message.Method)
            {
                case "initialize":
                    return await HandleInitializeAsync(message);
                
                case "tools/list":
                    return await HandleToolsListAsync(message);
                
                case "tools/call":
                    return await HandleToolCallAsync(message);
                
                case "notifications/cancelled":
                    // Handle cancellation notification from client
                    _logger.LogInformation("Received cancellation notification from client");
                    return null; // Notifications don't require responses
                
                case null when message.Id != null:
                    // This is likely a response to a request we made
                    return null;
                
                default:
                    _logger.LogWarning("Unknown method received: {Method}", message.Method);
                    return CreateErrorResponse(
                        message.Id,
                        McpErrorCodes.MethodNotFound,
                        $"Method '{message.Method}' not found");
            }
        }
        catch (JsonException ex)
        {
            _logger.LogError(ex, "Failed to parse JSON message");
            return CreateErrorResponse(null, McpErrorCodes.ParseError, "Invalid JSON");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing message");
            return CreateErrorResponse(null, McpErrorCodes.InternalError, ex.Message);
        }
    }

    private Task<McpMessage> HandleInitializeAsync(McpMessage request)
    {
        var response = new McpMessage
        {
            Id = request.Id,
            Result = new
            {
                protocolVersion = "2025-06-18",
                capabilities = new
                {
                    tools = new { }
                },
                serverInfo = new
                {
                    name = "dotnet-framework-mcp-tcp",
                    version = "1.0.0"
                }
            }
        };

        return Task.FromResult(response);
    }

    private Task<McpMessage> HandleToolsListAsync(McpMessage request)
    {
        var tools = _toolHandlers.Values.Select(h => h.GetDefinition()).ToList();
        
        var response = new McpMessage
        {
            Id = request.Id,
            Result = new { tools }
        };

        return Task.FromResult(response);
    }

    private async Task<McpMessage> HandleToolCallAsync(McpMessage request)
    {
        try
        {
            var toolCallParams = JsonSerializer.Deserialize<ToolCallParams>(
                JsonSerializer.Serialize(request.Params),
                _jsonOptions);

            if (toolCallParams == null || string.IsNullOrEmpty(toolCallParams.Name))
            {
                return CreateErrorResponse(
                    request.Id,
                    McpErrorCodes.InvalidParams,
                    "Invalid tool call parameters");
            }

            if (!_toolHandlers.TryGetValue(toolCallParams.Name, out var handler))
            {
                return CreateErrorResponse(
                    request.Id,
                    McpErrorCodes.InvalidParams,
                    $"Tool '{toolCallParams.Name}' not found");
            }

            var result = await handler.ExecuteAsync(toolCallParams.Arguments);

            return new McpMessage
            {
                Id = request.Id,
                Result = new
                {
                    content = new[]
                    {
                        new
                        {
                            type = "text",
                            text = JsonSerializer.Serialize(result, _jsonOptions)
                        }
                    }
                }
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error executing tool");
            return CreateErrorResponse(
                request.Id,
                McpErrorCodes.InternalError,
                $"Tool execution failed: {ex.Message}");
        }
    }

    private McpMessage CreateErrorResponse(object? id, int code, string message)
    {
        return new McpMessage
        {
            Id = id,
            Error = new McpError
            {
                Code = code,
                Message = message
            }
        };
    }

    private class ToolCallParams
    {
        public string Name { get; set; } = string.Empty;
        public JsonElement Arguments { get; set; }
    }
}
```

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

```csharp
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// dotnet CLI-based build executor
/// </summary>
public class DotNetBuildExecutor : IBuildExecutor
{
    private readonly ILogger<DotNetBuildExecutor> _logger;
    private readonly McpServerConfiguration _configuration;

    public DotNetBuildExecutor(
        ILogger<DotNetBuildExecutor> logger,
        IOptions<McpServerConfiguration> configuration)
    {
        _logger = logger;
        _configuration = configuration.Value;
    }

    public async Task<BuildResult> ExecuteBuildAsync(
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken = default)
    {
        var stopwatch = Stopwatch.StartNew();
        var errors = new List<BuildMessage>();
        var warnings = new List<BuildMessage>();

        try
        {
            // Validate project path
            if (!File.Exists(projectPath))
            {
                throw new FileNotFoundException($"Project file not found: {projectPath}");
            }

            _logger.LogInformation("Using dotnet CLI for build");

            // Build the project using dotnet CLI
            var result = await RunDotNetBuildAsync(projectPath, configuration, platform, restore, cancellationToken);
            
            // Parse the output for errors and warnings
            ParseBuildOutput(result.Output, errors, warnings);

            stopwatch.Stop();

            return new BuildResult
            {
                Success = result.ExitCode == 0,
                Errors = errors,
                Warnings = warnings,
                BuildTime = stopwatch.Elapsed.TotalSeconds,
                Output = TruncateOutput(result.Output, result.ExitCode != 0)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Build failed for project: {ProjectPath}", projectPath);
            stopwatch.Stop();

            return new BuildResult
            {
                Success = false,
                Errors = new List<BuildMessage>
                {
                    new BuildMessage
                    {
                        Message = ex.Message,
                        File = projectPath
                    }
                },
                Warnings = warnings,
                BuildTime = stopwatch.Elapsed.TotalSeconds,
                Output = TruncateOutput($"Build failed with exception: {ex.Message}\n{ex.StackTrace}", true)
            };
        }
    }

    private async Task<(int ExitCode, string Output)> RunDotNetBuildAsync(
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken)
    {
        var arguments = new List<string>
        {
            "build",
            $"\"{projectPath}\"",
            $"--configuration", configuration,
            "--verbosity", "normal"
        };

        // Platform is typically handled differently in dotnet CLI
        // For .NET Framework projects, it's often part of the runtime identifier
        if (!string.IsNullOrEmpty(platform) && platform != "Any CPU")
        {
            arguments.Add($"-p:Platform=\"{platform}\"");
        }

        if (!restore)
        {
            arguments.Add("--no-restore");
        }

        var argumentString = string.Join(" ", arguments);
        _logger.LogDebug("Running: {DotNetPath} {Arguments}", _configuration.DotNetPath, argumentString);

        var psi = new ProcessStartInfo
        {
            FileName = _configuration.DotNetPath,
            Arguments = argumentString,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            WorkingDirectory = Path.GetDirectoryName(projectPath) ?? Environment.CurrentDirectory
        };

        var output = new StringBuilder();

        using var process = new Process { StartInfo = psi };
        
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        try
        {
            process.Start();
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to start dotnet CLI. Make sure .NET SDK is installed and '{_configuration.DotNetPath}' is in PATH.", ex);
        }

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        // Wait for completion with timeout and cancellation support
        var timeoutMs = _configuration.BuildTimeout;
        using var timeoutCts = new CancellationTokenSource(timeoutMs);
        using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
        
        try
        {
            await process.WaitForExitAsync(combinedCts.Token);
        }
        catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
        {
            _logger.LogWarning("Build timed out after {TimeoutMs}ms, killing process", timeoutMs);
            process.Kill();
            throw new TimeoutException($"Build timed out after {timeoutMs}ms");
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Build cancelled by user, killing process");
            process.Kill();
            throw;
        }

        return (process.ExitCode, output.ToString());
    }

    private void ParseBuildOutput(string output, List<BuildMessage> errors, List<BuildMessage> warnings)
    {
        if (string.IsNullOrEmpty(output))
            return;

        var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);

        // Regex patterns for dotnet build error and warning messages
        var errorPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
        var warningPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+warning\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
        var generalErrorPattern = new Regex(@"^(.+?):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);

        foreach (var line in lines)
        {
            var trimmedLine = line.Trim();
            
            // Try specific error pattern first (file with line/column)
            var errorMatch = errorPattern.Match(trimmedLine);
            if (errorMatch.Success)
            {
                errors.Add(new BuildMessage
                {
                    File = errorMatch.Groups[1].Value,
                    Line = int.TryParse(errorMatch.Groups[2].Value, out var errorLine) ? errorLine : 0,
                    Column = int.TryParse(errorMatch.Groups[3].Value, out var errorCol) ? errorCol : 0,
                    Code = errorMatch.Groups[4].Value,
                    Message = errorMatch.Groups[5].Value
                });
                continue;
            }

            // Try warning pattern
            var warningMatch = warningPattern.Match(trimmedLine);
            if (warningMatch.Success)
            {
                warnings.Add(new BuildMessage
                {
                    File = warningMatch.Groups[1].Value,
                    Line = int.TryParse(warningMatch.Groups[2].Value, out var warningLine) ? warningLine : 0,
                    Column = int.TryParse(warningMatch.Groups[3].Value, out var warningCol) ? warningCol : 0,
                    Code = warningMatch.Groups[4].Value,
                    Message = warningMatch.Groups[5].Value
                });
                continue;
            }

            // Try general error pattern (no line/column)
            var generalErrorMatch = generalErrorPattern.Match(trimmedLine);
            if (generalErrorMatch.Success)
            {
                errors.Add(new BuildMessage
                {
                    File = generalErrorMatch.Groups[1].Value,
                    Code = generalErrorMatch.Groups[2].Value,
                    Message = generalErrorMatch.Groups[3].Value
                });
            }
        }
    }

    private string TruncateOutput(string output, bool isFailed)
    {
        const int maxChars = 15000; // Conservative limit to stay under 25k tokens
        
        if (string.IsNullOrEmpty(output) || output.Length <= maxChars)
        {
            return output;
        }

        if (isFailed)
        {
            // For failed builds, prioritize the end of the output (where errors typically appear)
            var lines = output.Split('\n');
            var importantLines = new List<string>();
            var currentLength = 0;
            
            // Add summary line if present
            for (int i = 0; i < Math.Min(10, lines.Length); i++)
            {
                if (lines[i].Contains("Build FAILED") || lines[i].Contains("error") || lines[i].Contains("Error"))
                {
                    importantLines.Add(lines[i]);
                    currentLength += lines[i].Length + 1;
                    break;
                }
            }
            
            // Add errors from the end
            for (int i = lines.Length - 1; i >= 0 && currentLength < maxChars - 100; i--)
            {
                var line = lines[i];
                if (currentLength + line.Length + 1 > maxChars - 100) break;
                
                if (line.Contains("error") || line.Contains("Error") || 
                    line.Contains("warning") || line.Contains("Warning") ||
                    line.Contains("Build FAILED") || line.Contains("Time Elapsed"))
                {
                    importantLines.Insert(importantLines.Count == 0 ? 0 : 1, line);
                    currentLength += line.Length + 1;
                }
            }
            
            var result = string.Join("\n", importantLines);
            if (result.Length < maxChars - 200)
            {
                // Add some context from the end
                var remaining = maxChars - result.Length - 100;
                var endPortion = output.Substring(Math.Max(0, output.Length - remaining));
                result += "\n...\n" + endPortion;
            }
            
            return $"[Output truncated - showing errors and summary]\n{result}";
        }
        else
        {
            // For successful builds, show beginning and end
            var halfMax = maxChars / 2 - 50;
            var start = output.Substring(0, Math.Min(halfMax, output.Length));
            var end = output.Length > halfMax ? output.Substring(output.Length - halfMax) : "";
            
            return start + "\n\n[... middle portion truncated ...]\n\n" + end;
        }
    }
}
```

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

```csharp
using System.Text;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// VSTest.Console.exe-based test executor
/// </summary>
public class VSTestExecutor : BaseTestExecutor
{
    public VSTestExecutor(
        ILogger<VSTestExecutor> logger,
        IOptions<McpServerConfiguration> configuration)
        : base(logger, configuration)
    {
    }

    public override async Task<TestResult> ExecuteTestsAsync(
        string projectPath,
        string? filter,
        bool verbose,
        CancellationToken cancellationToken = default)
    {
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();

        try
        {
            // Validate project path
            if (!File.Exists(projectPath))
            {
                throw new FileNotFoundException($"Test project file not found: {projectPath}");
            }

            var vstestPath = FindVSTestConsoleExecutable();
            if (string.IsNullOrEmpty(vstestPath))
            {
                throw new InvalidOperationException("VSTest.Console.exe not found. Please install Visual Studio or Build Tools.");
            }

            // Find test assembly
            var testAssembly = FindTestAssembly(projectPath);
            if (string.IsNullOrEmpty(testAssembly))
            {
                return new TestResult
                {
                    TotalTests = 0,
                    PassedTests = 0,
                    FailedTests = 0,
                    SkippedTests = 0,
                    Duration = 0,
                    TestDetails = new List<TestDetail>
                    {
                        new TestDetail
                        {
                            Name = "Assembly Not Found",
                            ClassName = "VSTest",
                            Result = "Failed",
                            Duration = 0,
                            ErrorMessage = $"No test assembly found for project {Path.GetFileNameWithoutExtension(projectPath)}",
                            StackTrace = null
                        }
                    },
                    Output = $"No test assemblies found for project {projectPath} in bin directories"
                };
            }

            _logger.LogInformation("Running tests from assembly: {TestAssembly}", testAssembly);

            var args = new StringBuilder($"\"{testAssembly}\"");

            if (!string.IsNullOrEmpty(filter))
            {
                args.Append($" /TestCaseFilter:\"{filter}\"");
            }

            // Always use detailed console output to capture error messages
            args.Append(" /logger:console;verbosity=detailed");

            // Try to find test adapters for better framework support
            var testAdapterPath = FindTestAdapterPath(projectPath);
            if (!string.IsNullOrEmpty(testAdapterPath))
            {
                args.Append($" /TestAdapterPath:\"{testAdapterPath}\"");
                _logger.LogDebug("Using test adapter path: {TestAdapterPath}", testAdapterPath);
            }

            // Add TRX logger for structured output
            var trxFileName = $"TestResults_{Guid.NewGuid():N}.trx";
            var trxFilePath = Path.Combine(Path.GetTempPath(), trxFileName);
            args.Append($" /logger:trx;LogFileName=\"{trxFilePath}\"");

            var result = await RunProcessAsync(vstestPath, args.ToString(), cancellationToken);
            
            // Parse results from TRX file
            var testResult = await ParseTrxFileAsync(trxFilePath, result.Output);
            
            // Clean up TRX file
            try
            {
                if (File.Exists(trxFilePath))
                    File.Delete(trxFilePath);
            }
            catch (Exception ex)
            {
                _logger.LogDebug("Failed to delete TRX file: {Error}", ex.Message);
            }

            stopwatch.Stop();
            testResult.Duration = stopwatch.Elapsed.TotalSeconds;
            
            return testResult;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error running VSTest for project: {ProjectPath}", projectPath);
            stopwatch.Stop();

            return new TestResult
            {
                TotalTests = 0,
                PassedTests = 0,
                FailedTests = 0,
                SkippedTests = 0,
                Duration = stopwatch.Elapsed.TotalSeconds,
                TestDetails = new List<TestDetail>
                {
                    new TestDetail
                    {
                        Name = "Test Execution Error",
                        ClassName = "VSTest",
                        Result = "Failed",
                        Duration = 0,
                        ErrorMessage = ex.Message,
                        StackTrace = ex.StackTrace
                    }
                },
                Output = $"VSTest execution failed: {ex.Message}\n{ex.StackTrace}"
            };
        }
    }

    private string? FindVSTestConsoleExecutable()
    {
        // Check environment variable first
        var envPath = Environment.GetEnvironmentVariable("VSTEST_CONSOLE_PATH");
        if (!string.IsNullOrEmpty(envPath) && File.Exists(envPath))
        {
            return envPath;
        }

        // Get preferred VS version from configuration
        var preferredVersion = _configuration.PreferredVSVersion?.ToLower() ?? "2022";
        
        var possiblePaths = new List<string>();

        // Add paths based on preferred version first
        if (preferredVersion == "2022" || preferredVersion == "auto")
        {
            possiblePaths.AddRange(new[]
            {
                @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
            });
        }

        if (preferredVersion == "2019" || preferredVersion == "auto")
        {
            possiblePaths.AddRange(new[]
            {
                @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                @"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
            });
        }

        var foundPath = possiblePaths.FirstOrDefault(File.Exists);
        if (foundPath != null)
        {
            var version = foundPath.Contains("2022") ? "2022" : foundPath.Contains("2019") ? "2019" : "Unknown";
            _logger.LogInformation("Found VSTest.Console.exe version {Version} at: {Path}", version, foundPath);
        }
        else
        {
            _logger.LogWarning("VSTest.Console.exe not found in standard locations");
        }

        return foundPath;
    }

    private string? FindTestAssembly(string projectPath)
    {
        var projectDir = Path.GetDirectoryName(projectPath);
        var projectName = Path.GetFileNameWithoutExtension(projectPath);
        
        // Look for test assemblies in bin directories
        var possiblePaths = new[]
        {
            Path.Combine(projectDir!, "bin", "Debug", $"{projectName}.dll"),
            Path.Combine(projectDir!, "bin", "Release", $"{projectName}.dll"),
            Path.Combine(projectDir!, "bin", "Debug", "net48", $"{projectName}.dll"),
            Path.Combine(projectDir!, "bin", "Release", "net48", $"{projectName}.dll"),
            Path.Combine(projectDir!, "bin", "Debug", "net472", $"{projectName}.dll"),
            Path.Combine(projectDir!, "bin", "Release", "net472", $"{projectName}.dll")
        };

        var testAssembly = possiblePaths.FirstOrDefault(File.Exists);
        
        if (string.IsNullOrEmpty(testAssembly))
        {
            // Fallback: search recursively
            var assemblyFiles = Directory.GetFiles(Path.Combine(projectDir!, "bin"), "*.dll", SearchOption.AllDirectories)
                .Where(f => Path.GetFileName(f).Equals($"{projectName}.dll", StringComparison.OrdinalIgnoreCase))
                .ToList();
            
            testAssembly = assemblyFiles.FirstOrDefault();
        }

        return testAssembly;
    }

    private string? FindTestAdapterPath(string projectPath)
    {
        try
        {
            var projectDir = Path.GetDirectoryName(projectPath);
            if (string.IsNullOrEmpty(projectDir))
                return null;

            // Look for test adapters in packages folder (packages.config style)
            var currentDir = new DirectoryInfo(projectDir);
            while (currentDir != null)
            {
                var packagesDir = Path.Combine(currentDir.FullName, "packages");
                if (Directory.Exists(packagesDir))
                {
                    _logger.LogDebug("Searching for test adapters in packages directory: {PackagesDir}", packagesDir);

                    // Look for NUnit test adapter (prioritize NUnit3TestAdapter)
                    var nunitAdapterPatterns = new[] { "NUnit3TestAdapter.*", "NUnitTestAdapter.*" };
                    foreach (var pattern in nunitAdapterPatterns)
                    {
                        var nunitAdapterDirs = Directory.GetDirectories(packagesDir, pattern, SearchOption.TopDirectoryOnly);
                        foreach (var adapterDir in nunitAdapterDirs)
                        {
                            var possiblePaths = new[]
                            {
                                Path.Combine(adapterDir, "build"),
                                Path.Combine(adapterDir, "build", "net35"),
                                Path.Combine(adapterDir, "build", "net40"),
                                adapterDir
                            };

                            foreach (var path in possiblePaths)
                            {
                                if (Directory.Exists(path))
                                {
                                    var adapterDlls = Directory.GetFiles(path, "*TestAdapter*.dll", SearchOption.AllDirectories);
                                    if (adapterDlls.Length > 0)
                                    {
                                        _logger.LogInformation("Found NUnit test adapter at: {Path}", path);
                                        return path;
                                    }
                                }
                            }
                        }
                    }

                    // Look for xUnit test adapter as fallback
                    var xunitAdapterDirs = Directory.GetDirectories(packagesDir, "xunit.runner.visualstudio.*", SearchOption.TopDirectoryOnly);
                    foreach (var adapterDir in xunitAdapterDirs)
                    {
                        var buildPath = Path.Combine(adapterDir, "build");
                        if (Directory.Exists(buildPath))
                        {
                            _logger.LogInformation("Found xUnit test adapter at: {Path}", buildPath);
                            return buildPath;
                        }
                    }

                    break; // Only check the first packages directory found
                }

                currentDir = currentDir.Parent;
            }

            return null;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error searching for test adapter path for project: {ProjectPath}", projectPath);
            return null;
        }
    }
}
```

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

```csharp
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using DotNetFrameworkMCP.Server.Configuration;
using DotNetFrameworkMCP.Server.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DotNetFrameworkMCP.Server.Executors;

/// <summary>
/// MSBuild-based build executor
/// </summary>
public class MSBuildExecutor : IBuildExecutor
{
    private readonly ILogger<MSBuildExecutor> _logger;
    private readonly McpServerConfiguration _configuration;

    public MSBuildExecutor(
        ILogger<MSBuildExecutor> logger,
        IOptions<McpServerConfiguration> configuration)
    {
        _logger = logger;
        _configuration = configuration.Value;
    }

    public async Task<BuildResult> ExecuteBuildAsync(
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken = default)
    {
        var stopwatch = Stopwatch.StartNew();
        var errors = new List<BuildMessage>();
        var warnings = new List<BuildMessage>();

        try
        {
            // Validate project path
            if (!File.Exists(projectPath))
            {
                throw new FileNotFoundException($"Project file not found: {projectPath}");
            }

            // Find MSBuild.exe
            var msbuildPath = FindMSBuildExecutable();
            if (string.IsNullOrEmpty(msbuildPath))
            {
                _logger.LogError("Could not find MSBuild.exe in any standard locations");
                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.");
            }

            _logger.LogInformation("Using MSBuild.exe: {MSBuildPath}", msbuildPath);

            // Build the project using MSBuild.exe process
            var result = await RunMSBuildAsync(msbuildPath, projectPath, configuration, platform, restore, cancellationToken);
            
            // Parse the output for errors and warnings
            ParseBuildOutput(result.Output, errors, warnings);

            stopwatch.Stop();

            return new BuildResult
            {
                Success = result.ExitCode == 0,
                Errors = errors,
                Warnings = warnings,
                BuildTime = stopwatch.Elapsed.TotalSeconds,
                Output = TruncateOutput(result.Output, result.ExitCode != 0)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Build failed for project: {ProjectPath}", projectPath);
            stopwatch.Stop();

            return new BuildResult
            {
                Success = false,
                Errors = new List<BuildMessage>
                {
                    new BuildMessage
                    {
                        Message = ex.Message,
                        File = projectPath
                    }
                },
                Warnings = warnings,
                BuildTime = stopwatch.Elapsed.TotalSeconds,
                Output = TruncateOutput($"Build failed with exception: {ex.Message}\n{ex.StackTrace}", true)
            };
        }
    }

    private string? FindMSBuildExecutable()
    {
        // Check environment variable first
        var envPath = Environment.GetEnvironmentVariable("MSBUILD_EXE_PATH");
        if (!string.IsNullOrEmpty(envPath) && File.Exists(envPath))
        {
            return envPath;
        }

        // Get preferred VS version from configuration
        var preferredVersion = _configuration.PreferredVSVersion?.ToLower() ?? "2022";
        
        // Look for MSBuild.exe in standard Visual Studio locations
        var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
        var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);

        var possiblePaths = new List<string>();

        // Add paths based on preferred version first
        if (preferredVersion == "2022" || preferredVersion == "auto")
        {
            possiblePaths.AddRange(new[]
            {
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
            });
        }

        if (preferredVersion == "2019" || preferredVersion == "auto")
        {
            possiblePaths.AddRange(new[]
            {
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
            });
        }

        // If not auto mode and preferred version is not 2022 or 2019, add all versions as fallback
        if (preferredVersion != "auto" && preferredVersion != "2022" && preferredVersion != "2019")
        {
            _logger.LogWarning("Unknown PreferredVSVersion '{Version}', falling back to auto detection", preferredVersion);
            possiblePaths.AddRange(new[]
            {
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFiles, @"Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"),
                Path.Combine(programFilesX86, @"Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe")
            });
        }

        // Add legacy paths as final fallback
        possiblePaths.AddRange(new[]
        {
            Path.Combine(programFilesX86, @"MSBuild\14.0\Bin\MSBuild.exe"),
            Path.Combine(programFilesX86, @"MSBuild\15.0\Bin\MSBuild.exe"),
            @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe",
            @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
        });

        var foundPath = possiblePaths.FirstOrDefault(File.Exists);
        if (foundPath != null)
        {
            var version = foundPath.Contains("2022") ? "2022" : foundPath.Contains("2019") ? "2019" : "Legacy";
            _logger.LogInformation("Found MSBuild.exe version {Version} at: {Path}", version, foundPath);
        }

        return foundPath;
    }

    private async Task<(int ExitCode, string Output)> RunMSBuildAsync(
        string msbuildPath,
        string projectPath,
        string configuration,
        string platform,
        bool restore,
        CancellationToken cancellationToken)
    {
        var arguments = new List<string>
        {
            $"\"{projectPath}\"",
            $"/p:Configuration={configuration}",
            $"/p:Platform=\"{platform}\"",
            "/v:normal", // Normal verbosity
            "/nologo"
        };

        if (restore)
        {
            arguments.Add("/restore");
        }

        var argumentString = string.Join(" ", arguments);
        _logger.LogDebug("Running: {MSBuildPath} {Arguments}", msbuildPath, argumentString);

        var psi = new ProcessStartInfo
        {
            FileName = msbuildPath,
            Arguments = argumentString,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            WorkingDirectory = Path.GetDirectoryName(projectPath) ?? Environment.CurrentDirectory
        };

        var output = new StringBuilder();

        using var process = new Process { StartInfo = psi };
        
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        // Wait for completion with timeout and cancellation support
        var timeoutMs = _configuration.BuildTimeout;
        using var timeoutCts = new CancellationTokenSource(timeoutMs);
        using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
        
        try
        {
            await process.WaitForExitAsync(combinedCts.Token);
        }
        catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
        {
            _logger.LogWarning("Build timed out after {TimeoutMs}ms, killing process", timeoutMs);
            process.Kill();
            throw new TimeoutException($"Build timed out after {timeoutMs}ms");
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Build cancelled by user, killing process");
            process.Kill();
            throw;
        }

        return (process.ExitCode, output.ToString());
    }

    private void ParseBuildOutput(string output, List<BuildMessage> errors, List<BuildMessage> warnings)
    {
        if (string.IsNullOrEmpty(output))
            return;

        var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);

        // Regex patterns for MSBuild error and warning messages
        var errorPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
        var warningPattern = new Regex(@"^(.+?)\((\d+),(\d+)\):\s+warning\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);
        var generalErrorPattern = new Regex(@"^(.+?):\s+error\s+([A-Z]+\d+):\s+(.+)$", RegexOptions.Multiline);

        foreach (var line in lines)
        {
            var trimmedLine = line.Trim();
            
            // Try specific error pattern first (file with line/column)
            var errorMatch = errorPattern.Match(trimmedLine);
            if (errorMatch.Success)
            {
                errors.Add(new BuildMessage
                {
                    File = errorMatch.Groups[1].Value,
                    Line = int.TryParse(errorMatch.Groups[2].Value, out var errorLine) ? errorLine : 0,
                    Column = int.TryParse(errorMatch.Groups[3].Value, out var errorCol) ? errorCol : 0,
                    Code = errorMatch.Groups[4].Value,
                    Message = errorMatch.Groups[5].Value
                });
                continue;
            }

            // Try warning pattern
            var warningMatch = warningPattern.Match(trimmedLine);
            if (warningMatch.Success)
            {
                warnings.Add(new BuildMessage
                {
                    File = warningMatch.Groups[1].Value,
                    Line = int.TryParse(warningMatch.Groups[2].Value, out var warningLine) ? warningLine : 0,
                    Column = int.TryParse(warningMatch.Groups[3].Value, out var warningCol) ? warningCol : 0,
                    Code = warningMatch.Groups[4].Value,
                    Message = warningMatch.Groups[5].Value
                });
                continue;
            }

            // Try general error pattern (no line/column)
            var generalErrorMatch = generalErrorPattern.Match(trimmedLine);
            if (generalErrorMatch.Success)
            {
                errors.Add(new BuildMessage
                {
                    File = generalErrorMatch.Groups[1].Value,
                    Code = generalErrorMatch.Groups[2].Value,
                    Message = generalErrorMatch.Groups[3].Value
                });
            }
        }
    }

    private string TruncateOutput(string output, bool isFailed)
    {
        const int maxChars = 15000; // Conservative limit to stay under 25k tokens
        
        if (string.IsNullOrEmpty(output) || output.Length <= maxChars)
        {
            return output;
        }

        if (isFailed)
        {
            // For failed builds, prioritize the end of the output (where errors typically appear)
            var lines = output.Split('\n');
            var importantLines = new List<string>();
            var currentLength = 0;
            
            // Add summary line if present
            for (int i = 0; i < Math.Min(10, lines.Length); i++)
            {
                if (lines[i].Contains("Build FAILED") || lines[i].Contains("error") || lines[i].Contains("Error"))
                {
                    importantLines.Add(lines[i]);
                    currentLength += lines[i].Length + 1;
                    break;
                }
            }
            
            // Add errors from the end
            for (int i = lines.Length - 1; i >= 0 && currentLength < maxChars - 100; i--)
            {
                var line = lines[i];
                if (currentLength + line.Length + 1 > maxChars - 100) break;
                
                if (line.Contains("error") || line.Contains("Error") || 
                    line.Contains("warning") || line.Contains("Warning") ||
                    line.Contains("Build FAILED") || line.Contains("Time Elapsed"))
                {
                    importantLines.Insert(importantLines.Count == 0 ? 0 : 1, line);
                    currentLength += line.Length + 1;
                }
            }
            
            var result = string.Join("\n", importantLines);
            if (result.Length < maxChars - 200)
            {
                // Add some context from the end
                var remaining = maxChars - result.Length - 100;
                var endPortion = output.Substring(Math.Max(0, output.Length - remaining));
                result += "\n...\n" + endPortion;
            }
            
            return $"[Output truncated - showing errors and summary]\n{result}";
        }
        else
        {
            // For successful builds, show beginning and end
            var halfMax = maxChars / 2 - 50;
            var start = output.Substring(0, Math.Min(halfMax, output.Length));
            var end = output.Length > halfMax ? output.Substring(output.Length - halfMax) : "";
            
            return start + "\n\n[... middle portion truncated ...]\n\n" + end;
        }
    }
}
```