内容
Forms MCP
[](https://github.com/aoreshkov/oracle-forms-mcp/actions/workflows/ci.yml)
[](https://github.com/aoreshkov/oracle-forms-mcp/actions/workflows/codeql.yml)
[](https://github.com/aoreshkov/oracle-forms-mcp/releases/latest)
[](LICENSE)
[](https://kotlinlang.org)
一个 [MCP](https://modelcontextprotocol.io) 服务器,用于提供Oracle Forms模块(`.fmb`表单、`.mmb`菜单、`.pll` PL/SQL库、`.olb`对象库)的内容,这些模块位于一个目录中,以便AI助手可以检查块、项目、触发器、程序单元和原始对象XML,而无需打开Forms Builder。
<!-- mcp-name: io.github.aoreshkov/oracle-forms-mcp -->
使用Kotlin多平台核心(纯`@Serializable`模型和端口)构建,顶部是JVM MCP服务器:声明式工具适配器在一个`FormsService`上,stdio和HTTP传输,以及基于指纹的磁盘缓存。Oracle工具转换馈送到流式StAX解析器,将Forms XML转换为结构化索引。
## 为什么
20世纪90年代至2000年代的Oracle Forms应用程序仍然运行着关键的业务流程,但其逻辑被锁定在只有Forms Builder才能打开的二进制`.fmb`/`.pll`模块中。这使得它们对现代AI工具不可见,并且难以审查、记录或迁移。
Oracle Forms MCP将这些模块转换为结构化、可查询的内容,以便AI助手可以:
- **了解遗留应用程序** — 在没有Forms IDE的情况下,枚举块、项目、触发器和程序单元。
- **审查和记录PL/SQL** — 直接将解码的触发器和程序单元主体拉取到模型上下文中。
- **协助现代化** — 将数十年前的业务逻辑馈送给助手,以便迁移到APEX、Java或重写,并在每个模块的源代码中进行搜索。
- **捕获和保留知识** — 允许助手在各个元素上记录注释、标签和交叉引用,这些注释、标签和交叉引用在会话和重新索引之间持久存在,构建起一个稳固的表单地图,而这个地图可能不再被完全记住。
它针对的是进行Oracle Forms **现代化、逆向工程、代码审查和文档**的开发人员和团队 — 任何需要比手动打开Forms逻辑更快读取Forms逻辑的人。
## 看看它是如何工作的
<!-- TODO: 记录一个短的asciinema/GIF演示并嵌入此处:
 (将文件放在docs/demo.gif或assets/路径下) -->
使用捆绑的`sample-forms`目录进行典型会话:
```text
你: ORDERS.fmb有什么作用?
AI → list_modules → ORDERS.fmb (NOT_CACHED),MAINMENU.mmb,UTILS.pll …
AI → fetch_module ORDERS.fmb → 转换 + 索引(2个块,3个触发器,3个程序单元)
AI → get_module_overview ORDERS → 块,触发器,LOV,记录组,窗口,画布 …
你: 显示我ORDERS块的验证逻辑。
AI → list_triggers block=ORDERS → WHEN-VALIDATE-ITEM(在ORDER_ID上),WHEN-VALIDATE-RECORD
AI → get_trigger ORDERS WHEN-VALIDATE-ITEM → 解码的PL/SQL主体
你: 哪里 else 调用了CALC_TOTAL过程?
AI → search_source "calc_total" scope=plsql → 触发器和程序单元中的命中
你: 这种验证是2010年前的遗留路径 — 记录它以便我们记住。
AI → annotate_element ORDERS trigger WHEN-VALIDATE-ITEM kind=note "遗留2010年前验证路径" → 已保存
(下一次会话) get_trigger ORDERS WHEN-VALIDATE-ITEM → 正文 + 内联存储的注释
```
## 工作原理
1. `list_modules` 扫描配置的 `--forms-dir` (非递归)并报告每个模块的缓存状态:`NOT_CACHED` 、 `CACHED` 、 `STALE` (源在磁盘上更改)或 `SOURCE_MISSING` 。
2. `fetch_module` 在缓存中生成模块的文本表单并对其进行索引:
- **`ORACLE_HOME` 已设置** — 使用Oracle工具在 `%ORACLE_HOME%\bin` 中进行转换:`frmf2xml` 用于 `.fmb`/`.mmb`/`.olbXML),`frmcmp_batch` 用于 `.pl`.pld` 文本转储)。
- **`ORACLE_HOME` 未设置** — 期望预转换的文件位于模块旁边(`orders_fmb.xml`,`dupes_fmb.xml`,`mainmenu_mmb.xml`,`objects_olb.xml`,`utils.pld`)并复制到缓存中。
3. 单次St将XML解析为索引(带有项目、触发器和解码的PL/SQL的块单元,LOV组,窗口,)。PL/SQL主体提取到 `.sql` 侧车文件中;每个元素都获得一个行范围引用,以便 `get_object_xml` 可以将其转换后的文件。
4. 其他工具读取缓存的索引。缓存基于指纹(源 修改时间 +):编辑模块将其标记为 `STALE` ,读取工具请求。
5. `annotate_element` 和 `_elements` 允许关于个别元素的信息(注释、标签、摘要、分类、交叉引用)。这保存在一个 **单独** 的存储中 — 不是派生索引 — 因此它在重新获取时存活下来,并且读取工具内联显示它。在源更改之前创建的注释被标记,从不被删除 工具
工具 | 返回|---|---|
list_modules` | Forms目录中的每个类型、大小和缓存状态 |
| `` | 转换索引一个模块等;进度通知) |
| `get_module_overview` | 名稱 + 计数后第一次调用 |
list_blocks` |基础表、项目触发器计数的| `get_block` | 一个完整的块:类型、列、、提示)+ 触发器名称 |
|_triggers`带有级别/范围的触发器;按块、项目或级别过滤(`verbosity=detailed` 添加预览) |
| `get_trigger` | 一个触发器的PL/SQL主体 `list_program_units` | 过程、函数、包规范/主体行计数 |
|_program_unit` |单元的PL/SQL `unitType` 消规范/主体| `search | 提取的PL/SQL(`plsql原始XML(`)或两者上的;通过 `offset`/`nextOffset` 分| `get_object_xml` | 任何命名XML片段 — 出口 |
### 注释
元信息助手记录 **关于元素的元信息,而不是读取 — 语、标签、分类引用关系。它保存在一个持久存储中,与派生索引(不在协议的 `_meta` 字段中)分开,因此在 `fetch_module` 重新索引时存活下来,并为后续会话提供服务。每个条目都带有其作者,并在其早于模块的当前源时标记为 `staleAgainstSource` ,因此注释永远不会被静默删除。上述读取工具(`get_module_overview`、`get_block`、`get_trigger`、`get_program_unit`、`get_object_xml`)内联显示元素的注释。
| 工具 | 执行操作 |
|---|---|
| `annotate_element` | 存储关于一个元素的注释/摘要/标签/分类 |
| `relate_elements` | 记录两个元素之间的有向交叉引用(例如,触发器 `calls` 程序单元) |
| `get_element_annotations` | 存储在元素上的注释和关系 |
| `search_annotations` | 按文本、类型或标签搜索模块的存储注释/标签/关系 |
| `remove_annotation` | 按ID删除存储的注释或关系 |
以及每个缓存模块的资源(`oracleforms://ORDERS.fmb/index`),`oracleforms://{module}/index` 和 `oracleforms://{module}/annotations` 资源模板,以及 `explain_module` 提示。
## 快速入门
需要JRE 21+。构建并安装:
```
gradlew :server:installDist
```
使用Claude Code(stdio)注册:
```
claude mcp add oracle-forms -- server/build/install/server/bin/server --forms-dir C:\path\to\forms
```
<details>
<summary><b>其他MCP客户端</b> (Claude Desktop,Cursor,VS Code)— 通过Docker镜像</summary>
发布的镜像在stdio上运行服务器,无需本地构建。将卷挂载到表单目录(复制模式:预转换的 `*_fmb.xml` / `*.pld` 文件必须位于模块旁边 — 参见 [Docker](#docker-copy-mode-only))。
**Claude Desktop** (`claude_desktop_config.json` )和 **Cursor** (`~/.cursor/mcp.json` )使用相同的形状:
```json
{
"mcpServers": {
"oracle-forms": {
"command": "docker",
"args": ["run", "-i", "--rm",
"-v", "ofmcp-cache:/home/mcp/.cache", "-v", "/path/to/forms:/forms",
"ghcr.io/aoreshkov/oracle-forms-mcp", "--forms-dir", "/forms"]
}
}
}
```
**VS Code** ( `.vscode/mcp.json` )使用 `servers` 键代替:
```json
{
"servers": {
"oracle-forms": {
"command": "docker",
"args": ["run", "-i", "--rm",
"-v", "ofmcp-cache:/home/mcp/.cache", "-v", "${workspaceFolder}/forms:/forms",
"ghcr.io/aoreshkov/oracle-forms-mcp", "--forms-dir", "/forms"]
}
}
}
```
`ofmcp-cache` 命名卷保留了解析模块的缓存和 — 更重要的是 — 持久注释存储在容器重启之间;`--rm` 删除容器但不删除命名卷。删除它,助手记录的注释/标签/关系不会在下一次运行中存活下来。参见 [Docker](#docker-copy-mode-only) 获取绑定挂载变体及其一次性 `chown` 。
更喜欢原生启动器?交换 `"command": "docker", "args": [...]` 为
`"command": "/abs/path/to/server/build/install/server/bin/server", "args": ["--forms-dir", "/abs/path/to/forms"]` 。
</details>
尝试使用捆绑的fixture,无需任何Oracle安装:
```
server --forms-dir sample-forms
```
HTTP传输:
```
server --forms-dir C:\forms --transport http --port 3000 # 端点: http://127.0.0.1:3000/mcp
```
### Docker(仅复制模式)
一个容器镜像发布到 `ghcr.io/aoreshkov/oracle-forms-mcp` 。Oracle的 `frmf2xml` / `frmcmp_batch` 二进制文件是专有的,**不** 捆绑,因此镜像仅在 **复制模式** 下工作:您挂载的模块必须已经具有其预转换的文本表单( `*_fmb.xml` / `*_mmb.xml` / `*_olb.xml` / `*.pld` )位于其旁边。对于实时 `.fmb` / `.pll` 转换,在具有Oracle Forms安装的主机(设置 `ORACLE_HOME` )上运行服务器。
```
docker run -i -v /path/to/forms:/forms ghcr.io/aoreshkov/oracle-forms-mcp --forms-dir /forms
```
**持久化缓存和注释。** 没有卷,缓存和持久注释存储在容器的可写层中,并在容器退出时被丢弃。在 `/home/mcp/.cache` 处挂载一个卷,以在运行之间保留它们:
```
docker run -i -v ofmcp-cache:/home/mcp/.cache -v /path/to/forms:/forms \
ghcr.io/aoreshkov/oracle-forms-mcp --forms-dir /forms
```
命名或匿名卷继承镜像的非根所有权(uid 10001)并且直接生效。主机 **绑定挂载** 则不 — Docker 永远不会更改目标的所有权 — 因此在主机上运行 `chown 10001 /host/cache` 一次,或将写入重定向到 `--cache-dir` / `--annotations-dir` 到容器用户可以写入的路径。
### 选项
```
--forms-dir <path> 包含Forms模块的目录(或位置传递)
--transport stdio|http 传输(默认:stdio)
--port <int> HTTP端口(默认:3000)
--allowed-host / --allowed-origin 额外的HTTP主机/源(默认仅限localhost)
--cache-dir <path> 缓存覆盖(默认:OS缓存目录 + /oracle-forms-mcp)
--annotations-dir <path> 持久注释存储(默认:<缓存目录>/annotations)
--conversion-timeout <sec> 杀死卡住的转换(默认:120)
```
## 缓存
`%LOCALAPPDATA%\oracle-forms-mcp` (Windows),`~/Library/Caches/oracle-forms-mcp` (macOS),`$XDG_CACHE_HOME/oracle-forms-mcp` (Linux)。每个模块一个目录:
```
ORDERS.fmb/
converted/orders_fmb.xml 转换后的(或复制的)文本表单
plsql/triggers/*.sql 解码的触发器体
plsql/program-unsql 解码的程序单元
plsql/menu-items/*.sql 菜命令体(菜单模块)
index 结构化索引
```
任何时候都可以安全删除;模块只是重新获取。
注释** 是此派生缓存的一部分。它们存在于单独的/` 存储中(每个模块一个 `NAME.ext.json` ,cache dir>/annotations` ,可通过 `--annotations-dir` 覆盖),因此删除模块的缓存条目 — 或重新获取它 — 会使您记录的注释、标签和关系保持完整。
## 关于Oracle工具的说明
- `frmf2xml` 将其输出写入进程工作目录;服务器运行它时的缓存目录,并传递 `OVERWRITE=YES USE_PROPERTY` 。
- 优选 `frmcmp_batch` 而非 `frmcmp` (无头);服务器传递 `Script=YES Batch=YES Logon=NO` 并用表单目录增强 `FORMS_PATH` ,以便附加库解析。
- Forms工具的退出代码不可靠;成功由输出文件存在、非空且更新时间较新来判断;失败会显示工具的输出尾部。
- `.pld` 文件可能以客户端NLS字符集写入;解析器读取UTF-8并使用windows-1252备用(相应设置`NLS_LANG`)。
## 开发
```
gradlew build # 编译 + 所有测试(无需Oracle安装)
gradlew updateKotlinAbi # 在公共API更改后刷新ABI转储(core/api/*.api)
gradlew :server:run --args="--forms-dir sample-forms"