WpfMcp 0.1.0

dotnet add package WpfMcp --version 0.1.0
                    
NuGet\Install-Package WpfMcp -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WpfMcp" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WpfMcp" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="WpfMcp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WpfMcp --version 0.1.0
                    
#r "nuget: WpfMcp, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WpfMcp@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WpfMcp&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=WpfMcp&version=0.1.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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