Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,14 @@

## 6.1.0 (preview)

- ### Bug fixes

- [#477](../../issues/477) - Binding Errors - Get error message
- [#483](../../issues/483) - Error when I try to GetBindingExpression from TextProperty
- [#484](../../issues/484) - Snoop fails to attach to application running on .NET Framework on ARM64

- ### Improvements
- ### New Features

- [#457](../../issues/457) - [Quality of Life] Synchronize Zoomer instance background slider value.
- [#482](../../issues/482) - Add "Auto-track on click" option (thanks @Koichi-Kobayashi)
- [#485](../../issues/485) - Snoop running on ARM hardware is using x64 emulation instead of the native Arm64
- **MCP (Model Context Protocol) Server Integration** - AI assistant integration for automated WPF inspection
- Added MCP server with 7 tools: get_visual_tree, get_selected_element, get_element_properties, select_element, find_elements, get_bindings, get_element_preview
- Integrated official MCP SDK for .NET 8+ builds with fallback implementation for .NET 6/Framework
- **MCP (Model Context Protocol) Server Integration** - Added AI assistant integration for automated WPF inspection and debugging
- Enables AI assistants like Claude to interact with and inspect WPF applications in real-time
- 7 MCP tools: get_visual_tree, get_selected_element, get_element_properties, select_element, find_elements, get_bindings, get_element_preview
- Official MCP SDK support for .NET 8+ with fallback implementation for .NET 6/Framework
- Server-Sent Events (SSE) transport on localhost:47700-47799
- Dedicated MCP Server window for server control and configuration
- Visual element screenshot capability using RenderTargetBitmap
- Enables AI assistants like Claude to debug, inspect, and analyze WPF applications in real-time
- Dedicated MCP Server window for easy configuration and control

## 6.0.0

Expand Down
1 change: 1 addition & 0 deletions Directory.packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ItemGroup Label="MCP">
<PackageVersion Include="ModelContextProtocol" Version="0.4.1-preview.1" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.4.1-preview.1" />
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>

<ItemGroup Label="PowerShell">
Expand Down
90 changes: 2 additions & 88 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,95 +69,9 @@ The MCP server exposes 7 tools for AI assistants:

The MCP server runs on `localhost:47700-47799` using Server-Sent Events (SSE) transport.

## Versions
## Changelog

You can read the [changelog](Changelog.md) for details on what changed in which version.

### [6.0.0](../../releases/tag/v6.0.0)

Breaking:

- Dropped support for all .NET Framework versions prior to .NET 4.6.2
- Dropped support for .NET 3.1 and NET 5

### [5.0.0](../../releases/tag/v5.0.0)

Highlights:

- Improved settings system that does not rely on `System.Configuration`
The new system allows sharing of settings between different snooped applications.
It also allows to define settings for whole directory trees.
- It's now possible to hide properties from Snoop in it's default view.
Just annotate your properties with `[System.ComponentModel.BrowsableAttribute(false)]`.
- Added the ability to show browser dev tools on browser controls.
`WebView2` and `CefSharp` are currently supported.
- Added dark theme

Breaking:

- Dropped support for all .NET Framework versions prior to .NET 4.5.2
- Dropped support for .NET 3.0
- Added support for .NET versions >= 6.0 (by not explicitly blocking versions greater than 6.0)

### [4.0.0](../../releases/tag/v4.0.0)

Highlights:

- Support for .NET 6.0
- Support for ARM/ARM64
- New "Diagnostics" view
- Settings for highlighting
- Artifacts are digitally signed thanks to SignPath.io (MSI, Chocolatey NUPKG and zip)

Breaking:

- Dropped support for all .NET versions prior to .NET 4.5.1

### [3.0.0](../../releases/tag/v3.0.0)

Highlights:

- Support for .NET Core (3.0, 3.1 and 5.0) (including self contained and single file applications)
- Rewritten injector code
- You no longer have to have installed any Microsoft Visual C++ Redistributable(s)
- Snooping disabled controls when holding `CTRL + SHIFT` works now
- Snoop now filters uncommon properties by default
- Snoop is now able to show `MergedDictionaries` from `ResourceDictionary`
- Snoop now has two tracking modes.
- Holding `CTRL` tries to skip template parts => this is changed to `CTRL + ALT` in newer versions
- Holding `CTRL + SHIFT` does not skip template parts
- Drastically improved performance of `AppChooser.Refresh()` (thanks @mikel785)
- Usability improvements for process dropdown (thanks @mikel785)
- Support for displaying the logical tree and the tree of WPF automation peers
- Ability to inspect `Popup` without opening it
- `Snoop.exe` and the injector launcher now support commandline args
- Global hotkey support (just start snoop, focus a WPF application and hit `CTRL + WIN + ALT + F12`)

Known issues:

- Trying to snoop a trimmed single file application might not work as trimming might have removed things Snoop relies on

### [2.11.0](../../releases/tag/2.11.0)

Highlights:

- Support for multiple app domains
- Auto elevation to enable spying of elevated processes without running Snoop as administrator
- Persistent settings for various settings
- Improved error dialog and issue reporting
- Rewritten window finder

### [2.10.0](../../releases/tag/2.10.0)

Was released on September 19th, 2018.
In this version we finally got rid of support for snooping WPF 3.5 applications.
This allowed us to move the Snoop projects forward to Visual Studio 2017 which should make it much easier to work with Snoop's source code.

### [2.9.0](../../releases/tag/2.9.0)

Was released on July 27th, 2018.
The big addition in this version was the inclusion of the triggers tab which was a useful feature of another WPF spying utility called WPF Inspector (written by [Christan Moser](https://github.com/ChristianMoser)).
It was ported to Snoop by Bastian Schmidt.
You can read the [changelog](Changelog.md) for details on what changed in each version.

## Documentation on how to use Snoop

Expand Down
46 changes: 23 additions & 23 deletions Snoop.Core/Infrastructure/MCP/McpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ namespace Snoop.Infrastructure.MCP;
#else
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -146,9 +146,6 @@ public async Task<bool> StartAsync(int preferredPort = 0)
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

// Configure the server URL
builder.WebHost.UseUrls($"http://localhost:{portToUse}");

// Add MCP server with the Snoop tools
builder.Services.AddSingleton(snoopContext);
builder.Services
Expand All @@ -158,6 +155,9 @@ public async Task<bool> StartAsync(int preferredPort = 0)

this.webApplication = builder.Build();

// Configure the server URL
this.webApplication.Urls.Add($"http://localhost:{portToUse}");

// Map the MCP endpoints (/sse and /messages)
this.webApplication.MapMcp();

Expand All @@ -166,7 +166,7 @@ public async Task<bool> StartAsync(int preferredPort = 0)
{
try
{
await this.webApplication.RunAsync(this.cancellationTokenSource.Token);
await this.webApplication.RunAsync();
}
catch (OperationCanceledException)
{
Expand All @@ -176,7 +176,7 @@ public async Task<bool> StartAsync(int preferredPort = 0)
{
System.Diagnostics.Debug.WriteLine($"MCP Server error: {ex.Message}");
}
});
}, this.cancellationTokenSource!.Token);

// Give the server a moment to start
await Task.Delay(100);
Expand Down Expand Up @@ -248,15 +248,15 @@ public void Dispose()

#else
// Fallback implementation for .NET Framework and .NET 6
public async Task<bool> StartAsync(int preferredPort = 0)
public Task<bool> StartAsync(int preferredPort = 0)
{
if (this.IsRunning)
{
return true;
return Task.FromResult(true);
}

this.cancellationTokenSource = new CancellationTokenSource();
this.sessionId = Guid.NewGuid().ToString("N")[..8];
this.sessionId = Guid.NewGuid().ToString("N").Substring(0, 8);

// Try to find an available port
var portsToTry = preferredPort > 0
Expand All @@ -276,7 +276,7 @@ public async Task<bool> StartAsync(int preferredPort = 0)
// Start listening for requests
_ = Task.Run(() => this.ListenAsync(this.cancellationTokenSource.Token));

return true;
return Task.FromResult(true);
}
catch (HttpListenerException)
{
Expand All @@ -285,7 +285,7 @@ public async Task<bool> StartAsync(int preferredPort = 0)
}
}

return false;
return Task.FromResult(false);
}

public void Stop()
Expand Down Expand Up @@ -390,7 +390,7 @@ private async Task HandleSseConnectionAsync(HttpListenerContext context, Cancell
response.Headers.Add("Cache-Control", "no-cache");
response.Headers.Add("Connection", "keep-alive");

using var writer = new StreamWriter(response.OutputStream, Encoding.UTF8, leaveOpen: true);
using var writer = new StreamWriter(response.OutputStream, Encoding.UTF8, 1024, leaveOpen: true);

// Send the endpoint event with the messages URL
var endpointEvent = new
Expand Down Expand Up @@ -505,7 +505,7 @@ private object HandleToolsList()
{
return new
{
tools = new[]
tools = new object[]
{
new
{
Expand Down Expand Up @@ -649,7 +649,7 @@ private async Task<object> HandleToolCallAsync(JsonElement root)
var toolName = paramsElement.GetProperty("name").GetString();
var arguments = paramsElement.TryGetProperty("arguments", out var args) ? args : default;

return await this.dispatcher.InvokeAsync(() =>
return await this.dispatcher.InvokeAsync<object>(() =>
{
try
{
Expand All @@ -665,7 +665,7 @@ private async Task<object> HandleToolCallAsync(JsonElement root)
_ => throw new InvalidOperationException($"Unknown tool: {toolName}")
};

return new
return (object)new
{
content = new[]
{
Expand All @@ -679,7 +679,7 @@ private async Task<object> HandleToolCallAsync(JsonElement root)
}
catch (Exception ex)
{
return new
return (object)new
{
content = new[]
{
Expand Down Expand Up @@ -721,7 +721,7 @@ private object ExecuteGetVisualTree(JsonElement arguments)

return new
{
tree = this.BuildTreeNode(root, 0, maxDepth, includeProperties, "")
tree = this.BuildTreeNode(root, 0, maxDepth, includeProperties, string.Empty)
};
}

Expand Down Expand Up @@ -792,12 +792,12 @@ private object BuildTreeNode(TreeItem item, int currentDepth, int maxDepth, bool

if (depObj is System.Windows.Controls.TextBlock tb)
{
props["Text"] = tb.Text?.Length > 100 ? tb.Text[..100] + "..." : tb.Text;
props["Text"] = tb.Text?.Length > 100 ? tb.Text.Substring(0, 100) + "..." : tb.Text;
}

if (depObj is System.Windows.Controls.TextBox textBox)
{
props["Text"] = textBox.Text?.Length > 100 ? textBox.Text[..100] + "..." : textBox.Text;
props["Text"] = textBox.Text?.Length > 100 ? textBox.Text.Substring(0, 100) + "..." : textBox.Text;
}

return props;
Expand Down Expand Up @@ -905,7 +905,7 @@ private string FormatValue(object? value)

if (value is string s)
{
return s.Length > 200 ? s[..200] + "..." : s;
return s.Length > 200 ? s.Substring(0, 200) + "..." : s;
}

if (value is Brush brush)
Expand Down Expand Up @@ -971,7 +971,7 @@ private object ExecuteFindElements(JsonElement arguments)
}

var results = new List<object>();
this.SearchTree(root, typeName, elementName, "", results, maxResults);
this.SearchTree(root, typeName, elementName, string.Empty, results, maxResults);

return new
{
Expand Down Expand Up @@ -1199,7 +1199,7 @@ private object ExecuteGetElementPreview(JsonElement arguments)
return null;
}

var indices = path.Split('/').Select(int.Parse).ToArray();
var indices = path!.Split('/').Select(int.Parse).ToArray();
var current = root;

foreach (var index in indices)
Expand All @@ -1224,7 +1224,7 @@ private async Task SendJsonResponseAsync(HttpListenerResponse response, object d
var buffer = Encoding.UTF8.GetBytes(json);

response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer);
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
response.Close();
}
#endif
Expand Down
Loading