[← 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.