MCP_VB6
Allows comparing unsaved VB6 IDE components against the Git working tree status via the workspace_status tool.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
VB6MCP — 让 VB6 IDE 成为 MCP 服务器的插件
中文 | English
变更记录:CHANGELOG.md
VB6MCP 是一个 VB6 插件(Add-In):把它加载到 VB6 IDE 之后,IDE 就变成一个 MCP (Model Context Protocol) 服务器。 Claude Desktop / Cursor / Codex 等 AI 编程助手即可直接读取、搜索、修改当前打开的 VB6 工程,并控制编译与运行。
MCP 客户端 (Claude Desktop / Cursor / ...)
│ stdio (JSON-RPC, 每行一条)
▼
bridge/vb6mcp_hub.mjs (Node,零依赖;MCP 线协议 + ide_* / 经验库共 11 个工具)
│ HTTP http://127.0.0.1:8767 (配了凭据时需 bearer token)
▼
bridge/vb6mcp_bridge.py (Python 3,仅标准库;本机唯一的 TCP 监听者)
│ 命名管道 (每个 IDE 实例一条)
▼
VB6MCP.dll ← 运行在 VB6.EXE 进程内 (自身不再监听 TCP)
├─ 命名管道服务端
├─ 自研 JSON 解析/序列化 (JsonLib.bas, 零外部依赖)
└─ VBIDE 扩展对象模型: 读取/修改代码、保存、编译、运行控制这是一个 AI 辅助构建的项目
本项目由 AI 辅助构建,并非逐行人工编写。把这一点讲清楚,对使用者与贡献者都重要。
代码:绝大部分源码由 AI 编程助手在人工监督下生成与修改。人类负责需求、取舍、验收, 以及真机验证——本项目所有关键结论都要求在真实 VB6 上取到可复现的证据。
方法:开发过程刻意按「可验证」组织——任务以卡片下发、每条结论都须附机器可读证据、 关键改动由独立验证方复核(实现者与验证者分开)、工具面有冻结基线门禁、工具做不到时 必须给出机器可读的原因而不是悄悄降级。
人工复核程度不作保证:请不要假设每一行都被人类逐行审阅过。用于生产或二次开发前请自行 评估;发现问题请开 issue(见 CONTRIBUTING)。
已知局限:见「七、已知限制」,尤其是端到端验证只在中文版 VB6(zh-CN)上做过;英文版能力上支持但未闭环这一点。
Related MCP server: forge-mcp
为什么做这个(项目背景)
VB6 在 2008 年就停止演进了,但它写的系统还在跑:工厂、财务、医院、政府内部系统里有大量 陈旧 VB6 工程仍需要维护,而维护者面对的是一套没有任何现代工具链的环境——没有语言 服务器、没有可靠的静态检查、编译报错只能手动点、改代码靠自己不手抖。
AI 编程助手已经能很好地处理现代语言,却够不到 VB6:它们既看不见 IDE 里打开的工程, 也点不了那个「生成工程」对话框。本项目就是补这个缺口。
做法:不另造编辑器,而是把 VB6 IDE 本身变成一个 MCP 服务器——加载一个插件,IDE 就对外提供 120 个工具:读/搜/改代码、静态检查、锚点编辑与安全事务、编译、运行与单步、 弹窗处理、窗体与项目元数据、快照与回滚、打包。此外还有 11 个 hub 面工具(IDE 实例管理与 经验库)在 IDE 进程外提供,客户端合计可见 131 个。 因为工具就住在 IDE 进程里,它能做进程外方案做不到的事:真的去点那个对话框、真的按运行、 真的读断点处的局部变量。
目标:让维护陈旧 VB6 的人(包括作者自己)用上现代 AI 工作流。项目采用 Apache-2.0 并 允许任意扩充——别人做得越好,我能用到的功能也越好。
与同类项目的关系
VB6 的现代化工具并非空白。已知的几条路线:
项目 | 路线 | 与本项目的关系 |
Agwstin/vb6-lsp(含 mcp-bridge.mjs) | VB6 语言服务器 + MCP 桥 | 进程外:编辑期分析强,但无法驱动 IDE 编译/调试/弹窗 |
xlide / xlide-mcp(VS Code 扩展,已迭代到 v10) | 在 VS Code 里支持 VB6/VBA | 编辑器内辅助,不操作真实 VB6 IDE |
wieslawsoltes/VB6(VB6 Studio Web) | 浏览器里重建 VB6 IDE | 另一条路线,面向重写而非维护既有工程 |
各类 VB6 静态分析 MCP | 源码只读分析 | 只读,不能改、不能编译、不能运行 |
本项目的不同:把真实 VB6 IDE 的完整操作面(编译、运行、断点、对话框、窗体、打包) 暴露成 MCP,并把回执诚实性当作产品的一部分——工具做不到时会给出机器可读的原因, 而不是假装成功。
目录结构
MCP_VB6/
├─ MCP_VB6.vbp VB6 工程文件(ActiveX DLL,输出 VB6MCP.dll)
├─ Connect.cls 插件入口,实现 IDTExtensibility2
├─ ServerHost.cls 组装服务器 + Add-Ins 菜单项
├─ McpServer.cls MCP/JSON-RPC 协议分发 + HTTP 路由
├─ HttpConn.cls HTTP/1.1 请求缓冲与解析(支持 keep-alive)
├─ ServerForm.frm 隐藏窗体,承载 Winsock 监听 + 端口探测定时器
├─ CodeTools.cls 120 个 MCP 工具的实现(完整 VBIDE 对象模型 + 文件系统 + 语法门禁/编辑安全/编译权威)
├─ JsonLib.bas 零依赖 JSON 解析/序列化
├─ Utf8.bas UTF-8/ANSI/UTF-16 转换(kernel32 API)
├─ Util.bas 日志、端口文件
├─ register.reg 在 VB6 中注册该插件的注册表项(导入一次即可)
├─ unregister.reg 卸载插件
├─ build.bat 一键编译 + regsvr32 注册 + 导入插件注册表
├─ mcp_config.example.json Claude Desktop 配置示例
├─ bridge/vb6mcp_hub.mjs MCP 入口(Node,stdio;ide_* 与经验库工具)
├─ bridge/vb6mcp_bridge.py 中间服务:本机唯一的 TCP 监听者(默认 8767),转发到命名管道
└─ tests/smoke_test.py 直接打 HTTP 端点的冒烟测试用 AI 助手一键安装(推荐)
不想照下面一步步点?把这一段整段复制进你的 AI 编程助手(Claude Code / Cursor / Codex / 任何能开终端的 agent)。
唯一要你补的是仓库地址:把 <REPO_URL> 换成这个仓库的 URL(先填上更好;留空也行,它会回头问你)。
它会自己取源码、编译、注册、启动 IDE、真的验证 MCP 服务能用,再把客户端接好,最后把「做了什么、看到什么、哪一步没做到」原样报给你。
你是我的安装助手。请在一台 Windows 机器上,从零把 VB6MCP 装好、启动,并通过真实验证。VB6MCP 是一个 VB6 IDE 插件(Add-In,ActiveX DLL,编译产物 VB6MCP.dll):把它加载进 VB6 IDE 之后,IDE 本身就成了一个 MCP 服务器,AI 客户端可以直接读写我当前打开的 VB6 工程、编译并运行它。
源码仓库:<REPO_URL>
如果这个地址还是占位符(空的、或仍写着 <REPO_URL>),先问我要仓库地址;不要猜,也不要去找一个同名的别的仓库。
本次交付:源码取到 -> 编译 -> 注册 -> 启动 IDE -> 真实验证 MCP 服务可用 -> 接好客户端 -> 跑冒烟测试 -> 如实汇报。
边界(请先读完再动手):
- 只做这一件事。不要顺手改我别的工程、别的插件或别的注册表项。
- 禁止破坏性或不可逆的操作:不要删除/覆盖非你创建的文件和目录,不要改动我已有的 VB6 工程,不要卸载或禁用我其它插件,不要"清理"进程或注册表。
- 不要杀进程。不要结束 VB6.EXE 或任何进程;需要关闭 IDE 时先问我(我可能有没保存的改动)。
- 端口只碰你探测到的那个。这一代里**中间服务**是唯一的 TCP 监听者:默认 127.0.0.1:8767(用 --port 或 VB6MCP_BRIDGE_PORT 改),插件自身不再监听 TCP。真实端口以 http://127.0.0.1:<端口>/health 返回的 port 字段为准;python bridge/vb6mcp_bridge.py --print-config 也会打印生效端口、传输与 authEnabled。不要扫描、占用或改动其它端口,也不要把这个服务暴露到回环地址之外(非回环绑定会被直接拒绝并以退出码 6 结束,没有覆盖开关)。
- 任何可能弄坏我现有 VB6 环境的动作(机器级重新注册 OCX/类型库、改 HKCU\Software\VB6MCP、改 VB6 的引用设置等),先停下来问我:你要改什么、为什么、怎么撤回。
- 所有产物只放在你新建的工作目录里;每个结论都要有可复现的命令和原始输出,不要用"应该成功了"下结论。需要我手工点的地方,直接告诉我。
步骤(按顺序执行,每步给出证据):
1) 查前提。确认是 Windows(32 位或 64 位),然后检查:
- 有没有装 VB6 IDE(SP6):找 VB6.EXE(标准安装目录是 VB98 文件夹,各机器可能不同)。
- MSWINSCK.OCX(Winsock 控件)是否可用/已注册;MSADDNDR.DLL(Add-In Designer 类型库)是否可用。
- 32 位注册工具:64 位 Windows 上用 %SystemRoot%\SysWOW64\regsvr32.exe,不要用裸 regsvr32,也不要把 Windows 目录写死(用 %SystemRoot% 变量)。
- 如果没有 VB6:立刻停下并如实告诉我——VB6MCP 是 VB6 IDE 的插件,没有 VB6 就既不能编译也不能运行,没有绕过办法;仓库也不分发任何微软文件,必须用我自己合法的 VB6 安装。不要试图用别的工具链"伪造"VB6,也不要在没有 VB6 的机器上继续后面的步骤。
- 如果缺 MSWINSCK.OCX / MSADDNDR.DLL,按仓库 README 的 FAQ 处理;要写系统目录先问我。
2) 取源码。git clone <REPO_URL> 到你/我约定的工作目录(或下载仓库 ZIP 解压)。确认目录里有 MCP_VB6.vbp、build.bat、register.reg、bridge/vb6mcp_bridge.py、bridge/vb6mcp_hub.mjs、tests/smoke_test.py。
3) 编译。优先直接运行 build.bat(它会先注册 MSADDNDR.DLL,再编译出 VB6MCP.dll)。如果 VB6 不在默认位置,改 build.bat 里的 VB6 变量,不要另处硬编码路径。
注意:不要在 IDE 已经加载了这个插件 DLL 时原地重编译——DLL 被占用会让编译失败。先关掉 IDE(关之前先问我有没有没保存的东西),或用另一个 VB6 实例构建。
也可以在 IDE 里打开 MCP_VB6.vbp 编译,但不要把 VB6MCP 工程自己当成当前活动工程去"生成"(同样是文件锁)。
编译失败时把编译器原始报错贴出来(例如工程引用是否勾选了 VB6 Extensibility 与 Add-In Designer,见 README 的 FAQ);不要凭猜测改源码。
4) 注册并启动 IDE。用步骤 1 确认的 32 位 regsvr32 注册 VB6MCP.dll,导入 register.reg,然后自己查一遍注册表确认:
reg query "HKCU\Software\Microsoft\Visual Basic\6.0\Addins\VB6MCP.Connect" /v LoadBehavior
应得到 0x3(=3,表示自动加载),FriendlyName 应为 VB6 MCP Server。build.bat 一般会替你做注册和导入,但仍要自己查证。
然后启动 VB6 IDE(它本来就开着且没加载插件时,先问我要不要重开,不要自己杀进程),在菜单 外接程序(Add-Ins) -> 外接程序管理器 里确认 "VB6 MCP Server" 已勾选(LoadBehavior=3 通常会自动加载)。
5) 真实验证服务器(关键一步,不能只看"进程起来了"):
- GET http://127.0.0.1:8767/health,直到返回 {"ok":true,...};以响应里的 port 为准,并与 python bridge/vb6mcp_bridge.py --print-config 的 port 对照。下面把这个端口记作 <端口>。**若你(或用户)配置了 token,这个请求也要带 `Authorization: Bearer <token>`**,否则会得到 401。
- 对它发一次真正的 MCP 调用:POST http://127.0.0.1:<端口>/rpc,Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"install-check","version":"1.0"}}}
然后 {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
- 数 tools/list 里的工具个数,并与仓库自己的权威清单对比:docs/TOOL_CATALOG.md 里记录的数量,以及 tools/schema_baseline.json 里 tools 数组的长度。不要写死任何数字——就用你刚克隆下来的仓库里的数字。也可以调用 mcp_info 工具,看服务端自报的 contract 与 toolCount。
- 三者一致就通过并记录数字;不一致就如实报告差多少、多了/少了哪些,不要改仓库、也不要"凑数字"。如果 tools/list 根本拿不到,把原始响应和 HTTP 状态码贴出来。
6) 接客户端(新一代推荐 stdio 启动 hub):command 指向 node,args 指向 <仓库>/bridge/vb6mcp_hub.mjs,并给它两个环境变量 —— VB6MCP_BRIDGE_URL=http://127.0.0.1:<中间服务端口>(hub 源码默认 http://127.0.0.1:8767,服务端口不同时必须显式给出)与 token(VB6MCP_TOKEN 或 VB6MCP_TOKEN_FILE,仅当服务端配置了凭据时)。Claude Desktop 是 %APPDATA%\Claude\claude_desktop_config.json;Cursor 用它的 MCP 配置(也可以 streamable HTTP 直连 http://127.0.0.1:<端口>/mcp,配了 token 时带 Authorization: Bearer <token> 头)。把占位路径换成这台机器上的真实路径;JSON 里用 / 或写成 \\。改配置前先备份原文件。
7) 跑冒烟测试:python tests/smoke_test.py --port <端口>(<端口> 指中间服务端口)。这一步必须在 VB6 IDE 已打开且插件已加载的前提下跑,它不是离线自检。注意该脚本**不发送 token**,仅在服务端未配置凭据时可用;配了 token 时请用 python bridge/vb6mcp_bridge.py --print-config 查看状态,并改用 --selftest 做进程内自检。
8) 汇报(最重要)。给我:
- 你实际执行过的命令(原样)与实际观察到的结果(关键输出、health 响应、工具数、冒烟测试结果);
- 每一步的判定(通过/失败/跳过),失败的给出原始报错;
- 你没能做到的事,直说(例如"没装 VB6,所以无法编译"、"需要我手工点某个对话框"、"这一步没权限")。
不要为了让报告好看而编造成功;做不到就说做不到。
再次强调:不要杀进程;不要动你没被要求动的端口、服务、注册表、工程文件;任何可能破坏我现有环境的事,先问再做。我需要的是"确实成功了",或者"诚实地说清卡在哪里"。想手动来一遍 → 见下面「一、构建」「二、连接模型、端口与多实例」「三、接入 MCP 客户端」。两条路任选其一即可。
一、构建
前提:装有 VB6 IDE(SP6)。MSWINSCK.OCX(Winsock 控件)随 VB6 安装并已注册。
双击运行
build.bat(若 VB6 装在其它目录,编辑其中的VB6变量)。 脚本会先注册 Add-In Designer 类型库(MSADDNDR.DLL),再编译。成功后生成
VB6MCP.dll,并自动执行regsvr32与register.reg导入。启动 VB6 IDE → 菜单 外接程序(Add-Ins) → 外接程序管理器,勾选 "VB6 MCP Server"(注册表 LoadBehavior=3,通常会自动加载)。
也可以在 IDE 里直接打开 MCP_VB6.vbp 编译(需引用:VB6 Extensibility、
Microsoft Add-In Designer;若编译报 IDTExtensibility2 相关错误,见下文 FAQ)。
⚠️ 不要把 VB6MCP 工程自身作为当前活动工程去"生成"——插件 DLL 正被 IDE 加载占用, 会因文件锁导致编译失败。请关掉 IDE 用 build.bat 编译,或用另一个工程测试。
二、连接模型、端口与多实例
新一代:一个监听端口 + 每个 IDE 实例一条命名管道。
客户端不再直连插件。本机由中间服务
bridge/vb6mcp_bridge.py持有唯一一个 TCP 端口: 默认http://127.0.0.1:8767(用--port或环境变量VB6MCP_BRIDGE_PORT改;VB6MCP_BRIDGE_HOST只接受回环地址)。非回环绑定不可能:绑定到非回环地址会被拒绝,并以退出码 6 结束,没有覆盖开关。
MCP 客户端经 hub
bridge/vb6mcp_hub.mjs(Node,零依赖,stdio)接入:hub 提供ide_*与经验库共 11 个工具, 并把插件面的 120 个工具转发给中间服务;hub 用环境变量VB6MCP_BRIDGE_URL找中间服务(源码默认http://127.0.0.1:8767)。插件只接受命名管道(每个 IDE 实例一条;同一条管道不允许并发连接)⇒ 客户端应在同一条连接上复用请求 id, 或把调用串行化;管道忙时回执给
pipeBusy,而不是「没有 IDE」。传输选择
VB6MCP_TRANSPORT=auto|pipe|tcp(默认auto:优先管道,不可用时可见地回退到插件的 HTTP;pipe为严格管道、缺管道是显式错误;tcp只走插件 HTTP,用于回滚与过渡)。自定义端口:
--port N、VB6MCP_BRIDGE_PORT(或同一台机器上的VB6MCP_PORT)。 同时开多个 VB6 实例时仍然只有一个服务端口;多实例靠_target寻址(见第三节)。运行状态查询:IDE 菜单 Add-Ins → VB6 MCP Status...;或
GET http://127.0.0.1:<端口>/health(配了凭据时该请求也要带 token);python bridge/vb6mcp_bridge.py --print-config打印生效端口 / 传输 /authEnabled。日志:中间服务的日志路径见
--print-config的logPath;插件自身日志仍是%TEMP%\vb6mcp.log。
哪些工具免 IDE、哪些必须有活 IDE
插件面 120 个工具:必须有活的 IDE。 它们全部由运行在 VB6.EXE 进程内的插件实现(读/改代码、静态检查、编译、断点、窗体、快照……),没有可执行的实例就没有执行者。列出工具可以在没有 IDE 时进行(hub 会用随包快照回 tools/list,标 faceSource=snapshot),但调用它们需要本机有活实例,或显式寻址一个有活实例的远端端点;没有活实例时的拒绝码见下一小节。
hub 面 11 个工具:在 IDE 进程之外运行,调用它们不需要加载插件,但依赖各不相同:
完全不需要 IDE(只读本机状态,或只读写本地 JSON 存储):
ide_list—— 读本机实例登记表 + 端口扫描 + 已配置远端端点的探测状态;一个实例都没有也照常回执(并说明原因与过期登记项);ide_target—— 只改 hub 自己的默认目标(本地状态,不碰 IDE);list_lessons/add_lesson/delete_lesson/export_lessons/import_lessons_md—— 只读写mcp_lessons/下的 JSON(写操作带.bak)。
需要一个目标实例才动手:
ide_stop(按 pid 停掉本 hub 自己启动的实例,绝不动外来实例)、ide_restart(恢复自有实例并验证就绪)、ide_kill_and_relaunch(管道 busy/wedged 时的带外复位:停自有实例、清注册、原工程重启并轮询就绪;绝不碰 8766/远端实例)。没有任何实例时它们回结构化拒绝,而不是假装成功(ide_restart在「一个实例都没有」时回no_ide_running)。不需要现有 IDE,但需要能启动 IDE 的条件:
ide_start(显式project或VB6MCP_AUTOSTART_PROJECT,加一个可解析的VB6.EXE;否则回not_implemented/vb6_exe_not_found/project_not_found)。
可离线运行的是
tools/check_*那一组门禁;tests/smoke_test.py与mcp_strict_audit必须先打开 IDE 并加载插件 —— 它们不是离线自检。
两个「没有 IDE」类拒绝码,各自的前置条件
no_ide_running:本机一个活实例都没有 —— 登记表里没有任何「pid 还活着」的条目,也没有实例能被探测到。它不用于下面这些情况(各有更精确的形状):
实例已登记、进程还活着,只是没有应答探测 ⇒ 回执给出该 pid、判定字段与恢复用的
nextStep,而不是no_ide_running;只有远端端点上有活 IDE、而这次调用没有显式寻址 ⇒
remote_requires_explicit_endpoint;hub 看不到机群是因为中间服务不可达或被鉴权拒绝 ⇒
bridge_unreachable/bridge_auth_failed(不是「本机空闲」);有多个活实例、无法唯一确定 ⇒
ambiguous_target(附candidates[]/suggestion/resolution)。 另外:没有_target且本机只有一个活实例时,它会自动被选中,不报错。
remote_requires_explicit_endpoint:前置条件必须同时成立 —— (a) 本机没有活实例;(b) 至少一个已配置的远端端点上有活 IDE;(c) 这次调用没有给带 endpoint 的目标(_target 不带 endpoint,也没有通过 ide_target 设过默认)。此时调用不会被自动路由到远端(本机也没有实例)⇒ 回执是 ok:false + routed:false + qualifiedKeys[](可直接照用的 endpoint:projectPath)+ hint。显式给 _target="<endpoint>:<path>"(或先 ide_target{target:"<endpoint>:<path>"})即可寻址。裸 pid / 裸工程名只寻址本机,永远不会被解释成远端。
工程身份:任意 junction / 别名拼写 = 同一个工程
VB6 不允许同一个 .vbp 被打开两次,而同一个工程可以有很多种拼写:目录 junction(别名目录)、junction 指向的真实目录、/ 与 \、不同大小写、8.3 短名。hub 用规范实路径把它们全部归一成同一个身份 —— 这些拼写是同一个候选,不是冲突(同一个身份被两个活实例发布才是冲突)。
归一化不会抹掉「你用的是哪种拼写」:ide_list 对每个实例同时给出
projectPathReal—— 规范身份(realpath 归一后);projectPathAsGiven—— 登记表/探测发布出来的拼写(projectPathIsAlias标出它与规范路径不同);aliasAsGiven/aliasesAsGiven[]/aliasSource/aliasMatchedBy—— 调用方传入的拼写(_target或ide_target默认值),以及它解析到哪个候选、依据是什么(规范路径相同 / 别名目录包含该工程 / 工程名);顶层
aliasEcho—— 逐条列出这次调用传入的每个拼写、命中几个候选、是否全部落到同一个候选(sameCandidate),并附上这条身份规则原文。
这些是加法回显,不是选择器:解析目标仍按规范身份与寻址规则(targetKey = [endpoint:]projectPath)。同一个规范路径在同一个端点上出现两次才算冲突,出现在两个端点上则不算。收益是双向的:任何拼写(junction、真实路径、分隔符差异、大小写、8.3 短名)都落到同一个工程;而「它为什么认成了另一个身份」可以直接从 aliasEcho 读出依据,不必猜。
常驻中间服务:安装、升级、健康与卸载
中间服务(bridge/vb6mcp_bridge.py)是常驻层,不是按需进程:它占住一个 TCP 端口、等待并管理所有 add-in 的命名管道、转发 hub 的调用。即使本机没有任何 VB6 IDE 在运行,它也必须活着;IDE 后起时会被自动接管(add-in 注册管道后立即可转发,无需重启服务)。hub 侧按需拉起(lazy-ensure)只是兜底,常驻服务才是主路径;两者并存不冲突(同一端口只能有一个监听者,服务持有这个单实例)。
安装(需管理员):
powershell -File tools\install_middle_service.ps1 -Action install。它写一个启动器(环境变量 + 日志重定向)、注册开机自启的计划任务(AtStartup、SYSTEM账户、失败按RestartCount/RestartInterval自动重启),启动后轮询/health直到 200。先看计划:加-DryRun只打印将执行的命令与所需权限、不做任何改动。脚本从不自行提权;非管理员运行会返回结构化needs_elevation。默认值:
-Port 8797、-ExePath dist\vb6mcp_bridge.exe(缺失时自动回退到python bridge\vb6mcp_bridge.py)、-TokenFile默认当前用户目录下的.vb6mcp\token.txt;另有-TaskName、-AuditLog、-UseService(检测到 nssm 时改走 Windows 服务)。升级:先停任务 → 替换产物(
dist\vb6mcp_bridge.exe或源码)→ 再跑一次-Action install(幂等覆盖注册)→-Action status应报verdict:UP。卸载(需管理员):
-Action uninstall:停止并移除任务/服务、停止本服务的进程;保留 token 文件与日志/审计(脚本不会删它们)。健康检查(无需管理员):
-Action status输出任务/服务、进程、端口是否LISTENING、/health原文(存在 token 文件时自动带Authorization: Bearer),并给出verdict:up/up-health-unverified/degraded/down。语义:/health的 HTTP 200 表示中间服务本身在跑;body 里的ok表示当前是否有活 add-in —— 没有 IDE 时ok:false属正常(服务在等),不是服务故障。-Action smoke用临时端口启一份新进程跑--smoke(不需要管理员、不碰线上端口),输出结构化结果。日志与审计:服务日志由启动器写到
%ProgramData%\vb6mcp\logs\middle-service.log;远端模式(--allow-remote+ TLS + token)要求--audit-log,安装器默认指向%ProgramData%\vb6mcp\logs\remote-audit.jsonl(JSONL,仅记录外网来源的请求)。两者在卸载时保留。token 轮换:token 来源优先
VB6MCP_TOKEN(不落盘),否则VB6MCP_TOKEN_FILE。轮换时把旧 token 传给--token-prev(或VB6MCP_TOKEN_PREV)⇒ 新旧在一个窗口内同时有效,客户端切换完成后去掉旧值。安装器只引用 token 文件,不创建也不删除。端口必须一致:
VB6MCP_BRIDGE_URL必须与安装器的-Port指向同一个服务端口(安装器默认 8797;bridge源码自身默认是8767,用安装器时以-Port为准)。改端口要同时改安装器与客户端配置,否则 hub 连不上。故障排查:
status报listener.listening:false⇒ 任务没起或进程已退(看日志与任务上次结果lastTaskResult);health.httpStatus:401⇒ 服务在跑但 token 不匹配(核对-TokenFile与客户端 token);hub 报127.0.0.1:null/rpc、RemoteDisconnected或所有插件工具静默失败 ⇒ 没有服务在监听该端口(本轮曾因此让 120 个插件工具全部静默);verdict:degraded⇒ 任务存在但端口未监听,重启任务并复看日志。
三、接入 MCP 客户端
连接与鉴权(新一代)
推荐:客户端以 stdio 启动 hub ——
command: node,args: ["<仓库>/bridge/vb6mcp_hub.mjs"], 环境变量给VB6MCP_BRIDGE_URL=http://127.0.0.1:8767(中间服务端口不同时必须显式给出)。HTTP 直连(streamable HTTP):
POST http://127.0.0.1:<端口>/mcp(/rpc是同一面)。鉴权:一旦服务端配置了凭据(
VB6MCP_TOKEN,或VB6MCP_TOKEN_FILE指向一个仅属主可读的文件), 所有 HTTP 面(/rpc、/mcp、/health…)都需要 bearer token:Authorization: Bearer <token>, 或X-VB6MCP-Token: <token>。把凭据放进 URL 会被拒绝(401token_in_url_rejected); 未配置凭据时服务照常运行,但/health会明确标注auth.enabled=false—— 多用户机器上不要这样长期运行。寻址:调用用
_target指定目标工程,规范写法[endpoint:]projectPath(例如C:/work/MyProj.vbp), 省略endpoint即本机;没有_target的调用不会被送到远端。旧代的直连端口(8766)在新一代不再存在 —— 仍指向它的客户端会直接连接失败。
远端端点:可达性来自转发层,凭据只发回环端点
中间服务只绑回环(非回环绑定被直接拒绝、退出码 6、无覆盖开关)⇒ 跨机可达性不由它提供。 要让另一台机器上的 hub 连到本机的中间服务,需要在它前面放一层中继 / 端口转发,把「只绑回环的」 服务暴露成一个 hub 可访问的 base:
http://<host>:<port>(RPC 路径即…/rpc)。hub 只对回环端点自动发送凭据:唯一的自动机制要求「端点是回环地址(
127.0.0.1/localhost/::1)」 且「该端点用401 token_missing明确索要」两者同时成立 —— 因此 token 不会经这条路径离开本机。对白名单里的远端端点,hub 不带 token ⇒ 远端端点要么自身不鉴权,要么把鉴权做在转发层 (中继 / 端口转发那一层)。
★例外(要清楚自己在做什么):
VB6MCP_BRIDGE_URL指到哪个 base,hub 就把它当作自己的中间服务, 不论它是否回环 —— 所以只有当你显式把一个远端 base 配成 bridge URL 时,hub 才会给它带 token。远端端点来自白名单:环境变量
VB6MCP_REMOTES,或 hub 目录下的hub_remotes.json(仓库附带hub_remotes.example.json模板),每条是{label, url, note?}。只探白名单、绝不自动发现主机; 连不上的端点照样列出并带探测状态;未知 label / 连不上 / 超时分别是结构化拒绝endpoint_unknown/endpoint_unreachable/endpoint_timeout。
Claude Desktop
让 Claude Desktop 以 stdio 启动 hub(路径换成你自己的):
{
"mcpServers": {
"vb6": {
"command": "node",
"args": ["C:/path/to/VB6MCP/bridge/vb6mcp_hub.mjs"],
"env": { "VB6MCP_BRIDGE_URL": "http://127.0.0.1:8767" }
}
}
}服务端配了 token 时,在 env 里再加 VB6MCP_TOKEN(或 VB6MCP_TOKEN_FILE)。
★配置里的路径都要换成你自己机器上的:示例(包括
mcp_config.example.json)用的是占位路径 ——command的 node/python、args指向的仓库脚本(如bridge/vb6mcp_hub.mjs)都要改成你的实际安装位置。JSON 里建议统一用正斜杠/;若要写 Windows 反斜杠,必须写成\\。
Cursor(streamable HTTP)
Cursor 也可以直接连中间服务的 streamable HTTP 面:
类型: streamable HTTP
URL: http://127.0.0.1:8767/mcp
请求头: Authorization: Bearer <token> # 仅当服务端配置了 token(用 stdio 启动 hub 也可以,见上。)
接入 DeepSeek Harness(DSH)
DSH 的 MCP 服务器不在 Windows 注册表里,而在 DSH profile 的 plugin 列表(典型路径
~/.dsh/profiles/<profile>/cordis.patch.yml)。加一条 @deepseek-ai/dsh-mcp-client 条目即可:
- id: mcp-vb6
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: vb6 # 工具呈现为 mcp__vb6__<tool>;[A-Za-z0-9_-]{1,32},同一作用域内唯一
transport: stdio # 推荐:走 stdio hub,DSH 启动时即使没有 IDE 也能看到完整工具面快照
command: node # 也可写你自己的 node 可执行文件绝对路径
args: [ '<仓库路径>/bridge/vb6mcp_hub.mjs' ]
cwd: '<仓库路径>'
env:
VB6MCP_BRIDGE_URL: http://127.0.0.1:8767 # 中间服务端口,不是 8766
VB6MCP_TOKEN_FILE: '<仓库路径>/vb6mcp_token.txt' # 未配置凭据时可省略三个实测坑(都踩过):
必须显式给
VB6MCP_BRIDGE_URL。省略时 hub 用自己的默认值走本地发现路径,会把中间服务误当成一个"假 IDE", 转发时不带 token → HTTP 401 → 每次工具调用都报Expected property name or '}' in JSON at position 1。不要指向 8766。8766 是维护者交互会话保留端口。若要直连服务(不经 hub),用
transport: streamable-http+url: http://127.0.0.1:8767/mcp,但那样只暴露插件面工具,不含 hub 面的 11 个工具(hub 面工具只在 stdio hub 路径下可见)。改完重启 DSH;用
mcp__vb6__mcp_info核对contract/toolCount,用mcp__vb6__ide_list看 IDE 发现情况。 若报bridge_unreachable/bridge_auth_failed,先确认中间服务-Action status的 verdict 是 UP(见上一节)。
其它 stdio 客户端
与 Claude Desktop 相同:command: node、args: ["<仓库>/bridge/vb6mcp_hub.mjs"],按需设置 VB6MCP_BRIDGE_URL 与 VB6MCP_TOKEN / VB6MCP_TOKEN_FILE。
验证链路是否打通:python tests/smoke_test.py --port <中间服务端口>(该脚本不发送 token,只在服务端未配置凭据时可用;且需要先打开 VB6 IDE 并加载插件 —— 这不是离线自检)。
四、工具列表(插件面 120 + hub 面 11 = 131 个)
权威清单见
docs/TOOL_CATALOG.md(由运行中的 add-in 经tools/list导出,再并入 hub 自有工具), 离线基线为tools/schema_baseline.json,可用tools/check_live_tools.py docs/tools_schema.json做离线交叉比对。工具来自两个面:插件面 120 个(
docs/tools_schema.json,住在 VB6 IDE 进程内)与 hub 面 11 个(bridge/hub_tools.json,在 IDE 进程外)。下面按功能把两个面共 131 个工具分组, 每个工具只出现一次:插件面分 16 类,hub 面见第 17 节;关键参数一列直接取自各自的inputSchema(必填参数在前),不是凭记忆写的。hub 面的 5 个经验库工具无需 IDE 在线即可用。
1. 工程与工作区
工具 | 能干什么 | 关键参数 |
| 读工程全貌:类型、IDE 状态、启动对象、编译路径、组件与引用 | - |
| 设置工程选项:描述、启动对象、编译输出路径、只读模式 |
|
| 打开 .vbp 工程,可独占替换当前工作区 |
|
| 新建工程,或按模板创建工程 |
|
| 从 IDE 工作区关闭一个工程 |
|
| 保存工程与全部未保存组件 |
|
| 读写 .vbg 工程组:列出、添加、移除工程 |
|
| 列出 IDE 中已改但未保存的组件,并分类 pane-vs-disk( | - |
2. 组件生命周期
工具 | 能干什么 | 关键参数 |
| 列出全部组件及其过程、行号、脏状态 | - |
| 新建模块/类/窗体等组件 |
|
| 从工程移除组件,不可撤销 |
|
| 把源文件或模板导入为工程组件 |
|
| 把组件代码或整个设计器导出到文件 |
|
| 重命名工程内的组件 |
|
| 从磁盘上绑定的文件重新加载组件 |
|
| 打开组件代码窗格或设计器并置前 |
|
3. 代码读取、检索与光标
工具 | 能干什么 | 关键参数 |
| 从 IDE 内存读组件代码,可只要某个过程或符号; |
|
| 读磁盘源码:编码识别 + 字节级 sha256 |
|
| 读任意文本文件,UTF-8/UTF-16/ANSI 自动识别 |
|
| 在组件内查找文本:整词、大小写、模式匹配;回 |
|
| 读 IDE 当前选中的组件、活动窗口与光标 | - |
| 读代码窗格的选区、顶行与可见行数 |
|
| 设置代码窗格选区与滚动,并可置前 |
|
| 读断点停在哪:模块、过程、行号、源码行 | - |
4. 代码编辑与安全事务
工具 | 能干什么 | 关键参数 |
| 行级改代码:替换、插入、追加、删除、整模块替换 |
|
| 向组件追加代码,来自字符串或文件主体;落点由写后回读给出( |
|
| 生成事件过程桩,如 Command1_Click |
|
| 写文本文件,可选编码、建目录与备份;回执含 |
|
| 写 VB 源码文件:显式编码 + 字节级 sha256;回执含 |
|
| 按唯一内容锚点替换整行,不依赖行号 |
|
| 原子批量锚点编辑,任一处失败则整批不改 |
|
| 只回统一 diff 预览,不写任何内容 |
|
| 跨组件字面/正则替换,默认只计划不写 |
|
| 按词法重命名标识符,跳过字符串与注释 |
|
| 事务化移动整个过程块(源删除 + 目标插入,任一侧失败双侧回滚) |
|
| 一次事务删除多个过程(每个受影响组件先写 undo 快照,失败全部恢复) |
|
| 按 GBK/ANSI 原始字节锚点改文件(base64 进出;拒绝切开双字节字符,失败不写盘) |
|
5. 语法门禁与工程校验
工具 | 能干什么 | 关键参数 |
| 写前静态校验代码,回带稳定指纹的诊断 |
|
| 用同一套规则校验 IDE 里的组件文本;可选 |
|
| 组件总判定:IDE 与磁盘一致 + 诊断计数;仅隐藏 Attribute 行的差异单列 |
|
| 批量核验多个组件的 IDE 与磁盘一致性;UNKNOWN 单列不算 fail( |
|
| 按块结构重排缩进,并证明只改前导空白 |
|
| 列出工程引用并标出文件缺失的引用 |
|
6. 依赖、纯度与公开 API
工具 | 能干什么 | 关键参数 |
| 组件与过程级依赖图:边、环、纯度分类;主截止是确定性字符配额( | - |
| 判定组件能否进离线测试宿主及原因 |
|
| 把组件公开 API 冻结成可比对规格;省略 |
|
| 与冻结规格比对,报移除、变更、新增成员;给了 |
|
| 生成可编译桩模块:留声明、清过程体 |
|
7. 编译与交付产物
工具 | 能干什么 | 关键参数 |
| 编译工程并回权威产物判定,可选真日志诊断;构建状态可查询( |
|
| 用隔离的 VB6.EXE /make 编译并回编译器日志 |
|
| 组装交付目录:exe、清单、依赖表、安装脚本 |
|
| 生成或读取 exe/dll 旁清单(UAC 与 DPI) |
|
8. 运行、调试与断点
工具 | 能干什么 | 关键参数 |
| 控制运行:启动、全编译、中断、单步、结束、重启 |
|
| 读进程窗口、是否无响应、模态框数量 |
|
| 列出本服务器认识的调试命令目录(固定表) | - |
| 注入 Debug.Assert False 断点,编译版自动省略 |
|
| 按 id 移除注入的断点块 |
|
| 列出注入的断点及其行号与启用状态 | - |
| 经调试菜单切换真实 IDE 断点或全部清除 |
|
| 在过程入口注入受控故障:抛错、返回、延时 |
|
| 移除先前注入的故障块 |
|
| 一次调用跑完整个调试会话:设断点 → 不保存工程启动 → 等命中 → 按序执行动作 |
|
| 运行到断点 / 进程退出 / 未处理错误 / 超时(两段式:Start 是排队命令) |
|
| 管理真实 IDE 断点:设/清/启用/禁用/列出(组件 + 源码锚点或 pane 行) |
|
| 断点态读当前过程的参数与局部变量(名字来自源码,值来自引擎) |
|
| 在 IDE 立即上下文求值(显式安全契约;对象/数组表达式先拒绝) |
|
| 从 IDE 启动当前工程(可设环境变量 / 命令行参数;两段式) |
|
| 读未处理错误的上下文:对话框正文 + 错误号 + 调用栈 + 局部变量名字 |
|
9. 追踪、调用栈与进程内执行
工具 | 能干什么 | 关键参数 |
| 在过程入口插 McpTrace 调用,缺模块自动生成;可选 |
|
| 移除注入的追踪块,自动生成的模块保留 |
|
| 读 McpTrace JSONL 采集文件:按 id/pid/前缀过滤、字节游标续读、严格 UTF-8、只读零写盘(采集侧 |
|
| 轮询 IDE 变更日志:组件与控件增删改名 |
|
| 读引擎自己的调用栈,逐帧给出过程名 |
|
| 进程内探 VBA6 调试引擎:模式、调用栈、立即执行 |
|
| 给出探针的有序执行计划与前置条件 | - |
| 在立即上下文求值一个表达式并取回结果 |
|
| 在立即上下文执行语句并取回打印输出 |
|
10. 窗口、对话框与 UI 自动化
工具 | 能干什么 | 关键参数 |
| 枚举 IDE 窗口:标题、可见性、位置与大小 | - |
| 操作单个 IDE 窗口:聚焦、关闭、显隐、移动、缩放 |
|
| 递归枚举本进程窗口,按关键字或类名过滤 |
|
| 读窗口及子树的可见文本 |
|
| 列出阻塞中对话框及其按钮,只读不点 |
|
| 只点安全按钮关掉阻塞对话框,可预演 |
|
| 向本进程窗口发消息:点击、按键或原始消息号 |
|
| 列出或执行 IDE 菜单命令,按名称或 id |
|
11. 设计器与控件
工具 | 能干什么 | 关键参数 |
| 窗体设计器:控件清单、属性、选区与剪贴板 |
|
| 按 ProgID 在窗体添加控件,先校验已注册 |
|
| 按名称或序号从窗体移除控件 |
|
| 设置控件的设计期属性 |
|
| 列出工程注册的 ActiveX 控件部件与解析路径 | - |
12. 引用、成员与属性
工具 | 能干什么 | 关键参数 |
| 按 GUID 或文件路径添加类型库引用 |
|
| 按名称或 GUID 移除工程引用 |
|
| 读组件设计期属性,即属性窗口内容 |
|
| 设置组件属性,支持多维索引属性 |
|
| 列出类组件的公开成员及代码位置 |
|
| 改类成员属性:说明、Hidden、Category 等 |
|
| 读写工程或组件的属性包键值 |
|
13. 磁盘文件与工程元数据
工具 | 能干什么 | 关键参数 |
| 按通配符递归列出目录下的文件 |
|
| 跨文件文本或正则搜索,回文件名与行号 |
|
| 读写 .vbp 版本资源与构建元数据 |
|
14. 快照、回滚与测试宿主
工具 | 能干什么 | 关键参数 |
| 工程级快照与回滚,带逐文件清单 |
|
| 组件级单步撤销,只恢复窗格文本不动磁盘 |
|
| 生成离线测试宿主 .vbp,或统计 PASS/FAIL |
|
| 以原始 ANSI 字节记录回归夹具 |
|
| 与夹具比对:相同、新增、删除、首个差异行 |
|
15. 扩展库
扩展库
mcp_extensions/:每个*.json是一个声明式工具包(name/tools[].inputSchema/tools[].handler)。 启用的包其工具自动并入tools/list并由CodeTools.CallTool兜底分发;state.json记录禁用列表(默认启用)。 处理器只开放少量安全原语(http_get/read_file/write_file),不从磁盘加载 VB6 代码。
工具 | 能干什么 | 关键参数 |
| 列出扩展包及其启用状态与工具数 | - |
| 启用扩展包,其工具并入 tools/list |
|
| 禁用扩展包,其工具从 tools/list 消失 |
|
| 重扫 mcp_extensions 并报告包与工具数 | - |
| 把扩展 JSON 安装进 mcp_extensions |
|
| 移除扩展包,文件改名保留 |
|
16. 治理、自检与 IDE 环境
工具 | 能干什么 | 关键参数 |
| 读服务器版本戳、契约、工具数与构建哈希; | - |
| 即时读 IDE 模式与在飞、取消、释放闸门状态( |
|
| 测量宿主进程桌面与可交互性,且不抢焦点 | - |
| 开或关安静模式:不抢前台,回执附提示 |
|
| 只读扫描仓库内的任务板与报告 | - |
| 读最近的工具调用审计条目 |
|
| 列出已注册插件及其连接状态 | - |
| 加载或卸载一个 IDE 插件 |
|
| 退出 IDE,响应后延迟执行,未保存会丢 | - |
17. hub 面:IDE 实例管理与经验库(11 个,在 IDE 进程之外运行)
这 11 个工具由 MCP hub(bridge/vb6mcp_hub.mjs)提供,在 IDE 进程之外运行:客户端不必先加载插件。其中 ide_list、ide_target 与 5 个经验库工具不依赖任何 IDE(经验库只读写本地 JSON);ide_stop / ide_restart 需要一个目标实例,ide_start 需要能启动 VB6 的条件。逐项清单与两个拒绝码的前置条件见「二、连接模型」里的「哪些工具免 IDE、哪些必须有活 IDE」。
经验库
mcp_lessons/:defaults.json(随包发布,勿改)+user.json(用户层,按 id 覆盖/禁用/新增)。 合并结果经initialize.instructions发布给客户端,agent 调工具前即可读到;add/delete/import写user.json时带.bak备份。IDE 实例管理:
ide_list列出 hub 能寻址的全部 VB6 IDE:本机实例(含同一工程被两个实例打开的冲突),ide_target记住默认目标,ide_start负责启动;ide_stop/ide_restart只操作本 hub 自己启动的实例;同时列出已配置的远端端点(只探白名单、绝不自动发现;连不上也照样列出并带探测状态),并回显实例的唯一身份(endpoint / targetKey / pathVia)、真实路径与登记表拼写、以及解析到同一身份的调用者别名(junction / 别名写法)。
工具 | 能干什么 | 关键参数 |
| 列出 hub 能寻址的全部 VB6 IDE:本机实例 + 已配置的远端端点(只探白名单、绝不自动发现;连不上也列出并带探测状态);回报 pid/端口/工程、唯一身份(endpoint / targetKey / pathVia)、真实路径与登记表拼写,并回显解析到同一身份的调用者别名;含过期登记项与「同一身份两个实例」冲突 | - |
| 设置或清除默认 IDE 目标:pid、工程名或 auto |
|
| 按 .vbp 启动一个 VB6 IDE 实例,并轮询到插件就绪( |
|
| 停止本 hub 启动的 IDE 实例(需要一个目标实例) |
|
| 重启卡死的自有 IDE 实例并证明已就绪(需要一个目标实例);长重启可 |
|
| 管道 busy/wedged 时的带外复位:只停本 hub 自有实例、清注册项、原 .vbp 重启并轮询就绪(不依赖插件管道) |
|
| 列出经验条目:场景、陷阱、做法(hub 面,无需 IDE 在线) |
|
| 新增或覆盖一条用户经验,带备份(无需 IDE 在线) |
|
| 删除用户经验,或禁用随包经验(无需 IDE 在线) |
|
| 把合并后的经验库导出为 Markdown(无需 IDE 在线) |
|
| 从 Markdown 导入经验,可只解析不写(无需 IDE 在线) |
|
get_codepane/set_codepane没有view参数:VB6 的可扩展模型把代码窗格的视图模式 (CodePaneView)定为只读、命令栏里也没有视图切换项,插件无法在 full_module / procedure 之间切换; 传入view会被记入ignoredArgs。
完整对象模型清单与差距分析见 内部 VBIDE 对象模型笔记(由 VB6EXT.OLB 5.3
类型库二进制解析生成,原始数据见 内部类型库转储)。
五、两条修改代码的路径(重要)
replace_code(推荐,作用于 IDE 内存):修改立即生效于 IDE 中的代码, 组件被标记为未保存;之后用save_project落盘。write_file(写磁盘):适合生成新文件或非 VB 文件。若目标是工程内已被 IDE 打开的文件,用reload_component从磁盘重载即可生效(无需重启 IDE)。
六、编码说明
VB6 源码文件(
.bas/.frm/.cls/.vbp/...)按 ANSI(系统代码页,如 GBK) 读写; 其它文件默认 UTF-8。write_file可用encoding参数显式指定auto / utf-8 / ansi / utf-16。MCP 线上协议一律 UTF-8;桥接器
ensure_ascii=False,中文内容可正常往返。
七、已知限制
VB6 是单线程 STA:大数据量搜索时会短暂阻塞 IDE 界面(已设 2000 文件上限)。
插件是单线程:一次调用还在飞行时,任何第二次调用(包括只读)都不会被执行,而是立即收到 busy 拒绝回执;回执字段
readonly_serviceable恒为false。cancel_inflight/release_inflight只是尽力而为——陷在宿主模态循环(例如 VB6 自己的对话框)里的调用无法被中止;唯一可靠出路是等它结束,或从外部终止 VB6 进程。超时参数不是强制预算:
build/make_project的timeout_ms(以及immediate_*的timeout_ms)不会在到点时中断正在执行的调用——它们只决定调用方等多久、或作为闸门年龄阈值。实测传入远小于真实耗时的值仍可成功返回(此时budgetMs可能为null)。要中止正在跑的调用,请从外部终止 VB6 进程。断点状态(break mode)下
make_project/control_execution可能失败, 工具会返回错误文本。make_project对当前活动工程执行;无法静默指定输出路径(跟随 IDE 的工程设置)。插件自身 DLL 被 IDE 占用时无法原地重建(见 FAQ)。
服务器接受连接时强制校验对端为 127.0.0.1(MSWINSCK.OCX 的 LocalIP 只读, 无法在监听前绑定回环地址,故用接受时过滤实现),无鉴权——只信任本机进程。
本地化与英文版 IDE 支持
结论:英文版(en-US)VB6 在能力上支持,但未做端到端实测(我们的验证机是中文版 zh-CN,见本节末尾)。
与语言无关的部分:菜单/调试命令按 caption 匹配、忽略本地化 bar 名(
Start/Continue/End、Toggle Breakpoint、Step Into/Over、Run To Cursor、Start With Full Compile、Clear All Breakpoints等);断点开关与调试命令的中英标题都在匹配表内;IDE 模式标记中英同集且大小写不敏感([设计]/[运行]/[Design]/[Run]);对话框按钮控件 ID 优先、中英文本兜底;运行时错误框的英文措辞(RUN-TIME ERROR/RUNTIME ERROR)已识别;注释/诊断语言界面优先(英文 IDE 得到英文注释);hub/bridge 侧只假设VB98目录名,与语言无关。仍需注意:个别流程的窗口标题匹配与少数弹窗仍以中文为基准;英文版首次使用建议先跑
mcp_info/ide_state/command_bars{action:"list"}三条只读调用,确认工具面与状态。欢迎反馈:若你在英文版(或其它语言版本)VB6 上使用,请报告 tool 名 + 回执 + IDE 界面语言——即使只是「可用/不可用」也是极有价值的贡献。
八、FAQ / 故障排查
编译报 "User-defined type not defined: IDTExtensibility2"
→ 需要 Microsoft Add-In Designer 类型库:MSADDNDR.DLL(Typelib GUID
{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4},通常位于
C:\Program Files (x86)\Common Files\designer\MSADDNDR.DLL)。
build.bat 会自动注册它(内部用 %SystemRoot%\SysWOW64\regsvr32.exe);手动注册:
"%SystemRoot%\SysWOW64\regsvr32.exe" "%CommonProgramFiles(x86)%\designer\MSADDNDR.DLL"。
编译报 "Method or data member not found"(如 Member/Reload/MakeCompiledFile)
→ 工程引用的是 Office 的 VBA 扩展性类型库({0002E157},64 个类型,缺 VB6 专属对象)。
必须在 Project → References 中勾选 Microsoft Visual Basic 6.0 Extensibility
(VB98\VB6EXT.OLB,GUID {EF404E00-EDA6-101A-8DAF-00DD010F7EBB},112 个类型)。
本工程 vbp 已写死该引用;若 IDE 又改回 Office 版,请手动改回。
本机 VB6 适配说明(已内化到代码)
MSWINSCK.OCX的LocalIP只读:监听绑定全部本机接口,接受连接时校验对端为 127.0.0.1; 若 OCX 未注册,build.bat前先"%SystemRoot%\SysWOW64\regsvr32.exe" "%SystemRoot%\SysWOW64\MSWINSCK.OCX"★32 位控件的注册必须用「本机」%SystemRoot%\SysWOW64\regsvr32.exe,绝不硬编码C:\Windows: 在部分机器上SystemRoot不是C:\Windows,而C:\Windows可能是一份陈旧的第二安装 ⇒ 用它会让 任何 32 位 DLL 注册失败(0xC0000139)且/s静默(build.bat已用%REGSVR%显式指向本机的 SysWOW64)。 (或在工程目录放一份 OCX 文件)。VB6EXT 中
CodePane.CodePaneView、Window.Caption为只读,相应设置请求会被忽略并说明。VBProjects.StartProject只有 Put 访问器,需用无 Set 的对象赋值。VB6 保留字坑:过程不能叫
Stop,参数不能叫attribute;数组参数必须 ByRef。
编译报 "procedure declaration does not match"(IDTExtensibility2_OnConnection)
→ Connect.cls 已按类型库原生声明使用 ext_ConnectMode / ext_DisconnectMode
(来自 Add-In Designer)。若个别环境的类型库版本不同,可改为
vbext_ConnectMode / vbext_DisconnectMode(来自 VB6 Extensibility)。
加载时报 MSWINSCK.OCX 缺失
→ 从 VB6 安装源找到 MSWINSCK.OCX 放到 %SystemRoot%\SysWOW64 并
"%SystemRoot%\SysWOW64\regsvr32.exe" MSWINSCK.OCX(★不要硬编码 C:\Windows,理由见上文)。
桥接器报 "VB6 MCP server unreachable"
→ 确认 VB6 IDE 已打开且插件已加载(Add-Ins → VB6 MCP Status... 查看端口),
端口与 %TEMP%\vb6mcp_port.txt 一致;防火墙若拦截本机回环,放行 python。
写文件后 IDE 里没变化 → 属于预期行为,见第五节;执行 File → Reload Project。
想在未打开 VB6 IDE 时也能用 → 不行:本插件依赖 VBIDE 对象模型,必须随 IDE 运行。日常做法是让 VB6 常驻后台。
Agent 正在操作 IDE 时,我能在同一台机器的 IDE 里打字吗?
→ 不要。 这不是建议而是纪律:插件驱动 IDE 时会设置光标、滚动、打开窗格,有的动作还会把窗口提到前台
(control_window{action:"activate"};native_breakpoint 会先把光标移到目标行)。这时你的按键会落到「当时前台的那个窗口」——
本项目实测过一次真实事故:用户在 Agent 操作期间打字,击键落进了代码窗,源码里多出一行游离字符
(那是击键写进去的,不是工具写的)。要在同一台机器上手工编辑,请先让 Agent 停手,或用另一台机器 / 另一个实例。
写类工具为此加了护栏(回执里都能看到):
写后回读断言:编辑类工具本来就有
expected_sha256/sha256Before/sha256After;不写文本的断点与窗口类工具 (native_breakpoint/control_window/set_codepane)现在也在操作前后各取一次窗格文本的 sha256,两者必须相同: 一旦不同,回执被翻成{ok:false, reason:"paneTextMismatch"}并附逐行差异(paneDiffLines)。 窗格读不到时如实标注paneAssertion:"notApplicable",不会当成「通过」。set_member_attribute写后回读该成员属性,回执给readback与applied(★那是内存里的值;能否落成磁盘上的Attribute行由 VB6 在保存组件时决定,见 FAQ §50)。焦点安全:
native_breakpoint/control_window的回执带foregroundBefore/foregroundAfter/foregroundTookFocus, 且非 activate 动作会把前台还给原来的窗口。断点类没有第三条路(如实写在回执里):VB6 没有断点 API,Debug 菜单项只作用于光标,另一条路
set_breakpoint是写文本注入标记块。 所以native_breakpoint采用补偿:操作后把光标放回原处(cursorRestored)。breakpointWithoutCursorMove:false就是这个事实的机器可读标记。
文件里改的 .vbp 为什么没生效?open_project 现在会告诉你
→ 已打开的工程以前只回 alreadyOpen:true,读起来像「你指名的文件被加载了」,其实没有重读磁盘。现在回执区分
reloaded / keptInMemory,回显 IDE 内存里的工程属性(inMemory{}),并把 .vbp 与内存做比较(diskVsMemory{})。
两者真的不一致时一律结构化拒绝(ok:false + 结构化 reason),绝不静默忽略,也不会为了让磁盘生效而去关工程:
内存里有未保存改动 ⇒
reason:"unsaved_changes_would_be_lost";内存干净 ⇒
reason:"disk_vs_memory_mismatch";VBProject.Saved读不到 ⇒reason:"project_saved_flag_unreadable"。
★为什么不做"真重载":本机实测(2026-10-13)关掉再重开(VBProjects.Remove → AddFromFile)会抛 59999「方法 '' 作用于对象 '' 失败」,
把工作区丢成 0 个工程,紧接着再 AddFromFile 仍然失败 ⇒ 工具不会为了刷新而摧毁工作区,回执带 reloadNotAttempted:true 与说明。
要采用磁盘版本,请自己走 remove_project → open_project。
★明确不提供的能力(按设计拒绝,不是缺陷):本插件没有「把 .vbp 层面的工程属性写进 IDE 内存」或「原地重载一个已打开的工程」的通道 —— 唯一能实现后者的机制就是上面那条会毁工作区的路径,所以它从不尝试:一旦「磁盘与内存不一致」,回执就是结构化拒绝(ok:false + reason + reloadNotAttempted:true + sideEffects:"none: ..."),而不是静默 no-op。能写进 IDE 的工程选项(描述、帮助文件/上下文、启动工程、编译输出路径、活动工程、只读模式)由 set_project_options 提供;该构建做不到的(如 startup_object)回结构化的 startupObjectError + startupObjectWorkaround,不会假装成功。.vbp 层面的版本资源等键由 project_meta{action:"set"} 写文件(带 .bak 与写入后 sha256),不会注入到 IDE 内存。
coords:"disk" 的行号为什么不是连续的?
→ 因为 VBA6 会把成员 Attribute 行(形如 Attribute Foo.VB_UserMemId = -4)留在磁盘上、却不显示在代码窗格里。
磁盘行号因此是逐行累计出来的(diskLine = ideLine + headerLines + 该行之上被隐藏的 Attribute 行数),
跳过隐藏行时行号自然不连续。回执带 mappingBasis / mappingResolved / hiddenInlineAttributeLines 与
hiddenInlineAttributeDiskLines(被隐藏的磁盘行号),对齐失败时如实标 mappingResolved:false,不假装精确。
build 的 /d 宏为什么偶尔不生效?
→ 已修:仅当值里含空格才加引号(无条件加引号会让编译器把引号折进宏名,宏静默失效而构建仍报成功)。
回执给 commandLine(实际命令行)、conditionalCompileArgs(结构化的 name / value / quoted)、
conditionalCompileArgsRaw 与 conditionalCompileQuoteRule。
vba6_probe{exec_probe} 报错时,怎么区分「语句抛错」和「通道挂了」?
→ 两种原因已分开:exec_error = 插件答了(语句抛错,或 EbExecuteLine 报了 VB 错误)⇒ 回执带结构化的
exec_error{vbaErrNumber, description};forward_timeout / forward_failed = 由hub(bridge/vb6mcp_hub.mjs)报告,
含义是插件根本没答(通道挂住或超出转发预算),此时不存在插件回执,也就不可能有 exec_error。
九、协议说明
实现 MCP 核心:
initialize/ping/tools/list/tools/call/ 通知。协议版本协商:服务端支持 2025-11-25 等版本,按客户端
initialize回显其请求的版本 (未带则用最新;见McpServer.NegotiateProtocol)。传输:中间服务提供
POST /rpc与POST /mcp(streamable HTTP);请求头带Accept: text/event-stream时用 SSE 帧返回单条消息。旧代的GET /sse+POST /message长连订阅不再实现(返回 501),因此不使用服务端主动通知(notifications)——客户端按需tools/list拉取即可。鉴权:配置了凭据后所有 HTTP 面都要
Authorization: Bearer <token>或X-VB6MCP-Token;把凭据放进 URL 一律 401token_in_url_rejected。实现了
resources/list|read(vb6://project+ 每个组件的vb6://component/<name>) 与prompts/list|get(edit-checklist/release-checklist);未知 uri/prompt 回-32602。JSON-RPC 通知返回 HTTP 202 空响应;工具错误按 MCP 规范以
isError: true的 content 返回,而非 JSON-RPC error。
open_project(D5 已破案并修复,2026-09-21)
VBProjects.AddFromFile的签名是AddFromFile(PathName, Exclusive)(见内部 VBIDE 对象模型笔记§3)。 早先“VB6 该 API 只接受文件名”的说法是错的;实测两种调用形式都会抛类型不匹配(13),但工程确实被加进去了。因此现在以 IDE 的工程列表判定成功,返回值只作参考:成功时响应里会带
apiError: "0xD 类型不匹配"与一句“这是虚警、打开其实成功”的说明。exclusive: true需要当前工程可关闭。有未保存改动时 VB6 会报 60082(工程已加载), 所以现在会先落盘(响应带savedBeforeClose:true)再关闭,实测closedProjects:1后切换成功。逐步日志:
%TEMP%\vb6mcp_openproject.log;方法级 begin 标记:%TEMP%\vb6mcp_audit.log。⚠ 早先记录的“调用后 MCP 端点失联”与
open_project无关:那是 VB6 进程崩溃, 触发态是.vbp引用了磁盘上不存在的组件文件。详见内部已知限制与 FAQ§10。
hub 侧恢复原语与可靠性开关(v1.13 ~ v1.16)
这一节从用户视角说明这批能力解决什么故障、什么时候用、回执里该看哪些字。逐字段契约见
docs/CONTRACTS.md的 v1.13.0 ~ v1.16.7 版本行。
ide_kill_and_relaunch(v1.13.0,hub 侧、不依赖插件管道) —— 管道 busy / wedged 时唯一的恢复原语:结束本 hub 自有的实例、清掉它的注册项、在原.vbp上重启并轮询就绪;只停 hub 自有实例,绝不碰 8766、远端实例或别人启动的 IDE。回执看ready/threeWay{http,mcpInfo,project}/ownership;ready:false只表示"进程已拉起但插件还没应答",照howToProceed轮询ide_list,不要重试 kill。 为什么要它:插件是单线程的,管道 busy 时连ide_state{release_inflight:true}都送不进去;以前唯一出路是人工Stop-Process再重开 IDE,现在一次调用即可,而且不动别人的实例。ide_restart的wait_ms+opId(v1.14.0) —— 重启可能比客户端超时长:wait_ms限制单次调用阻塞多久,到点回pending:true + state:"in_progress" + opId;拿opId再调ide_restart{opId}只查询、不启动任何东西(操作注册表保留 30 分钟,hub 重启即忘)。同一个 hub 同一时刻只允许一个重启,进行中再调会被restart_in_progress拒绝。 为什么要它:重启实际已完成、客户端却只收到Request timed out(无回执),调用方只能去查进程;盲目重试又会二次重启。ide_restart的 content 单 JSON 文档(v1.14.0) ——content[0].text现在是一个 JSON 文档:数组/标量 body 变为{singleDocument:true, data, receipt}(空结果 =data: [],与解析失败可区分),非 JSON body 给结构化content_not_json,对象 body 维持扁平;VB6MCP_LEGACY_TEXTBODY=1可临时回到旧两段式(只保留一个版本)。传输失败且 peer 可证已死时给peerState:"peer_dead"+lostContext+howToRecover[]。 为什么要它:旧版把"JSON 数组 + 追加回执对象"拼成两段,严格客户端JSON.parse直接失败 ——find_gui在无命中([])时甚至无法区分"空结果"与"解析失败"。build/make_project的离线路由(v1.13.0) —— 没有活 IDE 时,build/make_project(route!="ide")/project_meta由 hub 自己服务:offline:true + offlineRoute:"cli"|"file" + targetResolved:false,走独立的VB6.EXE /make;不能启动编译器时回reason:"cli_unavailable"+howToProceed,不再把"没有 IDE"当成target_mismatch。0 实例时package_app/verify明确offlineServed:false + no_live_ide,不假装离线可用。 为什么要它:离线编译是唯一能拿到真编译器日志的通道;以前 IDE 一关,build仍被陈旧目标挡住,只能绕过 MCP 手跑/make。verify/verify_scope的requireCompiler(v1.16.0) —— 默认 false 时回执逐字节不变;true 时要么给出编译器权威(authority:"compiler"+errorCount+artifactChanged+compiler{...}),要么结构化拒绝compiler_not_configured+compilerProbe{...}+howToProceed,永不用 lint 顶替编译。 为什么要它:lint 只做词法与结构,曾对大模块报 49 个 error,而同一时刻/make是 0 error;把"疑似"当"编译结论"会给出与事实相反的假红。apply_edits/preview_edit多组件批量(v1.16.0,仍是同一个工具) ——edits[].component让一批编辑跨最多 40 个组件;expected_shas[]任一不符即整批拒绝、一字节不写;顺序语义 = 按edits[]顺序、每条锚点在该组件已更新文本上解析;失败全或无回滚(rolledBack+restoreVerified+restoredComponents[]);max_ops默认 200 / 上限 1000。preview_edit用同一形状按组件分组 hunks 且什么都不写。 为什么要它:跨文件改名/搬迁原来要十几次单组件调用,中间失败会留下"改了一半"的状态;多组件批量把一批编辑变成一个事务,且先 preview 能看清每处落地。breakpoints{action:"probe"}两阶段行为验证(v1.16.0) —— 唯一能回答"这个断点到底生效吗"的通道:phase:"start"(默认)检查设计态 / 无模态框 / 可断行后启动程序(不保存、不写源码),回probeId + startedPending + howToContinue;phase:"observe"用同一probeId等停、读position+callstack、End 并用回读证明stateAfter=design。probeId单次使用、TTL 600000 ms;probeResult是闭合枚举。同一版里control_execution{mode:"end"|"stop"}在 RUN 态不再假拒绝(旧的commandbars_unavailable_while_running是伪拒绝),programEnded:true只在 design 回读之后出现。 为什么要它:native 断点"记录成功但从不命中",injected 断点写盘并两次搞死 IDE;只有真的跑一次、停在目标行,才能证明它有效。run_project的 observe 预算与完成标记(v1.15.0) ——observe_max_wait_ms(默认timeout_ms,夹取 1000..120000)到点返回:state是封闭五值done-by-marker | exited | break | error | timeout;done_marker{path,contains}可在 IDE 机器(允许 UNC)上等一个文件标记,只读前 65536 字节、不写文件。★timeout是ok:true + timeout:true + retrySafe:true + suggestedAction:"observe_again"(不是失败);break 态 observe 明确拒绝(observe_in_break_not_supported);IDE 自己的查找框会被分类成observe_blocked_by_ide_dialog,不再误报成程序错误。 为什么要它:回放 7 秒就结束,工具却报Request timed out并把管道留在 busy;完成标记让"已经跑完"可判定,而不是靠外部轮询。get_locals{allowUnsafeInBreak}(v1.15.0)与last_error结构化错误(v1.16.3) —— break 态get_locals默认拒绝(break_eval_requires_opt_in);true是知情同意,不保证不挂,恢复走 hub 的ide_kill_and_relaunch。last_error(以及ide_state.lastError)在错误框仍然存在时新增只读结构化字段err{number,description,source,hresult,numberBasis}/errBasis/errChannel{...}/errCrossCheck/errLocation{...};它不抑制、不自动关闭、不无框读取错误框,读不到的字段是 null + 原因,绝不猜。 为什么要它:break 态读局部变量可能把单线程管道 wedged,必须让调用方显式选择并知道恢复路径;错误框的本地化文本以前是唯一来源,读不到就只能外部截图。quit_ide的exitWrites(v1.16.4) ——requested:true的回执新增exitWrites{declaration,observed,condition,evidence,basis}:它是声明,不是测量(进程无法观测自身退出阶段)。condition只按当前可见工程数判定:0 个工程 ⇒no_project_observed_no_write(实测未写);≥2 个工程 ⇒project_group_context_then_clean_quit(约 250 ms 后出现 50~53 B 的.vbg);恰好 1 个 ⇒undetermined(两个实测形状相反)。sideEffects.filesWritten只覆盖调用期间,不覆盖退出阶段。 为什么要它:IDE 退出阶段可能写.vbg,而工具既不能确认也不能阻止;与其给一个"没写"的假保证,不如把能观察到的条件、证据和不确定都写出来。
以上能力都不改变既有默认行为(不传新参数 / 不用 probe / 不 requireCompiler 时,回执与旧版一致);契约与冻结面变化见
docs/CONTRACTS.md。
modMcpTrace 是干什么的?必须要有吗?
① 它是干什么用的:一个很小的标准模块,里面只有 McpTrace / McpTraceOn 两个过程(外加开关变量)。它只服务于 inject_trace —— 被注入的追踪调用最终落在它上面,负责把一行 JSON 写进追踪文件。
② 是否必须:VB6MCP 自身不需要它。 插件本身(编辑、检查、编译、运行、断点、弹窗、窗体、快照、打包等)与它完全无关;没有用过 inject_trace、也没有保留注入调用的工程里本来就不该有它。
③ 只有什么情况需要:只有当你用过 inject_trace 并且保留了那些注入调用时才需要 —— 注入块调用的 McpTrace / McpTraceOn 就定义在它里面。★保留注入调用并编译/发布 EXE 或 DLL 时,必须把该模块一起编进去(回执里的 shipNote 同样说明)。
④ MCP 怎么添加它:inject_trace 发现目标工程里没有 modMcpTrace 时,会自己按 IDE 语言生成并附加(模块源码内嵌在 VB6MCP.dll 里,免手工导入),附加后还会复验它确实在工程里;模块已存在则直接跳过(幂等)。回执带 moduleAutoAttached / moduleAttachAction / moduleAttachMs;附加失败时带 moduleAttachError(失败一律如实报告,不静默降级)。
注释语言怎么选(界面优先):先看 IDE 自己的界面语言(主窗口菜单文案 文件/编辑/视图/帮助,其次标题里的模式标记
[设计]/[运行])——能可靠读出就用它;读不出来再回退到 VB6.EXE 的版本资源语言 ID(\VarFileInfo\Translation,主语言中文 LANG_CHINESE ⇒ 中文);两者都拿不到 ⇒ 英文(英文在任何代码页都可读,中文在非中文系统会乱码)。回执里并排给出三个信号:ideLanguage(版本资源)·ideMenuLanguage(界面)·commentLanguage(最终选择),以及各自的*Source依据。
⑤ 什么情况会添加/移除它的「引用」:
添加:首次注入且工程内没有该模块时(见 ④)。
移除:新版不再自动移除。
remove_trace只删除该 id 的注入块,modMcpTrace一律保留;回执如实给出moduleRemoved:false·moduleLeftInPlace:true·moduleRemoveReason:"kept_by_design"(工程里根本没有该模块时为module_absent),并由moduleRemoveNote写明下面两条理由与手动移除指引。旧行为已废除(及原因):早期版本曾让
remove_trace顺带删除带自动生成标记的modMcpTrace(moduleRemoveReason:"removed_autogen_module")。该分支已删除,因为:(a) 用户可能在自动生成的模块里加了自己的代码,删模块会把那些代码一起毁掉;(b) 工程别处可能仍有@mcp-trace注入块,模块一旦被删,那些调用找不到McpTrace/McpTraceOn,工程将编译不过。需要手动清理时:先确认全工程没有
@mcp-trace残留,再自行调用remove_component{name:"modMcpTrace"}。注意VBComponents.Remove只从工程里移除组件,不会删除磁盘上的 .bas 文件(回执用moduleFileOnDisk如实标注它绑定在哪)。日志落点:追踪由 VB6 进程的环境变量控制 ——
MCP_TRACE_ON=1或MCP_TRACE_FILE=<路径>打开;默认写%TEMP%\mcp_trace.jsonl。写失败记到MCP_TRACE_ERR_FILE(默认%TEMP%\mcp_trace_err.log),不会静默吞掉。其它工具都不需要它:编辑、检查、编译、运行、断点、弹窗、窗体、快照、打包,全部与它无关。
编译产物默认带上 LAA(大地址感知)——make_project / build
make_project 与 build 在编译成功后由插件自己改写产物的 PE 头,置 IMAGE_FILE_LARGE_ADDRESS_AWARE(0x0020)——即 EditBin /LARGEADDRESSAWARE 的等价物,不依赖外部 EditBin.exe(用户机器不一定有它)。开关参数是 laa(两个工具都接受),默认开。
谁做决定(优先级从高到低):
本次调用的
laa参数(laa:true|false)——传了就由它决定(laaSource:"param");工程
.vbp的[VB6MCP]命名节(LAA=1,可选LAANote=<说明>;键名大小写不敏感,1/true/yes/on与0/false/no/off都认)——laaSource:"vb-project";工程目录旁的
.vb6mcp.json({"laa": true|false, "laaNote": "..."})——laaSource:"project-file";环境变量
VB6MCP_LAA——laaSource:"env";都没有 ⇒ 全局默认(开)——
laaSource:"global-default"。
回执:
laaSource/laaProjectFile(旁挂文件路径)/laaBefore/laaAfter/laaSet,另附laaSourceNote与laa{}明细;产物已带该标志时幂等(laaSet:false、laaAlready:true,一个字节都不写)。配置写错不会被静默吞掉:
[VB6MCP]里LAA=的值非法,或旁挂文件不是合法 JSON 对象 / 没有laa键 /laa不是布尔 ⇒ LAA 步骤被跳过并回laaConfigError(此时laaSource为vb-project-error/project-file-error),不会悄悄退回默认值。两条护栏(都拒绝写盘并给出原因):
signed_image:产物带 Authenticode 证书表(IMAGE_DIRECTORY_ENTRY_SECURITY非空)⇒ 拒绝改写,因为改 PE 头会让签名失效。正确顺序是先打标志、后签名(签名必须是最后一步);确实要非 LAA 产物就显式传laa:false。own_addin_dll:目标是插件自己的VB6MCP.dll时故意不打(ActiveX DLL 的 LAA 由宿主进程的地址空间决定,打在 DLL 上本身没有意义)。
写失败或复读校验不过 ⇒ 把原特性字回写(回滚),产物保持不变;只有在复读确认新值之后才报
laaSet:true(laaBytesPatched:2)。laaMachineEffective:该标志只在 64 位 Windows(或 32 位 Windows 开/3GB)上才真的扩大 32 位进程的用户地址空间;否则回执如实标注它在本机不生效。运维要点(重要):
[VB6MCP]节必须在工程在 IDE 里关闭时写进.vbp。工程打开期间从外部追加的节不在 IDE 的内存工程模型里,会被 VB6 那一次保存工程覆盖掉;而打开时就已经在的节,此后每次保存都会原样保留。需要随时可写,就改用旁挂.vb6mcp.json(VB6 只重写.vbp,不碰它)。
编译说成功却没产出?先看 unbuiltReasonUnknown,再用 diagnose:true 拿真编译日志
IDE 路由(默认
route:"ide")没有编译器子进程:VBProject.MakeCompiledFile只回报成功/失败,所以这条路由结构上就写不出/out日志 ⇒ 回执带logAvailable:false与logUnavailableReason/diagnosisUnavailableReason;exitCode:0只表示「没有编译器子进程跑过」,不是「编译器没有发现错误」。compiled:true不是证据:回执永远给compiled_means、artifactChanged、verdict与output{exists,size,mtime,sha256}。当compiled:true而artifactChanged:false时,回执会自动置unbuiltReasonUnknown:true,并写明未产出、原因未知、可能编译失败——绝不粉饰成成功。diagnose:true(也会自动运行):把.vbp及其组件文件复制到临时目录,对副本跑隔离的VB6.EXE /make /out <log>,回真日志 +firstError{file,line,text},来源标为firstErrorSource:"isolated_copy_make";明细在diagnosis{}(route:"isolated_copy_make"、tmpDir、copiedFiles、missingFiles、errorCount、artifactExists等)。在「compiled=true但artifactChanged=false」时它会自动运行,因为那条判决否则与编译失败不可区分。副本反映的是磁盘上的状态,所以未保存的窗格改动不在副本里。
已知边界:另一个 VB6 实例正持有同一个
.vbp时,隔离/make会退出 0 且不写日志——先关掉那个实例,或改对工程副本编译,再重试。
编译失败时还会尝试读 VB6 自己的编译错误框(
firstErrorSource:"ide_compile_error_dialog";errorCount:1只表示「看到过一个框」,不是「恰好一个错误」);读不到就如实说读不到。project_meta{action:"set", cond_comp:""}——清空宏是被接受的(生产构建要清掉测试宏):参数存在就触发写入,空串不等于「没给字段」。
断点(break)模式下,代码窗滚轮也能用了
插件自带的代码窗滚轮(默认关闭,可在 MCP 设置界面里开启)在中断态下也按窗格工作:消息落在哪个代码窗格就滚哪个。
以前在断点态失效的根因:
VBE.CodePanes在断点态可能返回空表(调试器打开的那个模块窗格在设计期从未存在过),于是每个VbaWindow都被判成not_a_code_pane、从不挂钩 ⇒ 滚轮消息根本进不来。现在只要曾经确认识别过一个代码窗格,空表时仍会挂钩;这次表是从哪来的由captionsSource如实给出(all/active_fallback/empty)。失败原因如实分流(不再共用一句含糊的失败):
lastTopStatus:"pane_not_found":标题没能解析成 CodePane ⇒ 滚轮原样转发,什么都不滚;lastTopStatus:"pane_not_writable_in_break":窗格解析到了,但 IDE 拒绝了TopLine的读/写(断点态实测)⇒ 回执带真实 VB 错误(lastTopError,形如「VB error N (…)」),并把这次滚动排队,由定时器在窗口过程(WndProc)之外重试(pendingScroll/pendingScrollResult)。
观测字段:
lastResolveVia(caption/component/active_name/none,这次靠哪条通道找到窗格)、lastTopStatus、lastTopError、captionsSource/captionsCount,以及原有的lastTopLineBefore/After、handled/forwarded、msgSeen等(都在ide_state与mcp_info的wheel{}里)。立即 / 监视 / 局部变量窗不会被误映射:它们与代码窗格共用窗口类,但唯一那条「映射到活动窗格」的兜底要求窗口标题里确实含有该窗格的组件名,因此这三个非代码窗口结构上不可能被当作代码窗格去滚(只会出现在
skipped[]里)。lastTopLineBefore/After是 VBIDECodePane.TopLine(1 基);-1表示「没有值」,不是第 0 行、也不是「没有滚动」——原因看lastTopStatus/lastTopError。要看当前行请读paneTopLineNow(活动窗格的TopLine)。
工具回执的失败面与行号口径(v1.11.6 ~ v1.11.7)
这一代给「没做成的事必须说出来」补了几处此前会把失败静默掉的地方。
未声明的参数名一律拒绝:
export_public_api/check_public_api/validate_code/check_component先查参数名,未声明 ⇒ok:false+reason:"unknown_argument"+unknownArguments[]+acceptedArguments[](_target是传输上下文,恒接受)。整个工具面做同一件事之后,才不会有「这个工具查、那个工具不查」的盲区;reason还细分unknown_argument/component_not_found/target_has_no_project/ide_not_ready(旧值保留在reasonLegacy)。比对永不跳过:
check_public_api只要给了name就永远比对,membersOnly只影响输出大小;给了一个不是规格文本的baseline⇒ok:false+reason:"baseline_unusable"(不再回「没有差异」)。组件级与整工程级两个入口回同一组comparedAgainstBaseline/baselineLines;check_component也接受可选baseline。写文件回读:
write_file/write_source回执带createdDirs[]/createdDirsCount/exists/size;父目录建不出来 ⇒reason:"parent_dir_missing"(missingPath+reasonLegacy:"parent_not_creatable"),不再返回一个「写了多少字节」的自洽成功形状。分页有终点:
find_in_component回hits[]/count/nextStartLine;走到末尾 ⇒found:false+reason:"no_more_hits",不回绕(连续取页不会又把前面的行交回来)。隐藏 Attribute 行单列:
verify对「只差隐藏 Attribute 行」的面板/文件差异给attributeOnlyDiff/attributeLineCount,与真实源码差异分开计,比较时跳过这些行。注意:经 VBIDESaveAs保存会把隐藏 Attribute 行物化到磁盘。两个行号空间:
get_component_code{proc|symbol}的范围字段是 IDE 行,另给startLineDisk/endLineDisk;add_code的落点由写后回读量出,给出insertedAt{ide,disk}与inDeclarationArea(真正的声明区:insertedAt.ide小于第一个过程头行;末尾追加为false)。范围形状自带模式:
requestedRange每个形状都带mode——range(replace_code/add_code/ 守卫拒绝)、anchor(edit_by_anchor)、anchor-batch(apply_edits),调用方一段代码即可判定。返回信封:
result.content[0].text是一个 JSON 字符串(这一条已写入契约)。
verify_scope 的计数口径:UNKNOWN 不是 fail;build 证据是【项目级】
UNKNOWN 单列:
verify_scope的三个桶互斥 ——pass/fail/unknown,且不变式pass + fail + unknown == checked恒成立(回执带countsConsistent);failures[]只装已证实的 IDE 与磁盘不一致。unknown的含义是「没有足够证据」,不是「不一致」。build 证据按【项目】判定:
buildOk/buildSource/buildNote来自项目级 build 缓存(key 与 hash 都是项目级),单个组件不可能「仅自己缺 build 证据」。因此unknown是项目级结论,pass只在有新鲜 build 缓存的工程上可达 —— 从未构建过的工程里,任何 IDE 与磁盘一致的组件都会是unknown。为什么值得单独写:把 UNKNOWN 当 fail,会让一次「没测到」看起来像一次「不一致」;把项目级证据当组件级用,会读出一个不可能的结论。两种误读都会让调用方去修一个不存在的问题。
传输可靠性:间歇断连与大于 64 KiB 的响应截断已修复
曾经有两个看起来像「服务偶发不稳定」的症状:间歇性的
RemoteDisconnected,以及大响应在 64/96 KiB 处被截断。它们不是环境噪声,而是三个确定性缺陷叠在一起:连接对象未被持久化 ⇒ 一条请求跨 TCP 段到达时会丢掉前半段,要等 30 s 空闲清扫才断开;
close-after-send 的判定顺序放在队列已经清空之后 ⇒
Connection: close的响应会滞留到空闲清扫才被发出;flush 的累积
Mid$起点越过 64 KiB,且send()=-1伴随WSAGetLastError()=0被误判为致命错误 ⇒ 正文被截断。
三处都已修好。工具面与参数表没有因此变化(当时插件 110 + hub 10 = 120,不变):这是一次行为修复,升级后不需要改任何调用方式。
许可证
Apache License 2.0(见 LICENSE)。可自由使用、修改、分发、商用,也允许闭源衍生; 只需保留版权与许可声明,并说明你修改了什么。
平台前提:本项目是 VB6 的插件,使用与构建都需要你自己合法的 VB6 安装;本仓库 不分发任何微软文件(见 THIRD_PARTY_NOTICES.md)。本项目与 Microsoft 无隶属关系。
关于代码注释里引用的文档
源码与注释中会提到 内部逆向记录(多篇)、内部交接说明、内部方案文档、 内部已知限制与 FAQ、内部 VBIDE 对象模型笔记、内部类型库转储、 内部命令栏清单、内部禁用路由说明 等文件。它们属于维护者的内部设计与 逆向研究记录,不随源码发布(本仓库只发布代码,以及构建与使用所需的最小集合)。这些引用不影响 编译与运行;如果你确实需要其中某一篇的内容,请开一个 issue 说明用途。
docs/tools_schema.json(工具面契约)与 tools/schema_baseline.json(冻结基线)已包含在本源码包中,
因此 tools/ 下的离线门禁脚本可以直接运行(check_schema_baseline / check_hub_alignment / check_live_tools docs/tools_schema.json);需要活 IDE 的 mcp_strict_audit 与 smoke_test 要先把 VB6 跑起来。
如何参与
见 CONTRIBUTING.zh-CN.md(构建、注册、门禁、编码规则)。
本地化:英文版 VB6 能力上已支持(菜单/调试命令按 caption 匹配、模式标记中英同集、对话框按钮控件 ID 优先、运行时错误框英文措辞已识别),但尚未做端到端实测(验证机为中文版 zh-CN)。欢迎英文版用户报告「哪条工具可用/哪条失败(附回执与 IDE 界面语言)」——即使只是「能用/不能用」也极有价值。
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
A full backend your AI coding agent can set up over MCP
Agent Replay Debugger MCP — record every agent step + deterministic replay. Step-debugger for
The project brain for AI coding agents — memory, decisions, sprints, knowledge base via MCP.
Build and publish full-stack apps from your coding agent: models, rules, pages, auth, per-app MCP.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceMCP server for TwinCAT 3 PLC development enabling AI coding agents to build, configure, deploy, and debug TwinCAT PLC projects through a standard tool interface.2-
- AlicenseNot gradedqualityBmaintenanceProvides MCP tools for AI-powered code review, research, and book writing via a fleet of coding agents.Apache 2.0
- AlicenseBqualityBmaintenanceEnables AI agents to code and debug Commodore PET software using the VICE emulator, with CLI and MCP tools for session control, screen reading, memory manipulation, and testing.441MIT
- AlicenseNot gradedqualityBmaintenanceProvides IDE-like coding capabilities for AI agents via MCP, enabling semantic code retrieval, symbol-level editing, refactoring, and navigation across many programming languages.MIT