lu-query:终于完成的 Oracle MCP 服务器
这个项目我拖了几个月。今年 7 月 8 日我在 GitHub 上建了个仓库,叫 mcp-oracle-db,Java 写的,目标很明确:给 AI 代理一个能连上 Oracle 的 MCP 服务器。
然后就没有然后了。那个仓库现在是空的。
上周,10 月 17 号晚上,我决定不再修补它。用 TypeScript 从头写,18 号晚上发到 npm。28 小时里发了 8 个版本。
所以这篇不是产品公告。我想回答的是一个小得多的问题:同一个东西,为什么第一次死了,第二次活了。
第一次为什么会死
我不想把它讲成一个技术选型的英雄故事,因为那不是真的。
真正的原因是:那个 Java 原型从来没有面对过任何一个真实的反馈。我写了一些代码,写完之后没有任何一件事逼着我把它写完。没有人在用,没有真实的 Oracle 库在等着被查,没有一次半夜的调用把它打挂。一个没人用的项目,光靠热情是撑不下去的。
这不是 Java 的问题。换成 Python 一样会死。
第二次换掉了什么
技术选择里最省事的一条,是 oracledb 的 thin 模式。不需要 Oracle Instant Client,不需要 Docker,不需要 LD_LIBRARY_PATH,不需要 sqlnet.ora。装好依赖就能连。这条我认为是整个项目能不能推进下去的分水岭。不是那 75 个 tool,是「能不能在我自己的机器上跑起来」。
外面是 75 个 tool、4 个 resource、9 个 prompt。75 个拆开看:1 个系统、2 个查询、3 个 DML、7 个 DDL、8 个核心元数据、21 个目录、7 个优化器统计、2 个 PL/SQL、4 个 explain 与调优、7 个图关系、13 个跑在 V$ 视图上的性能工具。
数字看着吓人。但我自己更在意的是另一件事:所有 SQL 只走一个出口,DataAccess,在 src/db/data-access.ts。超时在那里,行数上限在那里,审计在那里,commit 在那里。加一个新 tool 的时候,我不需要重新想一遍安全策略,因为它没有别的路可走。
规模上:35 个文件,7,009 行 TypeScript;235 个测试文件;219 个对着真实 Oracle 的集成测试;CI 跑 3 个操作系统乘 Node 22、24、26。现在是 2.18.0,11 个 release,156 个 commit,MIT 协议。
另外还有 10 个 skill,放在 skills/ 目录里,是写给代理看的说明书,不是给我的。
我喜欢的
- 一个收口。 七十五个 tool 全部经过
DataAccess。这是整个设计里我最不后悔的一个决定。 - 只做 Oracle。 要求 12c 以上,我实际测的是 Oracle Database 26ai Free 23.26.3。不做兼容层,不猜方言。对一个专用的东西来说,专注比通用值钱。
- 性能是用 V$ 讲的,不是用嘴讲的。 13 个 tool 直接去查
list_blocked_sessions、get_wait_events、list_top_sql。代理想回答「现在谁在阻塞」,它有数据,不需要猜。 - benchmark 那个 skill,一个新 tool 都没加。 我逐条对着真库验证,发现它完全能用已有的 tool 拼出来。验证过再写文档,比写完文档再假设它能用要靠得住。
- 审计是按会话写成 Markdown 的,不是 JSON。 我可以用 grep 找到上周某个具体的 SQL。42 个真实的审计文件就在
mcp-audit-logs/里。
还没做完的部分
下面这些是真的缺口,不是谦虚。
node-sql-parser 没有 Oracle 方言。它按 PostgreSQL 解析,MERGE 会变成 UNKNOWN。我没有假装能绕过去,我在文档里把这个洞写清楚了。
thin 模式不自动提交,也没有 stmtTimeout。我只能用 Promise.race 加 breakExecution 去模拟一个超时。这是模拟,不是原生的,这点我不想含糊。
EXPLAIN PLAN 会污染会话,必须包在 PRAGMA AUTONOMOUS_TRANSACTION 里才能安全用。
还有几个小事:CHANGELOG.md 停在 2.16.3,package.json 已经写到 2.18.0。npm 的 topics 里还留着 sqlite,在一个只讲 Oracle 的项目上。都是小事,但能看出我发布的时候有多粗糙。
两个把我打醒的 bug
有一个 bug,让我比那个 bug 本身更难受。
CURRENT_SCHEMA。驱动返回的列名是大写,而我的缓存按 key 存,所以缓存永远填不上,所有目录 tool 全部失败。修的时候才发现另一半:测试里的 fake 返回的是小写。所以整套测试是绿的,功能是全挂的。
测试全绿,功能全挂。这句话我写在这儿,是因为我不想只记住「我修了个大小写问题」。真正的教训是,我的测试验证的是 fake 的行为,不是驱动真实会返回的东西。绿灯不等于它是对的,它只等于 fake 是对的。
第二个是 LOB。CLOB、NCLOB、BLOB 带着一个对连接的引用,JSON.stringify 直接抛 Converting circular structure to JSON。任何一张有 CLOB 的表,在 lu-query 里都是废的。
两个都在 2.16.3 修掉了。
安全这件事我不想含糊
Anthropic 那个作为参考的 Postgres MCP 服务器,2025-07-10 因为一个绕过了只读模式的 SQL 注入被归档了。executeautomation 的 mcp-database-server 有个至今没打补丁的绕过。2026 年 4 月一个研究,把 17 个号称只读的 SQL MCP 服务器全部绕过了,17 个。
我对着这批事故做了几件很具体的事:
- 只走 stdio,不开任何端口。信任边界在进程,不在网络。
- 标识符走白名单
^[A-Za-z][A-Za-z0-9_$#]{0,127}$,字面量全部转义。 run_query只接受 SELECT。DML 和 DDL 走各自的 tool,顺序是 preview、rollback、execute。- 凭据只从环境变量来,不落盘,不进日志。
但我也要把话说清楚:它不是防火墙。第一道屏障永远是数据库用户自己的权限。
还有一条让我不太舒服的:审计日志里存的是字面 SQL。它既是我可追溯性的来源,也是一份资产记录。谁碰过什么,都在里面。
我担心的
坦白讲,这个生态很小。Oracle 的社区 MCP 项目基本都是 0 到 2 颗星。lu-query 现在 2 颗星,第一天 725 次下载,0 个外部贡献。我知道这些数字,因为它就这么难看。
Oracle 官方自己也有 MCP 服务器,走 OAuth 2.0 加 OCI IAM,Streamable HTTP,带 NL2SQL。我的优势和劣势都很清楚。优势是我不需要云账号,不需要 IAM,没有 HTTP 暴露面,每条语句都有审计。劣势是我没有认证,没有多租户,没有 NL2SQL。如果你的场景需要这些,官方的那个更合适,我这儿给不了。
还有一个我必须说的:我没有做过任何自己测量的 benchmark。仓库里 benchmark skill 的参考文档中有一组很漂亮的毫秒数和 buffer 数,那是示例数据,不是我的测量结果。谁要拿那组数字说事,那是误读。
最后,75 个 tool 是个赌注。多不等于好。模型选错 tool 的概率是真实存在的,而且我已经看到了。
我现在的判断
如果有人问我为什么第一次死、第二次活,我不会说「因为 TypeScript 比 Java 好」。
区别在于,第二次我知道这个东西完成之后要面对什么:一个真实的数据库,一套会挂的测试,一个真的要发出去的包。第一次那个版本没有这些,它只是一个我觉得应该存在的东西。
至于收益,这个项目确实帮我把工作补上了一块,查询写得更好,性能的问题也知道从哪儿下手。它对我更像是一段关于重新开始的手记,而不是一份产品说明书。
仓库里还有一段 33 秒的短片,用 Remotion 渲染的,在 docs/lu-query-promo.mp4。
参考链接
代码里值得先看的两处:src/db/data-access.ts 是所有 SQL 的唯一出口,skills/ 是那 10 份给代理的说明书。
我们下个视频见。