Repository navigation
feat(tools): add typephp-mcp, a TypePHP-built MCP dev assistant (Windows + macOS) - #174
yangweijie wants to merge 3 commits into
Conversation
Windows was the only platform with an entry point, so the tool could neither be compiled nor tested here: build.bat is cmd-only, McpBinaryTest hard-coded typephp_mcp.exe and therefore always skipped, and the README documented only Windows paths and clients. Add build.sh. There is no tpc package directory on Unix, so it locates a host embed runtime instead (PHP_HOME/PHPX_HOME, then the newest php-builder private runtime, then vendor/swoole/phpx) and derives the link libraries from the archive: a cached private runtime carries extensions this project does not declare, and tpc does not add their system libraries for a static libphp.a (-lonig -lxml2 -lsqlite3 -lz -lresolv -lcurl -lssl -lcrypto -liconv, the last three behind keg-only brew prefixes). It also rejects a phpx older than the php::empty_string API the compiler now emits, which otherwise fails in every translation unit with a C++ error that does not point at the version gap. McpBinaryTest picks the binary by platform, accepts TYPEPHP_MCP_BINARY, and now drives the compiled artifact through both exec() paths: get_index_info's tpc+git and check_code's tpc --dry. Validation: 36 tests / 383 assertions pass with one platform-scoped skip (vswhere); all 12 tools answer byte-identically from the AOT binary and from interpreted PHP apart from check_code's uniqid() build directory; pointing PHP_HOME at a runtime without an embed library turns all three check_code assertions red together, which is the control that they own the compiler path. Linux shares the script but is not yet measured.
matyhtf
left a comment
There was a problem hiding this comment.
请求修改后再合并。
这个方向有价值,尤其是 TypePHP 约束、API 检索和编译诊断。但目前还存在错误知识注入、检索漏报、协议兼容声明不实、子进程缺少资源保护,以及原生产物验证不足的问题,具体要求见行内意见。
部署部分也请一并补齐:区分编译器资源目录与用户项目目录;修复 Windows 项目路径被冒号拆开的情况;Linux 构建支持共享库 .so,并按平台选择依赖检查命令,不能无条件调用 otool。构建脚本应遵循仓库约定:专用 CI 按实际 CPU 数量构建,本机保留单任务及资源保护。
验证要求:三平台实际构建 MCP 二进制,用官方 SDK 跑 stdio 会话,覆盖正常调用、错误调用、超时以及从仓库外启动。CI 必须明确区分“通过”和“因缺少产物而跳过”。
本次已阅读实现,并用 PHP 解释执行探针验证了任意协议版本被接受、核心符号检索漏报、文件不存在未标记工具错误,以及通知被回复等情况;未构建本 PR 的原生 MCP 二进制。
| . '2) bin mode requires a global main() with signature main() or main(int $argc, array $argv). ' | ||
| . '3) once a local is inferred as a concrete native type (int/float/bool/string/array) it cannot be reassigned to an incompatible type in the same scope; use std::any() for dynamic semantics. ' | ||
| . '4) reference arguments of dynamic/Closure calls need explicit std::ref()/toRef(); closures cannot return by reference; Closure::bind/bindTo/call are unsupported. ' | ||
| . '5) built-in classes cannot be extended (e.g. PDO) - use composition; Native Class objects are not plain Zend objects/arrays/mixed. ' |
There was a problem hiding this comment.
[P1] 请按真实继承边界修正规则,避免初始化时持续误导 AI。
TypePHP 允许继承内置类,也允许继承参与 AOT 编译的用户类;不允许继承仅在运行时定义或加载、未参与编译的用户类。这里的“built-in classes cannot be extended”以及中文规则中“不能继承内置类”均不正确。仓库已有 tests/compiler/class/class_extends.phpt 覆盖继承 ArrayObject。
请同时修正中文规则、英文摘要和相应测试,并将普通 Zend 类与 Native 类各自的约束区分清楚。规则应关联适用的编译器版本及权威出处,避免文档、规则表和初始化摘要分别维护后发生漂移。
| private function readLines(string $path): array | ||
| { | ||
| $size = filesize($path); | ||
| if (!is_int($size) || $size <= 0 || $size > self::MAX_FILE_BYTES) { |
There was a problem hiding this comment.
[P2] 不要把超过大小限制的核心文件静默当成没有匹配。
当前 PR 的 src/Translator.php 为 409,520 字节,超过 256 KiB,因此会被这里直接跳过。我调用 get_symbol("genClassPropertyInit"),得到 No symbol found,但该方法实际就在这个文件中。这会给 AI 提供错误的否定结论。
请改为流式或分段扫描,或明确返回未扫描文件、截断与覆盖范围。还应支持完整命名空间和 Class::method,并补充大文件及重名符号测试。目录递归需要真实路径去重和全局扫描预算,避免符号链接环或大型项目拖住服务;返回内容限制为 4 KB 不等于扫描成本受限。
| . ' --no-color --no-progress 2>&1'; | ||
| $output = []; | ||
| $code = -1; | ||
| exec($command, $output, $code); |
There was a problem hiding this comment.
[P1] 编译器调用需要超时、取消和整个子进程树的资源限制。
这里同步 exec(),没有超时、输出上限或可取消的进程控制。服务主循环在等待期间无法处理其他消息;检查大型项目或编译器卡住时,整个 MCP 服务都会阻塞。
请使用可控制生命周期的子进程执行方式,设置超时、输出预算和进程树内存限制,异常或超时后清理临时目录并保持服务可继续响应。如果提供真正的编译/链接档位,本机还必须落实公共构建锁、--job 1 以及相应的 Make/CMake 并发限制,不能只在工具说明里约定。
| exec($command, $output, $code); | ||
| self::removeDir($buildDir); | ||
|
|
||
| return self::format($resolved, $code, $output); |
There was a problem hiding this comment.
[P1] 请区分检查阶段与工具执行失败,避免笼统的 OK/FAILED 文本。
--dry 只生成 C++,既不验证 C++ 是否可编译,也不验证链接。#172 中的未声明临时变量就是这种检查发现不了的错误。目前提示只突出“不检查链接”,容易让使用方误以为已经通过 C++ 编译。
请明确返回实际检查阶段与已验证阶段,建议提供结构化的 ok、stage、diagnostics,诊断包含文件、行列、严重程度和可用的诊断编号。生成、编译、链接应分别标识,后两者显式启用。
路径不存在、PHP/SDK 缺失、编译器执行异常等属于工具执行失败,应设置 isError: true。目前不存在的文件也返回普通成功结果中的文本;我已通过 tools/call 复现。用户代码被诊断为不合法则应以清晰的结构化检查结果表示,与工具自身执行失败区分。
| $clientCaps = isset($params['capabilities']) && is_array($params['capabilities']) ? $params['capabilities'] : []; | ||
| $this->clientSupportsRoots = isset($clientCaps['roots']); | ||
| return $this->result($id, [ | ||
| 'protocolVersion' => $requested !== '' ? $requested : self::PROTOCOL_VERSION, |
There was a problem hiding this comment.
[P1] 不能通过原样回显来声称支持客户端请求的任意协议版本。
我用 protocolVersion: "1900-01-01" 初始化,服务仍成功返回同一个版本。工具名称相同不代表协议版本之间没有差异。请明确实际支持的版本列表,并按所选版本的规范协商;暂时只支持旧版也可以,但不能假装支持任意新版。
请同时修正能力声明:旧版 roots 是客户端能力;新版 MCP 已弃用 roots,新接口应优先使用显式工作区参数或配置,roots 保留兼容即可。
还需要补协议负向测试:未支持版本、无效请求、错误参数和通知不回复。当前不带 id 的 tools/list 通知仍会得到回复,我已复现。
| * from the current working directory, then this constant. The constant only | ||
| * matters when the binary is started outside the repository. | ||
| */ | ||
| const TYPEPHP_MCP_DEFAULT_ROOT = 'D:\\git\\php\\aot-compiler'; |
There was a problem hiding this comment.
[P2] 发布产物不能依赖开发机的绝对路径。
这个默认值在其他机器上无效。客户端从用户项目或仓库外启动时,上级目录通常也找不到编译器仓库,服务会直接启动失败。
请通过明确配置、安装布局或产物位置定位与当前编译器版本匹配的资源,并区分“工具/文档资源目录”和“用户项目目录”。缺失依赖时给出可操作的错误信息。请补一个从仓库外启动的解释执行及原生产物测试,验证不依赖开发机路径。
| } | ||
|
|
||
| public function testStdioMcpSession(): void | ||
| { |
There was a problem hiding this comment.
[P1] CI 需要实际构建 MCP 产物,不能依靠缺产物时跳过的测试证明原生兼容。
本 PR 将测试加入 PHPUnit,但没有增加 MCP 二进制的 CI 构建步骤;这里缺少产物就跳过。因此现有绿色 CI 无法证明这批新增代码已经在各平台 AOT 编译、链接并跑通 stdio 会话。
请在 Linux、macOS、Windows 上实际构建 MCP 产物,再运行原生会话测试;该 CI 阶段缺产物应判失败。至少覆盖官方 SDK 的握手与工具调用、持续打开 stdin 时及时刷新响应、错误返回、超时恢复和仓库外启动。解释执行与 AOT 的关键结果也应保持一致。
结论
新增
tools/typephp-mcp:用 TypePHP 自己(AOT)实现的 MCP server,通过 stdio 以换行分隔的 JSON-RPC 2.0 对外提供 12 个工具,替代设计文档里原计划的 Python/FastMCP
实现——协议层、检索层、编译器 oracle 全是编译成原生二进制的 PHP 代码。
22 files changed, +3482/-1。除
tools/typephp-mcp/**外只有 3 处顺带改动(见文末)。它是什么
check_codetpc --dry验证 TypePHP 子集合法性,返回file:line诊断或 OKlist_restrictionsget_symbol/search_codefile:line+ 所在声明list_stdlib/get_example/get_stub_api.stub.phpABIsearch_docs/get_changelog/list_topics/get_build_config_helpget_index_info设计取舍(详见
tools/typephp-mcp/README.md):initialize的 instructions 与check_code/list_restrictions的 tooldescription 内置约 800 token 红线摘要,不调工具也能压住头号失败模式。
(
vendor/swoole/typephp/ composer 依赖 /project.yml)并入默认检索集;客户端不发roots 时用
TYPEPHP_MCP_PROJECT_ROOT兜底。构建
tools/typephp-mcp/build.bat(vswhere 定位vcvars64.bat,tpc 发行包路径相对脚本解析,产物
typephp_mcp.exe+ 同目录运行时 DLL)。tools/typephp-mcp/build.sh(本 PR 新增)。Unix 侧没有 tpc 发行包目录可默认,所以脚本改为定位宿主 embed 运行时:显式
PHP_HOME/PHPX_HOME→~/.typephp/php-builder/php-*/install里最新的私有运行时 → 仓库vendor/swoole/phpx,都找不到时给出可执行的下一步提示,而不是让人对着 tpc 那句
The host PHP embed library is missing猜。macOS 实测(Apple Silicon,PHP 8.5.7 + 私有 8.5.11 embed 运行时 + phpx 2.9.4)
三道只有换平台才暴露的墙,逐个实测定位:
libphp.{a,dylib}都不存在,tpc --dry在prepareRuntimeDependencies()直接 Fatal——check_code在 mac 上因此全红(报的是编译器,不是工具逻辑)。
php::empty_string,而composer.lock已不入库,本地vendor/swoole/phpx停在 v2.7.0(头文件里没有该符号)→ 16 个翻译单元全在编译期死。CI 的macos-arm64 作业躲过了是因为它 checkout phpx master。
build.sh:62现在提前校验并直接报「太老,去
composer update swoole/phpx」,不再让人对着 C++ 报错猜。libphp.a时 tpc 不按运行时实际扩展集推导系统库,需应用侧补
-lonig -lxml2 -lsqlite3 -lz -lresolv -lcurl -lssl -lcrypto -liconv(iconv/openssl 还是brew keg-only,要带
-L)。build.sh用ar -t探测归档对象后补全,不靠手抄清单。读数:
php vendor/bin/phpunit tools/typephp-mcp/tests/→ 36 用例 / 383 断言全绿,唯一 skip是
testMsvcBinDirDiscovery(vswhere 只在 Windows 存在)。typephp_mcp(Mach-O arm64,25 MB),真实 stdio 会话跑通initialize/tools/list(12) /tools/call;两条exec()通路(get_index_info的 tpc+git、check_code的 tpc --dry)在编译产物里正常。check_code里那个uniqid()临时构建目录名。PHP_HOME指向无 embed 库的目录时,3 条check_code断言(两个单测 + 产物会话各一)一起变红——这几条断言确实在管编译器通路。
McpBinaryTest原来硬编码typephp_mcp.exe,在 mac 上恒 skip;现在按平台取名并支持TYPEPHP_MCP_BINARY=<path>覆盖,另补了上面两条exec断言。复现
顺带的 3 处改动
phpunit.xml:注册TypePhpMcpTests套件(./tools/typephp-mcp/tests)。.gitignore:忽略/tools/typephp-mcp/build/;工具目录内另忽略产物名typephp_mcp(.exe)。tests/windows/clang/README.md:一处 BuildTools 路径笔误(C:→ 该机实际的D:),与本PR 主题无关,介意可以拆走。
已知边界(想请维护者定夺)
check_code只跑--dry,覆盖 C++ 生成、不覆盖链接期,输出里已明确写这句。要不要再加一条链接期档位?
STDIN/STDOUT/STDERR常量在某些项目形态下被 -O2 折叠成字面量0(
src/Parser/ConstantExpressionTrait.php的常量折叠路径),运行时fwrite()/fgets()抛TypeError。本工具用fopen('php://stdin')绕开,规避写在main.php注释里,但根因在编译器侧,另开 issue 更合适。
libphp.dylib所以躲过了,只有静态 embed 运行时才需要那一串
-l。本 PR 只在build.sh里绕过,没动src/Build/NativeBuildConfigurationTrait.php。build.sh同时按 Linux 写(uname分支只影响 libiconv 那条),Linux 未实跑验证。TYPEPHP_MCP_DEFAULT_ROOT(src/Config.php:10)是开发机绝对路径,在别的机器上永远命中不到,只在「客户端从仓库外拉起且没设
TYPEPHP_MCP_ROOT」时才暴露;是否改成按产物同目录推导,听各位意见。