在上一篇把 MCP Server 跑通之后,我一直觉得这件事才算真正开始。因为微信SDK + Senparc.AI + MCP这套组合最有意思的部分,不是能在本地调通一个接口,而是当它在 Cursor、VS Code 里被 AI Agent 当成自己的工具来用时,写微信相关代码的方式完全变了:你不用再翻 Senparc.Weixin 的文档去确认某个 API 的参数顺序,只要告诉 AI 你要什么,它自己去查、去调、去生成,你只需要负责 review 和放行。
这篇主要写给两类人:一类是已经在用微信 SDK(尤其是 Senparc.Weixin)写公众号、小程序、企业微信服务的 .NET 开发者,想把手上那堆重复劳动交给 AI;另一类是玩 Cursor / VS Code + MCP 有一阵子,但还没找到合适落地场景的人。微信开发其实是特别适合 MCP 的场景——接口多、参数约定多、而且几乎每个项目都要做一遍"取 access_token、发模板消息、查用户信息"这种固定动作。传统补全只能帮你写个函数签名,MCP 能让你直接对整个业务链路喊话。
1. 从"聊天答疑"到"IDE 自动编写",MCP 到底解决了什么
1.1 为什么不能直接把微信 SDK 文档喂给 AI
很多人一开始会想:微信 SDK 文档是公开的,AI 模型训练时大概率见过,我直接让 AI 写不就行了?试过之后就发现不对。
第一,微信公众平台的接口调整频率不算低,模板消息、订阅消息、客服消息这些接口在不同时期有不同的调用约束,模型的训练数据是有截止时间的,它不会知道你当前项目引用的 SDK 版本里某个方法已经过时。
第二,纯对话式的 AI 只能"看"你贴给它的代码片段,看不到你项目里实际的依赖版本、命名空间、配置文件的真实内容。你让 AI 生成一个调用CustomApi.SendTextAsync的代码,它可能按旧版 SDK 输出一个不存在的重载。
第三,生成代码之后没法验证。AI 写了一段自以为正确的代码,你复制进项目一编译,报一堆错,来回改,那体验跟手写没什么区别。
MCP 解决的就是这三件事:让 AI 能实时读取工具提供的真实环境信息,能直接调用本地进程里跑着的 SDK 方法,并且生成完代码还能联动构建、执行、验证。这才是"自动编写"和"聊天生成代码"的分水岭。
1.2 MCP 的三种角色:Host、Client 与 Server 的分工
MCP(Model Context Protocol)从命名上就看得出来,它关心的是"模型上下文"。这个协议定义了三个角色:
- MCP Host:就是 AI 应用本身,比如 Cursor、VS Code、Claude Desktop。它负责向模型提供工具列表,并接收模型发起的工具调用请求。
- MCP Client:嵌在 Host 里面,负责与 Server 建立连接、发现工具、转发调用。用户一般不需要直接操作这一层,但它决定了你用的是 stdio 还是 HTTP 传输。
- MCP Server:我们这边要自己写的部分。它把微信 SDK、Senparc.AI 的能力包装成一个一个的 Tool,用标准协议暴露出来。
用生活化的类比:Cursor 是工作台,AI 是坐在工作台前的工人,MCP Server 是挂在墙上的工具箱。工人知道自己有这个工具箱,需要时伸手去拿,拿完把结果放回台面上。协议规定了"工具箱怎么挂""工人怎么打开""用完怎么归位",剩下的逻辑全部由工具箱内部的工具决定。
这个分工很重要,因为它决定了我们后续所有代码的组织方式:Server 端只负责把能力干净地暴露出去,不碰 IDE 的任何东西;IDE 侧只负责配置一条命令,让 Host 能把 Server 拉起来。职责一拆,后面调试就很清晰——出问题先判断是 Server 没起来,还是工具没暴露,还是模型没调用。
1.3 Senparc.AI 在新架构里的位置:大脑接线,而不是重复造轮子
这个项目里同时出现了 Senparc.AI 和 IDE 侧的 AI,有人会混淆:到底哪个是大脑?
我的定位是这样:IDE 里的 Cursor/Claude 负责"开发时的自动编写",Senparc.AI 负责"运行时用户消息的智能处理"。两边的 AI 各管一段,但底层的模型接入、对话上下文管理、插件调用机制,完全可以用 Senparc.AI 统一做。
Senparc.AI 是 .NET 侧的 AI 框架,封装了多种模型服务商(OpenAI 兼容接口、DeepSeek、通义等)的接入,也自带 Plugin 机制和对话记忆管理。它和 Senparc.Weixin 同一生态,可以直接读取微信回调事件,把用户发给公众号的消息丢给 AI 处理。所以在这个架构里:
- MCP Server 的工具层:调用 Senparc.Weixin SDK 的能力(发消息、查用户、拉粉丝列表)。
- Senparc.AI 的语义层:理解用户意图、做上下文记忆、决定调哪个插件。
- IDE 里的 AI Agent:通过 MCP 协议把上面两层当成可调用的工具,在开发期直接驱动它们生成代码。
这样组合的好处是:生产环境里微信用户的对话由 Senparc.AI 接管,开发环境里 Cursor 写代码时也可以借助同一个工具层来验证 SDK 调用。你不需要给同一个微信功能写两套封装,一套服务于运行时,一套服务于 IDE,那是浪费。
2. Server 端就绪检查:让微信 SDK 能力对 AI"可见、可调、可验证"
2.1 .NET 侧 MCP Server 的骨架与工具注册要点
用 .NET 写 MCP Server,目前主流方案是引入官方的mcpdotnet库,或者社区维护的 ModelContextProtocol SDK。无论用哪个,核心套路都一样:创建一个 Server,注册若干 Tool,然后启动传输。下面是一个简化骨架(API 以你引入的库版本为准):
var server = new McpServer(options => { options.ServerInfo = new Implementation { Name = "weixin-ai-mcp", Version = "1.0.0" }; }); server.RegisterTool("get_user_info", "根据 OpenId 获取微信用户昵称、头像、关注状态等基本信息。参数:openId(微信用户唯一标识)。", async (UserInfoRequest request, CancellationToken ct) => { // 这里调用 Senparc.Weixin SDK var token = await AccessTokenContainer .GetAccessTokenResultAsync(appId, appSecret); var result = await UserApi.InfoAsync(token.access_token, request.OpenId); return JsonSerializer.Serialize(result); }); await server.StartAsync(transportMode: TransportTypes.Stdio);这里面最容易被忽略的是工具描述(description)。MCP Server 注册工具时,name 是给代码用的,description 是给 AI 模型看的。模型会依据这段描述决定"什么时候该调用这个工具、传什么参数"。描述写得越具体,AI 的调用准确率越高。
我在实际项目中总结了一个工具描述模板,四个要素缺一不可:
- 这个工具干什么:一句话说清楚,不要用"处理微信用户信息"这种模糊表述。
- 输入参数是什么:每个字段的类型、含义、取值范围。
- 返回什么:说清楚返回的是序列化 JSON,还是错误信息。
- 典型使用场景:给模型一个"什么时候该用我"的提示。
比如get_user_info的描述如果只写"获取用户信息",AI 可能不知道该传 openId 还是 unionId,可能在不需要的时候乱调。但如果写明"当用户需要查询粉丝资料、展示用户详情、判断用户是否关注时使用",模型就会在合适的时机主动调用。
2.2 access_token 与凭据:直接把 Secret 暴露给 AI 的后果
微信接口调用的核心是 access_token,所有业务操作之前都要先拿 token。这里面有个坑:如果每个工具实现里都自己写一段"取 token"的逻辑,不仅代码冗余,而且 token 刷新并发控制会出问题。
Senparc.Weixin 提供了AccessTokenContainer,它自带缓存和刷新机制。强烈建议所有工具实现都走这个容器,而不是每次调用都手动请求 token。手动请求的后果是:AI 在 IDE 里自动编写时可能连续触发多个工具调用,每个调用都去刷新一次 token,很快就把微信接口频控打爆。
凭据安全也要单独说。MCP Server 做的事本质上是"把微信操作能力暴露给 AI",但这不意味着把 appSecret 直接塞进工具参数。正确的做法是:
- appId、appSecret 通过环境变量或本机 user-secrets 注入,不进代码仓库。
- MCP 工具层只暴露业务操作(发消息、查用户),不暴露"获取 token"这种底层能力。
- 工具返回结果做脱敏,日志里不打印完整 openId 和任何凭据信息。
我之前见过一个团队把 appSecret 直接写在.mcp.json的 env 字段里,文件提交到 Git 后,整个仓库都泄漏了。这类配置一定要走环境变量,.mcp.json里只写占位符,并且把真实配置加入.gitignore。
2.3 stdio 还是 HTTP:IDE 场景必须知道的传输选型理由
MCP 支持两种主要传输方式:stdio 和 HTTP/SSE。选哪个,直接决定 IDE 侧的连接体验。
| 维度 | stdio | HTTP / SSE |
|---|---|---|
| 启动方式 | IDE 帮你拉起本地进程 | 服务独立运行,IDE 远程连接 |
| 适用场景 | 本机开发、单用户、随 IDE 启停 | 远程容器、团队共享、服务化部署 |
| 调试便利度 | 可直接在终端手动启动看日志 | 需要额外处理端口、鉴权 |
| 与 IDE 生命周期 | 跟随 IDE 一起退出 | 常驻,需自己管理启停 |
在 Cursor、VS Code 这种本地 IDE 场景下,我几乎无脑推荐 stdio。原因很简单:配置里写一条command,IDE 帮你启动进程,崩溃了 IDE 会提示,日志直接在终端输出,连调试都省了。HTTP 模式适合你把 MCP Server 部署到远程开发机、或者多个 AI 应用共享同一个 Server 时用,本地单机用 stdio 最省心。
不过要注意:stdio 模式下,MCP Server 进程的生命周期由 IDE 管理。如果你改了 Server 代码,IDE 里的 MCP 连接不会自动重启,需要在工具的 MCP 面板里手动重启 Server。这个细节后面会专门讲,因为它是"改动不生效"的头号原因。
3. Cursor 接入:一条 JSON 把微信助手挂进 Agent
3.1 两种配置方式的适用边界
Cursor 接入 MCP Server 有两条路径:
- 全局方式:打开 Cursor Settings,进 MCP 面板,添加一个 Server,填命令和参数。这种方式对所有项目生效,适合你个人常用的工具。
- 项目级方式:在项目根目录放一个
.mcp.json,Cursor 检测到这个文件后,会自动把里面声明的 Server 挂到当前项目的 Agent 上。文件可以提交到 Git,团队其他人拉下来直接用。
我的建议是:微信相关的 MCP Server 走项目级配置。因为微信开发的项目通常都绑定了特定的 appId、回调地址等,不同项目用的配置不一样。.mcp.json里如果直接写死 command 但环境变量走系统级注入,每个开发者拉到项目后只要保证本机环境变量到位,就能直接用,省去在 IDE 里手动配来配去的麻烦。
3.2 项目级 .mcp.json 配置示例与启动顺序
在 Cursor 里,项目级 MCP 配置文件长这样:
{ "mcpServers": { "weixin-dev": { "command": "dotnet", "args": [ "run", "--project", "/Users/me/projects/WechatMCP/Server" ], "env": { "WECHAT_APPID": "wx1234567890abcdef", "WECHAT_SECRET": "" } } } }注意几个细节:
command用的是dotnet,因为 MCP Server 是 .NET 项目。如果你的机器上有多个 dotnet 版本,建议用绝对路径,避免 IDE 拉起进程时选错运行时。args里的项目路径,最好用你机器上的绝对路径。团队成员路径可能不同,所以实际协作时更推荐先dotnet build生成 DLL,然后用dotnet /path/to/Server.dll方式启动,少一层编译耗时。env里只留占位符,真实 Secret 走系统环境变量或 user-secrets。- Cursor 对
.mcp.json的识别是有延迟的,文件新建后需要在 MCP 面板里刷新一下,或者重新打开项目。
启动顺序上,我习惯先在终端手动跑一遍命令,确认 Server 能正常起来、没有任何报错,再让 Cursor 连接。否则你会在 Cursor 的 MCP 面板里看到一个红色感叹号,还以为是 IDE 的问题,实际上是自己 Server 压根没启动成功。
3.3 实测:在 Cursor 里用中文描述让 Agent 自动完成微信功能开发
配置好之后,最爽的部分来了。我在一个测试项目里新建了个空文件,然后给 Cursor 的 Agent 发了一条中文指令:
读取项目里的 appsettings.json,获取公众号 AppId。通过 MCP 工具获取最近 200 个关注用户,然后生成一个
SendTemplateMessage.cs,实现向指定 openId 列表发送模板消息的功能,模板 ID 从 appsettings.json 读取。生成完成后,用 dotnet build 验证代码能编译通过。
Cursor 的执行链路是这样的:先读项目文件确认 AppId 配置,然后调用 MCP Server 里的get_user_list工具拉取用户列表,接着搜索项目里引用的 Senparc.Weixin 版本,生成对应 API 的调用代码,最后执行 dotnet build。
这个过程中最值钱的不是那几行代码,而是 AI 通过 MCP 工具拿到的"真实数据"。它拿到的用户列表是你公众号里的真实粉丝,不是模型编造的假数据。生成的代码里 openId 都是真实存在的测试值,可以直接拿去联调。
实际跑下来,第一次生成的代码会有一些小毛病:比如模板消息的跳转链接字段名写错、异步方法没 await。这些都是正常现象。你在对话框里指出问题,AI 改起来也快。关键流程已经打通——从需求描述到真实 SDK 调用再到编译通过,整个链路只需要几分钟,这就是 MCP 在 IDE 里的核心价值。
4. VS Code 与 Claude Code 的接入路径及隐蔽坑位
4.1 VS Code 原生 MCP 配置位置与命令面板操作
VS Code 从较新的版本开始原生支持 MCP,配置位置在项目根目录的.vscode/mcp.json。格式和 Cursor 的略有不同:
{ "servers": { "weixin-dev": { "type": "stdio", "command": "dotnet", "args": [ "run", "--project", "D:/projects/WechatMCP/Server" ], "env": { "WECHAT_APPID": "" } } } }配置好之后,在命令面板里输入MCP就能看到相关命令:MCP: List Servers列出所有 Server 及状态,MCP: Restart Server重启指定 Server。开发时我几乎每改一次 Server 代码都会用一次 Restart Server,这是 VS Code 侧最常用的操作。
VS Code 里还有一个容易忽略的点:MCP 工具默认只在 Copilot 的 Agent 模式下被主动调用。如果你只是用普通 Chat 提问,Copilot 可能不会触发工具调用。所以测试时记得把对话模式切到 Agent,或者直接在指令里写明"使用 MCP 工具完成"。
4.2 复用同一个 Server:Claude Code for VS Code 的二次接线
如果你装了 Claude Code for VS Code 这个扩展,会发现它也能挂 MCP。很多人问:同一个微信 MCP Server,能不能让 Cursor、VS Code Copilot、Claude Code 三方共用?
答案是可以的,而且这正是 stdio 模式的好处——它就是一个本地进程,谁拉起它谁用。只要别同时让两个 IDE 进程操作同一个 Server 实例就行(每个 IDE 会拉起自己独立的进程,互不影响)。
Claude Code 的接线方式是在终端执行:
claude mcp add --transport stdio weixin-dev -- dotnet run --project D:/projects/WechatMCP/Server这条命令会把 Server 注册到 Claude Code 的全局配置里。之后你在 Claude Code 里启动会话,它就能发现并调用这些工具。
复用时有个小建议:把 Server 项目单独建库,不要塞进某个业务项目内部。这样 Cursor、VS Code、Claude Code 都指向同一个项目路径,业务方只需要维护一份工具代码。我在项目初期没注意这点,Server 代码散落在两个仓库里,改一个忘一个,后来才抽成独立项目。
4.3 我在 VS Code 侧踩过的三个具体问题
第一个问题是路径写错。VS Code 的mcp.json里args如果用相对路径,是相对于当前工作区根目录解析的,但很多人惯性写成相对于配置文件。比如我在子目录打开项目时,--project指向的项目路径就不对了。解决办法是统一用绝对路径,或者先dotnet build生成 DLL,用 DLL 路径。
第二个问题是改动不生效。改完 MCP Server 的代码,在 VS Code 里怎么调用都还是旧逻辑。原因是 stdio 进程一旦被 IDE 拉起,不会感知代码变更,必须手动 restart。VS Code 的命令面板里MCP: Restart Server能解决 90% 的"为什么改了没反应"问题。
第三个问题和能力边界有关:MCP 工具返回的数据太大,会把上下文撑爆。比如拉取 200 个用户列表,工具返回一个超长 JSON,这段 JSON 会被塞进 AI 的上下文窗口,不仅浪费 token,还可能让模型忽略掉后面的指令。后来我在工具实现里加了分页和截断参数,默认只返回最重要的字段,描述里也写明"可选参数 limit,默认 20"。这算是一个从实际使用中反推出来的设计优化,一开始根本想不到。
5. 端到端演示:一句话让助手完成模板消息推送
5.1 需求拆分与给 AI 的 Prompt 设计
前面讲了一堆配置和概念,现在来一个完整的端到端场景:我想给一个 CSV 文件里的用户发送模板消息。文件里有 openId 和昵称,模板里要带上不同用户的昵称,跳转链接都一样。
这个需求如果手写,流程是:读 CSV、取 token、循环构造模板数据、调用发送接口、处理错误、写日志。代码量不大但很琐碎。用 MCP 打通之后,我给 AI 的指令是这样的:
项目根目录有
subscribers.csv,第一列是 openId,第二列是昵称。请写一个控制台程序,读取这个文件,给每个用户发送一条模板消息,模板 ID 用 appsettings.json 里的TemplateId,模板内字段:昵称 nickname,跳转链接 url。注意:先只取前 5 个用户发送,不要全量发。发送前先通过 MCP 的 get_user_info 工具确认这 5 个 openId 都是有效的关注用户。代码写完后执行 dotnet run 验证,并且打日志输出每次发送的结果。
这里刻意加了几个约束,是有讲究的:
- 明确输入位置:告诉 AI 数据源在哪,避免它自己编造一个文件路径。
- 限定范围:"先只取前 5 个"是为了安全,防止 AI 一发疯把全量用户都发了。对于有副作用的操作,Prompt 里必须设安全边界。
- 要求工具验证:指定用 get_user_info 确认 openId 有效性,强制触发 MCP 工具调用。
- 要求日志:让 AI 写日志输出,这样我能事后核验。
5.2 实际执行链路:工具调用、代码生成、构建验证
AI 拿到这个指令后,执行链路大致如下:
- 读取
subscribers.csv,解析出前 5 行数据。 - 调用 MCP Server 的
get_user_info工具,逐个确认 openId 是否有效。 - 在项目里搜索 Senparc.Weixin 的模板消息 API,确定当前 SDK 版本的方法签名。
- 生成
Program.cs,用AccessTokenContainer获取 token,循环调用模板消息接口。 - 执行
dotnet run,观察日志输出。
实际执行时会遇到一个典型问题:AI 生成的代码里,模板消息的 data 字段格式容易错。模板消息的 data 里每个字段要传value和color两个属性,新手经常漏掉color。AI 第一次生成的代码就漏了,跑起来接口返回错误码。我直接把错误信息贴回对话框,说"返回 47003 参数格式错误,检查 data 字段结构",AI 很快就修正了。
这个正反馈循环是传统开发方式给不了的:代码生成、真实运行、报错、修复、再运行,全部在几轮对话里完成,而其中"真实运行"这一步是关键。没有 MCP 工具提供的真实 token 和用户数据,AI 生成的代码只能在语法层面正确,很难在业务层面正确。
5.3 结果核验与失败回滚的兜底手段
让 AI 自动写代码并运行,听起来很爽,但也得有兜底手段。我在这个演示里做了三件事:
- 限制发送范围:指令里明确只发前 5 个用户,且这 5 个用户都用 get_user_info 验证过。就算逻辑写错,影响面也可控。
- 日志留痕:要求 AI 在代码里写日志,每次发送都输出 openId 和接口返回码。事后我可以对照 CSV 一行行核验。
- 构建产物独立:生成的
Program.cs只依赖一个独立的控制台项目,和原有业务代码隔离。就算它把整个项目改崩了,git 回滚也很快。
还有一点,如果发的是真实的用户,建议先把自己的微信号混进测试列表里,用真实身份接收一条,确认模板渲染效果没问题再扩大范围。我一般会在 CSV 里先放两行测试数据,一行是自己的 openId,一行是乱写的无效 openId,用来验证工具能区分有效和无效用户。这一招在生产环境尤其管用。
6. 从"能跑通 Demo"到"稳定可用":工具描述、超时与权限管理
6.1 工具描述写不好,AI 就会"自由发挥"
MCP 工具能不能被 AI 正确使用,七成靠描述,三成靠命名。我见过不少团队写的工具描述是这样的:
获取用户信息
然后 AI 就会在各种奇怪的地方调用它:生成代码前先查一下用户、调试时乱传参数。这不是 AI 笨,是你没告诉它正确的使用边界。
对比一下好的描述:
根据 openId 获取微信用户基础信息,包括昵称、头像、关注状态、性别、城市。当需要确认用户是否有效、展示用户资料、判断粉丝状态时调用。参数 openId 必填,必须是微信用户唯一标识。返回 JSON 格式用户信息,若用户不存在或已取关,返回 is_subscribe=false。此工具为只读工具,不产生任何业务副作用。
这段描述把"什么时候用、怎么用、返回什么、有什么副作用"全部说清楚了。模型看到这个描述,就知道这是一个只读查询工具,不会在发消息的场景乱调用。
工具命名也建议遵循统一规则:动词 + 对象。get_user_info、send_template_message、get_user_list,一眼就能看出该在什么时候用。命名混乱的后果是 AI 在工具列表里找不到合适工具,转而自己"编造"一个 SDK 调用方式,那就又回到老路了。
6.2 超时与并发:IDE 卡顿的常见元凶
MCP Server 跑在 IDE 的子进程里,如果某个工具执行时间过长,会直接影响 IDE 侧的使用体验。我这里遇到过的典型情况是:get_user_list分页拉取大量用户时,接口耗时超过几十秒,IDE 的 MCP 面板一直转圈,AI 对话框也等不到响应。
解决思路有两个方向:
- 工具内部限时:所有工具方法都加 CancellationToken,调用微信接口时设定超时时间(比如 15 秒),超时立刻返回错误信息,不让调用方无限等待。
- 长任务拆分:不要在单个工具里做"全量拉取",改成
get_user_list支持分页参数,每次返回一页。AI 需要多少就拉多少,避免一次调用拖垮整个进程。
并发问题则集中在 access_token 刷新上。多个工具同时被 AI 调用时,如果每个都自己去刷新 token,微信接口会返回频繁调用错误。用AccessTokenContainer统一管理后,它内部有锁机制,多个并发请求只会触发一次刷新。这个点如果自己实现而不走 SDK 容器,很容易踩并发刷新雷。
6.3 一套安全的权限分层配置
最后说说权限。MCP 打通之后,AI 理论上能操作你公众号的一切能力,权限设计必须提前想清楚。
首先,工具要分层。只读类工具(查用户、查列表、查菜单)和写操作类工具(发消息、改菜单、群发)分开注册。AI 调用写操作工具时,Cursor 和 VS Code 都会弹出审批确认,这个机制别关掉。我之前为了图方便,在 Cursor 里把自动执行工具打开了,结果 AI 在调试时真的把一条测试消息发到了用户手机上,虽然内容无害,但也吓得够呛。
其次,凭据不进代码。环境变量注入、.gitignore 排除、user-secrets 管理,这套流程在 Server 端就做好,IDE 侧配置只用占位符。
最后,日志脱敏。MCP Server 的日志如果直接打印 openId、token 等敏感信息,会增加泄漏面。统一用脱敏函数处理后再输出,日常调试看个大概就好,真要排查时再开详细日志。
权限这块我的原则是:MCP 让 AI 变得能干,但能干的前提是可控。审批弹窗多点是小事,发错消息影响用户信任才是大事。
最后分享一个我在实际使用中的体会:MCP 接入 IDE 后,收益最高的并不是"AI 能写出多复杂的逻辑",而是把"查 SDK 文档—确认参数—生成样板代码—编译试错"这条链路缩短到几分钟。微信开发里的很多任务本质上是固定模式的重复,ACCESSTOKEN 管理、模板消息构造、用户数据拉取,这些活交出去,人的精力就能留给真正需要判断的业务设计上。后续我还打算在这个 Server 里继续扩展企业微信和微信支付的工具,把更多日常操作纳进来。如果你也在做类似的事,建议先从一两个只读工具开始跑通全链路,再用到实处。