[← Usage index](Usage.md) # Building windeskctl ## Prerequisites | Need | Why | |---|---| | Windows 10 1903+ | `Windows.Graphics.Capture` — to run what you built | | .NET 10 SDK, 10.0.302 or later | `global.json` pins the feature band and rolls forward within it | Check the SDK: ```powershell dotnet --version # 10.0.3xx ``` If this reports a lower version or errors with a `global.json` message, install the .NET 10 SDK. `rollForward: latestFeature` accepts any 10.0.3xx and above; it will not silently fall back to an older band. Nothing else is needed. A clone plus the SDK builds and tests the whole solution. ## Build and test ```powershell dotnet build dotnet test ``` `TreatWarningsAsErrors` is on for every project, so a warning fails the build — including the trim/AOT analyzer warnings that `IsAotCompatible` turns on everywhere. That is deliberate: it surfaces an AOT incompatibility here, at `build` time, rather than leaving it for whoever produces the release binary. Tests are xunit.v3 on Microsoft Testing Platform. The test binary is its own runner — there is no VSTest adapter — so the filter syntax differs from what you may be used to: ```powershell dotnet test tests/WinDeskCtl.Core.Tests --filter-method '*Translate*' dotnet test tests/WinDeskCtl.Core.Tests --filter-class '*ScreenCoordsTests' ``` Only `WinDeskCtl.Core` has tests. `WinDeskCtl.Platform` is untested by design — it is P/Invoke and WinRT against a live desktop, and `windeskctl doctor` is its runtime check. ## Run what you built ```powershell dotnet run --project src/WinDeskCtl -- doctor dotnet run --project src/WinDeskCtl -- windows list ``` `doctor` is the fastest confirmation that a build works against your machine: it measures display topology, DPI, and drag thresholds rather than assuming them. ## Adding a package Versions are managed centrally. Add the version to `Directory.Packages.props`: ```xml ``` and reference it from the project **without** a `Version` attribute: ```xml ``` `NuGet.config` clears inherited sources and declares nuget.org alone, so restore does not depend on whatever private feeds a given machine has configured. ## When the build fails **`error NETSDK1045` / a `global.json` SDK error.** The installed SDK is older than 10.0.302. Install the .NET 10 SDK; do not lower the pin. **An `IL2xxx`/`IL3xxx` warning fails the build.** A trim/AOT incompatibility, reported as an error because warnings are errors. Fix the incompatibility rather than suppressing the warning — a suppression here becomes a runtime failure in the AOT-published binary, where JIT no longer covers for it. **A green build is not proof the MCP server starts.** Every type crossing JSON must be listed in `Core/Json/WinDeskCtlJsonContext.cs`, in the same commit that introduces it. MCP builds its tool schemas at startup, so an unlisted type takes the server down entirely — and only in the published binary, since JIT hides it under `dotnet run`. The compiler will not catch this for you. ## Verifying the published server `dotnet publish` needs the Visual Studio C++ toolchain, which NativeAOT links with. The AOT targets find it via `vswhere.exe`, which the installer does not put on `PATH`: ```powershell $env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\Installer;$env:PATH" dotnet publish src/WinDeskCtl/WinDeskCtl.csproj -c Release -o publish ``` Then drive the published binary the way a client does. The point is `tools/list`: it is answered from the schemas built at startup, so a missing JSON registration fails here and nowhere earlier. **Keep stdin open until the replies have been read** — piping a file in closes it immediately, and the server shuts down before it writes anything back. ```powershell $psi = [Diagnostics.ProcessStartInfo]::new() $psi.FileName = ".\publish\windeskctl.exe" $psi.ArgumentList.Add('mcp') $psi.RedirectStandardInput = $true $psi.RedirectStandardOutput = $true $psi.UseShellExecute = $false $p = [Diagnostics.Process]::Start($psi) $p.StandardInput.WriteLine('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"1"}}}') $p.StandardInput.WriteLine('{"jsonrpc":"2.0","method":"notifications/initialized"}') $p.StandardInput.WriteLine('{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}') $p.StandardInput.Flush() $p.StandardOutput.ReadLine() $p.StandardOutput.ReadLine() $p.StandardInput.Close() ``` Two JSON-RPC replies naming the `howto` and `execute` tools means the server is sound. Anything else — no output, or an exception on stderr — is a startup failure, and a type missing from `WinDeskCtlJsonContext` is the first thing to check. The server's own logs go to stderr and will interleave with the replies in a terminal. That is correct behaviour, not noise to fix: stdout carries the protocol and nothing else.