WpfMcp 0.1.0
dotnet add package WpfMcp --version 0.1.0
NuGet\Install-Package WpfMcp -Version 0.1.0
<PackageReference Include="WpfMcp" Version="0.1.0" />
<PackageVersion Include="WpfMcp" Version="0.1.0" />
<PackageReference Include="WpfMcp" />
paket add WpfMcp --version 0.1.0
#r "nuget: WpfMcp, 0.1.0"
#:package WpfMcp@0.1.0
#addin nuget:?package=WpfMcp&version=0.1.0
#tool nuget:?package=WpfMcp&version=0.1.0
WpfMcp
Turn a WPF application into an MCP server. Mark a class and its methods with two attributes, and a source generator exposes them as Model Context Protocol tools over HTTP — including tools bound to a live window, so a model can read and change what is on screen.
Install
dotnet add package WpfMcp
The package contains the runtime library, the attributes, and the source generator. One reference is all you need.
Targets net6.0-windows, so .NET 6, 7, 8 and 9 WPF applications can all consume it. Building
against it needs .NET SDK 6.0.400 / Visual Studio 2022 17.3 or newer, which is the floor for
loading the analyzer.
Write a tool
A static collection, for anything that doesn't touch the UI:
using System.ComponentModel;
using WpfMcp.Core;
[McpToolCollection]
public static class MathTools
{
[McpTool("create_sum")]
[Description("Adds two numbers together")]
public static int Sum(
[Description("First value")] int a,
[Description("Second value")] int b) => a + b;
}
[Description] on the method and each parameter becomes the tool description and JSON Schema
documentation an MCP client sees.
Start the server
public partial class App : Application
{
private McpServer? _server;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_server = new McpServer("http://127.0.0.1:9000/mcp");
_server.Start();
}
protected override void OnExit(ExitEventArgs e) => _server?.Stop();
}
Nothing registers tools by hand — static collections register themselves at module load, and window-bound collections register as the window initialises.
Tools bound to a window
Make the tools instance methods on a partial class and the generator registers the live
instance, so the tool can touch real UI state. Tools run on a thread-pool thread, so marshal to
the dispatcher:
[McpToolCollection]
public partial class MainWindow : Window
{
[McpTool("set_status")]
[Description("Sets the status message shown in the application window")]
public string SetStatus([Description("Text to display")] string message)
{
Dispatcher.Invoke(() => StatusText.Text = message);
return $"Status is now: {message}";
}
}
Reading and writing a control
Because the tool runs against the live window, a model can edit a control's contents and read back what the user typed into it — both write to the same place:
[McpTool("set_editor_text")]
[Description("Replaces all text in the editor shown in the application window")]
public int SetEditorText([Description("The new editor contents")] string text)
{
return Dispatcher.Invoke(() =>
{
Editor.Text = text ?? string.Empty;
return Editor.Text.Length;
});
}
[McpTool("read_editor_text")]
[Description("Reads the editor contents, including anything the user typed by hand")]
public string ReadEditorText() => Dispatcher.Invoke(() => Editor.Text);
Anything on the window works the same way — this changes the actual title bar:
[McpTool("set_window_title")]
[Description("Changes the text shown in the application's title bar")]
public string SetWindowTitle([Description("The new window title")] string title)
{
Dispatcher.Invoke(() => Title = title);
return $"Window title is now: {title}";
}
The sample app implements all of these — run it and drive them from any MCP client to watch the window change.
Async, progress and cancellation
Declare a CancellationToken or an IMcpProgress parameter and the framework supplies it — these
never appear in the tool's JSON schema:
[McpTool("count_slowly")]
[Description("Counts up to a number, reporting progress along the way")]
public static async Task<int> CountSlowly(
[Description("How high to count")] int steps,
IMcpProgress progress,
CancellationToken cancellationToken)
{
for (int i = 1; i <= steps; i++)
{
await Task.Delay(300, cancellationToken);
await progress.ReportAsync(i, steps, $"Step {i} of {steps}");
}
return steps;
}
When a client sends params._meta.progressToken, the response streams as text/event-stream
carrying notifications/progress followed by the result. A client's notifications/cancelled
cancels the token for real.
Observing activity
McpServer raises ToolInvocationStarted, ToolProgressReported and ToolInvocationCompleted
(correlated by a Guid) so a host application can display live MCP activity without any logging
code inside the tools. Handlers run on thread-pool threads.
Protocol support
HTTP only — the stdio transport is deliberately not implemented.
Implements the Streamable HTTP transport of MCP 2025-06-18: a single endpoint serving POST
for JSON-RPC and GET for a server-to-client SSE stream, initialize version negotiation,
ping, tools/list (paginated), tools/call, notification handling, per-request cancellation,
and notifications/tools/list_changed when the tool set changes. Origin is validated on every
request to prevent DNS-rebinding attacks against the loopback server.
tools/list pages at McpServer.ToolPageSize (50 by default), returning nextCursor while more
remain. Cursors are opaque and encode a position by tool name rather than by index, so paging stays
correct even as the tool set changes between requests — which it does, when windows open and close.
Not implemented: sessions (Mcp-Session-Id), OAuth authorization, and
structuredContent / outputSchema.
Diagnostics
| ID | Severity | Meaning |
|---|---|---|
| MCP001 | Error | [McpToolCollection] type with instance tools isn't partial |
| MCP002 | Error | Parameter or return type isn't a supported primitive |
| MCP003 | Error | Two tools in one type share a name |
| MCP004 | Warning | [McpTool] method in a type without [McpToolCollection] |
| MCP005 | Warning | No automatic registration hook; call RegisterMcpTools() |
Changelog
See CHANGELOG.md.
Licence
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0-windows7.0 is compatible. net7.0-windows was computed. net8.0-windows was computed. net9.0-windows was computed. net10.0-windows was computed. |
-
net6.0-windows7.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 114 | 8/8/2026 |