1. Windows MCP.Net 是什么:桌面自动化场景下的 MCP 服务器
Windows MCP.Net 是一个基于 .NET 构建的 Windows 桌面自动化 MCP 服务器,它把鼠标点击、键盘输入、窗口切换、文件读写、OCR 识别、音量亮度调节这些桌面操作,统一封装成符合 Model Context Protocol 规范的工具,让 AI 助手可以通过标准协议直接调用。简单说,它解决的是「AI 能聊天但碰不到我的桌面」这个问题——你不再需要手动把屏幕截图贴给模型,而是让模型自己决定点哪里、输入什么、打开哪个程序。
它适合谁?三类人最值得关注。第一类是 .NET 开发者,想给自己的应用加一层 AI 可调用的自动化能力;第二类是自动化测试和 RPA 工程师,希望用自然语言驱动桌面流程;第三类是 AI 工具玩家,手里已经有 Claude Desktop、Cline、Cursor 这类支持 MCP 的客户端,想给它们接上一双能操作 Windows 的手。
MCP 协议本身是客户端和服务器之间的约定:客户端负责把工具列表和调用请求发出去,服务器负责执行并返回结构化结果。Windows MCP.Net 扮演的就是服务器角色,通过 stdio 传输层和客户端通信。它的工具注册机制基于特性标注,一个带[McpServerToolType]的类加上若干[McpServerTool]方法,就能被自动扫描并暴露给客户端,不需要手写路由表。
我实测下来,这套设计的好处是扩展成本低。你想加一个新工具,只要在服务接口里加方法、在实现类里写逻辑、再建一个工具类包一层,重启服务就能在客户端看到新工具。整个过程不涉及协议细节,对 .NET 开发者非常友好。
从架构上看,它分成五层:协议通信层负责 MCP 消息收发,工具实现层把服务能力包装成工具,业务服务层写具体逻辑,接口定义层做解耦,Windows API 层通过 P/Invoke 调用 user32.dll 等系统库完成底层操作。这种分层让每一层职责清晰,测试和替换都方便。
需要说明的是,桌面自动化天然涉及系统权限,建议在受控环境里跑,别一上来就让它操作生产环境的敏感窗口。下面我会从环境准备开始,一步步带你把这个服务器跑起来,并接上支持 MCP 的客户端完成一次真实的桌面点击验证。
2. 前置准备:.NET 环境、TaoToken 接入与 MCP 客户端选型
在动手之前,先把三样东西准备好:.NET SDK、一个能调用模型的 API 通道、以及一个支持 MCP 的客户端。这三者缺一不可,很多人卡在第一步就是因为 SDK 版本不对。
.NET 版本方面,Windows MCP.Net 用的是较新的 .NET 框架特性,建议装 .NET 8 或更高版本的 SDK。你可以打开 PowerShell 执行dotnet --list-sdks确认。如果输出里没有 8.0 及以上,去微软官网下载安装包,装完重开终端再验证一次。这里有个坑:装完 SDK 后dotnet命令仍报找不到,多半是环境变量没刷新,重启终端或注销重登即可。
模型通道这块,我用的是 TaoToken 提供的 API 接入。它的 Base URL 是https://taotoken.net/api,兼容 OpenAI 风格的接口,客户端配置里填上这个地址和你的 Key 就能调用模型。对于 MCP 场景,模型需要具备工具调用(function calling)能力,否则客户端拿到工具列表也没法触发。你可以在模型对话页面先确认目标模型支持工具调用,再去配置客户端。
MCP 客户端的选择上,常见的有 Claude Desktop、Cline(VS Code 插件)、Cursor 等。它们都支持在配置文件里声明 MCP 服务器。以 Cline 为例,它读取的是 VS Code 的 settings.json 里的 MCP 配置段;Claude Desktop 则读自己的claude_desktop_config.json。不管你用哪个,核心都是告诉客户端:这个服务器的启动命令是什么、工作目录在哪、通过什么传输方式通信。
这里要强调一个概念:MCP 服务器本身不调用模型,它只提供工具。模型调用发生在客户端侧,客户端把工具列表连同用户问题一起发给模型,模型决定调哪个工具,客户端再把调用请求转发给服务器执行。所以你的模型通道(TaoToken)和 MCP 服务器是两条独立的链路,都要配通。
如果你打算长期跑编码类或 Agent 类任务,可以考虑 TaoToken 的 Coding Plan,它在多轮工具调用场景下更省心。只是做一次验证的话,用按量计费的 API Key 就够了。Key 在控制台的 API Keys 页面创建,创建后复制保存,页面关闭后不再完整显示。
最后确认一下工作目录。MCP 服务器启动时会以某个目录为基准,文件操作类工具的相对路径都基于它。建议单独建一个测试目录,比如D:\mcp-test,避免误操作到系统盘重要文件。
3. 可复制配置:MCP 服务端启动与客户端接入片段
这一节给你可以直接复制的配置。先看服务端怎么启动,再看客户端怎么接。
服务端如果用现成的 Windows MCP.Net 项目,编译后得到一个可执行文件,启动命令类似这样:
cd D:\projects\Windows-MCP.Net dotnet build -c Release dotnet run --project .\src\WindowsMcp.Server\WindowsMcp.Server.csproj如果你要自己写一个最小可用的 MCP 服务器,Program.cs的关键注册代码如下:
var builder = Host.CreateApplicationBuilder(args); // 日志输出到 stderr,stdout 留给 MCP 协议消息 builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace); builder.Services .AddSingleton<IDesktopService, DesktopService>() .AddSingleton<IFileSystemService, FileSystemService>() .AddSingleton<ISystemControlService, SystemControlService>() .AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(Assembly.GetExecutingAssembly()); await builder.Build().RunAsync();注意LogToStandardErrorThreshold这行,它把日志全部导向 stderr。原因是 stdio 传输下 stdout 被 MCP 协议独占,任何多余的 stdout 输出都会污染协议消息,导致客户端解析失败。这是新手最容易踩的坑之一。
工具类的写法,以点击工具为例:
[McpServerToolType] public class ClickTool { private readonly IDesktopService _desktopService; private readonly ILogger<ClickTool> _logger; public ClickTool(IDesktopService desktopService, ILogger<ClickTool> logger) { _desktopService = desktopService; _logger = logger; } [McpServerTool, Description("Click at specific coordinates on the screen")] public async Task<string> ClickAsync( [Description("X coordinate")] int x, [Description("Y coordinate")] int y, [Description("Mouse button: left, right, or middle")] string button = "left", [Description("Number of clicks: 1=single, 2=double")] int clickCount = 1) { _logger.LogInformation("Clicking at ({X},{Y})", x, y); var (response, status) = await _desktopService.ClickAsync(x, y, button, clickCount); var result = new { success = status == 0, message = response, coordinates = new { x, y } }; return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); } }客户端接入配置,以 Cline 在 VS Code 的settings.json为例:
{ "mcpServers": { "windows-mcp": { "command": "dotnet", "args": [ "run", "--project", "D:\\projects\\Windows-MCP.Net\\src\\WindowsMcp.Server\\WindowsMcp.Server.csproj" ], "env": { "DOTNET_ENVIRONMENT": "Development" } } } }如果你用的是 Claude Desktop,配置写在claude_desktop_config.json里,结构类似:
{ "mcpServers": { "windows-mcp": { "command": "D:\\projects\\Windows-MCP.Net\\bin\\Release\\net8.0\\WindowsMcp.Server.exe", "args": [] } } }这里三件套要齐全:Base URL 指向https://taotoken.net/api,Key 填你创建的 API Key,Model ID 填支持工具调用的模型名。客户端里模型配置和 MCP 配置是分开的两块,别混在一起填。
配置完成后重启客户端,在 MCP 面板里应该能看到windows-mcp这个服务器,展开后列出所有工具。如果工具列表为空,先检查服务端是否真的启动成功,再看日志有没有报错。
4. 验证请求:一次桌面点击调用的完整过程
配置好之后,最关键的一步是验证工具真的能被调用。我建议从最简单的点击开始,别一上来就搞复杂流程。
先手动确认服务端能独立运行。在终端执行启动命令,如果看到类似Application started的日志且没有异常退出,说明服务端本身没问题。此时它处于等待 stdio 输入的状态,你直接敲键盘它不会有反应,这是正常的。
接下来在客户端里发起一次调用。以 Cline 为例,在对话框里输入类似这样的指令:
请调用 windows-mcp 的 click 工具,在屏幕坐标 (400, 300) 处单击一次。
模型收到后,会先返回一个工具调用请求,客户端把它转成 MCP 消息发给服务端。服务端执行SetCursorPos(400, 300)然后触发鼠标事件,返回 JSON 结果:
{ "success": true, "message": "Successfully clicked at (400,300) with left button 1 time(s)", "coordinates": { "x": 400, "y": 300 } }客户端拿到这个结果后,再把它回传给模型,模型据此生成自然语言回复。整个链路走通,你会看到鼠标真的移动到了指定位置并完成点击。
如果你想验证更贴近实际的场景,可以试试「打开记事本并输入文字」这个组合。指令写成:
用 windows-mcp 启动记事本,然后在编辑区输入「MCP 桌面自动化测试成功」。
模型会依次调用 launch_app、type 两个工具。launch_app 通过开始菜单启动 notepad,type 在指定坐标输入文本。这里要注意,type 工具需要先确保焦点在编辑区,所以通常会在输入前先 click 一下编辑区坐标。如果输入没生效,多半是焦点没对上,调整坐标即可。
验证成功的标志有三个:客户端 MCP 面板显示工具调用记录,服务端日志打印出对应的LogInformation,以及屏幕上能看到实际效果。三者都对上,说明整条链路完全打通。
实测下来,第一次调用往往会有几秒延迟,因为服务端要完成依赖注入和工具扫描。后续调用就快了。如果延迟特别长,检查是不是每次都在重新编译,用编译好的 exe 直接启动会快很多。
5. 常见报错排查:401、local proxy failed 与 reading choices
跑通之后不代表一劳永逸,下面这几个报错是我和身边人遇到频率最高的,逐个拆解。
401 Unauthorized。这个几乎都出在模型通道配置上。客户端调用模型时返回 401,说明 API Key 无效或没带上。检查三处:Key 是否复制完整(前后有没有多余空格)、Base URL 是否写成https://taotoken.net/api(注意结尾不要多加斜杠或路径)、请求头里的 Authorization 格式是否为Bearer <你的Key>。如果用的是环境变量注入 Key,确认变量名和客户端读取的名字一致。改完配置记得完全重启客户端,有些客户端不会热加载。
local proxy failed / connection refused。这个报错通常出现在客户端启动 MCP 服务器时。意思是客户端尝试拉起服务端进程但失败了。原因可能是 command 路径写错、args 里的项目路径不存在、或者 dotnet 不在系统 PATH 里。排查方法:把配置里的 command 和 args 拼成一条命令,在终端里手动执行一遍,看报什么错。如果终端能跑通而客户端不行,多半是客户端的工作目录和终端不同,把路径改成绝对路径即可。还有一种情况是端口被占用,但 stdio 传输不涉及端口,所以这个报错在 stdio 模式下基本是进程启动问题。
Error reading choices / unexpected end of JSON。这个报错指向模型返回的内容解析失败,常见于工具调用场景。原因是模型返回的 JSON 不完整或被截断,客户端解析时炸了。可能的原因:模型不支持工具调用却硬要它调、max_tokens 设得太小导致 JSON 被截断、或者网络中断。解决办法是先确认模型支持 function calling,再把 max_tokens 调大,最后检查网络稳定性。如果用的是流式输出,某些客户端在工具调用时会关闭流式,确认一下客户端设置。
OAuth 相关报错。如果你接的是需要 OAuth 的远程 MCP 服务,可能会遇到 token 过期或 scope 不足。本地 stdio 服务器一般不涉及 OAuth,如果你看到这类报错,说明配置里混入了远程服务器条目,检查一下 mcpServers 里是不是有多余的项。
工具列表为空。服务端启动了但客户端看不到工具,先看服务端日志有没有WithToolsFromAssembly扫描到类型。如果没扫描到,检查工具类是否标了[McpServerToolType]、方法是否标了[McpServerTool]、以及程序集是否被正确加载。还有一种可能是 stdout 被日志污染,协议消息解析失败,回到第 3 节确认日志导向 stderr。
排查这类问题的通用思路是分层定位:先确认服务端能独立跑,再确认客户端能拉起服务端,最后确认模型能触发工具调用。哪一层断了就修哪一层,别混着改。
6. 从验证到落地:把桌面自动化接进你的工作流
跑通一次点击只是起点,真正有价值的是把它接进日常流程。这里给你几个我实际用过的方向。
批量文件处理是最容易上手的场景。你可以让模型调用 list_directory 列出目录、search_files_by_extension 按扩展名筛选、copy_file 批量复制。比如「把 D:\Documents 下所有 .txt 文件复制到 D:\Backup」,模型会自己组合这几个工具完成。比写脚本灵活的地方在于,你可以用自然语言描述筛选条件,不用改代码。
系统状态调节也很实用。set_volume_percent、set_brightness_percent 这类工具,配合 get_desktop_state 可以先读当前状态再调整。开会前让模型把音量调到 50%、亮度调到 80%,一句话的事。
OCR 相关的工具适合处理「屏幕上有什么」这类问题。extract_text_from_screen 全屏提取,find_text_on_screen 查找特定文字并返回坐标,再配合 click 就能实现「找到按钮并点击」的闭环。这在自动化测试里很有用,元素位置变了也不用改坐标,靠文字定位。
如果你要长期跑 Agent 类任务,建议把 MCP 服务器做成常驻服务,而不是每次让客户端拉起。常驻的好处是启动开销只付一次,工具调用响应更快。做法是把服务端编译成 exe,用 Windows 服务或计划任务托管,客户端配置里直接指向 exe 路径。
扩展新工具时,记住那个三步套路:接口加方法、实现写逻辑、工具类包一层。测试用 xUnit 写单元测试,mock 掉 Windows API 调用,保证逻辑正确性。配置项通过 appsettings.json 注入,超时、重试次数这些别写死在代码里。
最后提醒一句,桌面自动化涉及系统操作权限,跑之前想清楚边界。测试环境随便折腾,生产环境务必加操作确认或审计日志。工具能力越强,越要管住调用范围。