内容
# WPFPreviewMCP
Claude Code 的 Web 版 Preview(浏览器观察和操作 MCP)的一个通用再现版本,用于 **WPF 桌面应用程序**。
由于浏览器中没有与 Chrome DevTools Protocol 相当的观察层,WPF 中没有,因此会在目标进程中注入一个小的**代理**,通过它可以访问 Visual Tree、DependencyProperty、截图、日志、C# eval 等(Snoop 方式)。
## 特点
- **通用零触摸注入**: 使用 `DOTNET_STARTUP_HOOKS`,**无需修改目标应用程序的源代码**,即可将代理注入到 .NET 5 及以上版本的 WPF 应用程序中。
- **进程内**,因此可以通过外部 UI Automation 无法实现的 DependencyProperty 生效值、绑定和任意 C# 执行。
- MCP 服务器、代理和示例应用程序均使用 **.NET 10 / C#**。
## 运行环境
- **操作系统**: Windows(由于 WPF / `System.Windows.Forms` 依赖于 Windows)。
- **.NET SDK**: .NET 10 SDK(构建和运行所需)。
- **目标应用程序**: .NET 5 及以上版本的 WPF 桌面应用程序(零触摸注入条件)。.NET Framework 旧版 WPF 需要额外的代理引用或 DLL 注入。
- **客户端**: 支持 MCP 的客户端(例如 Claude Code)。
## 构成
```
src/WpfPreview.Agent … 在目标进程内运行的观察和操作代理(注入的 DLL)
src/WpfPreview.Mcp … stdio MCP 服务器(公开 preview_* 工具)
samples/DemoApp … 用于注入验证的示例 WPF 应用程序(不引用代理)
```
```
Claude ──stdio──▶ WpfPreview.Mcp ──HTTP(localhost)──▶ WpfPreview.Agent(目标进程内)
└ Dispatcher / Visual Tree
```
代理在启动时将自己注册到 `%TEMP%/wpfpreview/<pid>.json`(端口/标题)。MCP 服务器会枚举这些文件并发现目标(`serverId` = 进程 ID)。
## 构建
```sh
dotnet build -c Debug
```
## 提供工具(preview_*)
| 工具 | 功能 |
|---|---|
| `preview_start` | 使用 `DOTNET_STARTUP_HOOKS` 启动 exe 并注入代理(无需修改) |
| `preview_list` | 列出正在运行的包含代理的应用程序 |
| `preview_stop` | 终止目标进程 |
| `preview_snapshot` | Visual Tree 快照(uid/类型/名称/AutomationId/角色/文本/屏幕坐标) |
| `preview_screenshot` | 窗口的 PNG。`mode='wpf'`(默认,`RenderTargetBitmap`)/`mode='native'`(`PrintWindow`获取 D3DImage/视频预览等的实际像素) |
| `preview_inspect` | DependencyProperty 生效值、值来源、绑定、坐标(类似于 Web 的 computed styles) |
| `preview_click` | 使用 AutomationPeer 的 Invoke/Toggle/Select 进行点击 |
| `preview_fill` | 在 TextBox 等中设置值 |
| `preview_resize` | 调整窗口大小 |
| `preview_console_logs` | 跟踪/调试/控制台输出和未处理的异常 |
| `preview_network` | HttpClient 通信历史(尽力而为) |
| `preview_eval` | 在进程内评估 C# 表达式(全局变量:`App`/`Main`/`Windows`/`Find(selector)`) |
### 选择器
- `#Name` … `x:Name` 或 `AutomationId` 匹配
- `Type[index]` … 按类型名称查找第 N 个(例如:`TextBox[0]`)
- `uid:eN` … `preview_snapshot` 分配的 uid
### XAML 源行映射(调试构建)
如果目标应用程序是**调试构建**,并且在启动前设置了 `ENABLE_XAML_DIAGNOSTICS_SOURCE_INFO=1`,则 `preview_snapshot` 的每个节点和 `preview_inspect` 将具有 **元素定义 XAML 文件的行**(`source`/`sourceUri`/`sourceLine`/`sourcePosition`)。使用 `System.Windows.Diagnostics.VisualDiagnostics.GetXamlSourceInfo`(与 Snoop 相同的机制)。
- 如果通过 `preview_start` 启动,则**自动设置此环境变量**。
- 如果是 Release 构建或信息不可用,则 `source` 为 null(尽力而为,其他功能不受影响)。
- 示例:`Button GreetButton → source: "MainWindow.xaml:10"`
## 注册到 Claude Code
请在 `.mcp.json`(项目根目录)或 `~/.claude.json` 中添加以下内容。`<repository-path>` 应替换为克隆此存储库的绝对路径。
```json
{
"mcpServers": {
"wpf-preview": {
"command": "dotnet",
"args": ["<repository-path>\\src\\WpfPreview.Mcp\\bin\\Debug\\net10.0\\wpfpreview-mcp.dll"],
"env": {
"WPFPREVIEW_AGENT_DLL": "<repository-path>\\src\\WpfPreview.Agent\\bin\\Debug\\net10.0-windows\\WpfPreview.Agent.dll"
}
}
}
}
```
`WPFPREVIEW_AGENT_DLL` 是 `preview_start` 注入的代理 DLL 的位置(如果未设置,则尝试从构建输出中进行默认搜索)。
## 手动注入(不使用 MCP)
```sh
DOTNET_STARTUP_HOOKS="…/WpfPreview.Agent.dll" path/to/YourApp.exe
# 从另一个 shell
curl http://127.0.0.1:<port>/snapshot
```
## 安全
- 代理的 HTTP 服务仅限于 `127.0.0.1`(环回地址)+ 随机端口。
- 在启动时生成会话令牌并写入发现文件(同一用户的 `%TEMP%`)。MCP 读取该文件并在 `X-WpfPreview-Token` 头中发送。**令牌不匹配返回 401**,带有 `Origin`/`Referer`(即浏览器来源)的请求 **403** 被拒绝,阻止本地 CSRF 和其他进程的盲攻击。
- `preview_eval` 可以在目标进程中执行任意 C# 代码(设计上的 RCE)。受令牌身份验证保护,但仅对受信任的对端使用。
## 限制
- 零触摸注入**仅限于 .NET 5 及以上版本**(.NET Framework 旧版 WPF 需要代理引用或 DLL 注入)。
- `preview_network` 仅限于 `HttpClient` 系(不包括自定义 socket/gRPC)。
- `preview_eval` 假设同步表达式(在 UI 线程上评估)。
- `mode='wpf'` 无法捕获 D3DImage/视频预览等硬件渲染(显示为空/透明)。请使用 `mode='native'`。 native 模式包含标题栏等 OS 元素,DPI 为实际像素,最小化时无法捕获。
## 许可证
[MIT License](LICENSE)