关于 Oracle Forms
Oracle Forms 是 MCP 服务,可将目录中的 Forms 模块转换并索引为可查询内容,供开发团队检查遗留应用逻辑、审查代码和辅助现代化。它支持注释与元素关联,并通过缓存保留索引数据。
主要功能
- 01扫描并分页列出模块
- 02转换模块并建立结构化索引
- 03查询区块、触发器与程序单元
- 04搜索 PL/SQL 和 XML 源码
- 05保存元素注释与交叉引用
适用场景
- 01梳理遗留应用中的模块结构
- 02检查触发器和程序单元的 PL/SQL
- 03跨模块查找过程调用
- 04记录表单元素的维护知识
MCP 相关
通过 stdio 和 HTTP 传输向 MCP 客户端提供工具及资源,供其读取模块索引、源码片段和 XML。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
Oracle Forms MCP
一个 MCP 服务,用于提供目录中 Oracle Forms 模块(.fmb 表单、.mmb 菜单、.pll PL/SQL 库、.olb 对象库)的内容,让 AI 助手无需打开 Forms Builder,即可检查块、项、触发器、程序单元和原始对象 XML。
本项目基于 Kotlin Multiplatform 核心构建(纯 @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 更快阅读其逻辑的人。
看看它的实际效果
针对捆绑的 sample-forms 目录进行的典型会话:
You: What does ORDERS.fmb do?
AI → list_modules → ORDERS.fmb (NOT_CACHED), MAINMENU.mmb, UTILS.pll …
AI → fetch_module ORDERS.fmb → converted + indexed (3 blocks, 3 triggers, 3 program units)
AI → get_module_overview ORDERS → blocks, triggers, LOVs, record groups, windows, canvases …
You: Show me the validation logic on the ORDERS block.
AI → list_triggers block=ORDERS → WHEN-VALIDATE-ITEM (on ORDER_ID), WHEN-VALIDATE-RECORD
AI → get_trigger ORDERS WHEN-VALIDATE-ITEM → the decoded PL/SQL body
You: Where else is the CALC_TOTAL procedure called?
AI → search_source "calc_total" scope=plsql → hits across triggers and program units
You: That validation is the legacy pre-2010 path — note it so we remember.
AI → annotate_element ORDERS trigger WHEN-VALIDATE-ITEM kind=note "Legacy pre-2010 validation path" → saved
(next session) get_trigger ORDERS WHEN-VALIDATE-ITEM → body + the stored note inline
工作原理
list_modules会扫描配置的--forms-dir(不递归),并报告每个模块的缓存状态:NOT_CACHED、CACHED、STALE(磁盘上的源文件已更改)或SOURCE_MISSING。生产环境的 Forms 目录包含数千个模块,因此结果会经过筛选并分页:使用pattern/type/status缩小范围,通过nextCursor获取其余结果,并查看countsByStatus了解所有匹配项的整体分布。fetch_module会在缓存中生成模块的文本形式并为其建立索引:- 设置了
ORACLE_HOME— 使用%ORACLE_HOME%\bin中的 Oracle 工具转换二进制文件:对.fmb/.mmb/.olb使用frmf2xml(XML),对.pll使用frmcmp_batch(Module_Type=LIBRARY Script=YES)(生成.pld文本转储)。 - 未设置
ORACLE_HOME— 需要模块旁有预转换文件(orders_fmb.xml、dupes_fmb.xml、picker_fmb.xml、toolbar_fmb.xml、mainmenu_mmb.xml、objects_olb.xml、utils.pld),并将这些文件复制到缓存中。
- 设置了
- 单次 StAX 遍历会将 XML 解析为结构化索引(包含项的块、含解码后 PL/SQL 的触发器、程序单元、LOV、记录组、窗口、画布等)。PL/SQL 主体会提取到
.sql旁文件中;每个具名 XML 元素都会获得一个行范围引用,以便get_object_xml从转换后的文件中截取该元素。 - 其他工具会读取缓存的索引。缓存基于指纹(源文件的大小、修改时间和 sha256):编辑模块会使其标记为
STALE,读取工具会要求重新获取。 annotate_element和relate_elements允许助手将持久化元信息写回各个元素(备注、标签、摘要、分类、交叉引用)。这些信息保存在单独的存储中,而非派生索引中,因此重新获取后仍会保留,读取工具也会在相应内容中显示这些信息。源文件更改前创建的注释会被标记,绝不会被丢弃。
工具
| 工具 | 返回内容 |
|---|---|
list_modules | forms 目录中的模块,包含类型、大小和缓存状态;可按 pattern/regex、type、status 筛选,并通过 limit/cursor 分页 |
fetch_module | 转换并索引一个模块(幂等;会发送进度通知) |
get_module_overview | 每个节的名称和计数——获取模块后的首次调用(verbosity=detailed 会添加窗口和画布对象) |
list_blocks | 包含基表、项目数和触发器数的块 |
get_block | 单个块:项目的类型、属性类、提示词和触发器名称,以及主从关系(verbosity=detailed 会添加数据类型、列、画布、大小、可见/必填/LOV,以及通过每个项目的属性类解析出的 DML 属性;columns=true 会添加基表的列和未由任何项目提供的列);标记继承自父类的块/项目 |
list_triggers | 包含级别/作用域的触发器;可按块、项目或级别筛选(verbosity=detailed 会添加 PL/SQL 预览) |
get_trigger | 单个触发器的解码后 PL/SQL 正文(通过 ownerPath 或 level 区分同名触发器);resolve 会沿继承指针查找缓存中的父模块 |
list_program_units | 过程、函数、程序包规范/主体及其行数 |
get_program_unit | 单个程序单元的 PL/SQL(通过 unitType 区分规范/主体);resolve 的行为与 get_trigger 相同 |
search_source | 在一个已获取的模块中按行搜索:提取的 PL/SQL(plsql)、原始 XML(xml)或两者;通过 offset/nextOffset 分页 |
search_modules | 在所有缓存模块中执行相同的搜索——跨表单调用、共享的 :GLOBAL、继承指针;报告无法访问的模块;通过不透明的 cursor 分页 |
read_source | 按 uri(用于指定模块)或 module + file 读取缓存文件中的行范围——转换后的 XML 或提取的 PL/SQL 旁文件 |
get_object_xml | 任何命名对象的原始 XML 片段——兜底选项 |
每个工具都会在运行前根据自己的输入架构检查参数。工具不支持的参数绝不会被静默忽略:调用会失败,并一次性列出所有问题(未知参数及最可能的预期名称、缺少的参数及允许的值、枚举范围外的值),还会附上示例调用(如果该工具提供示例)。
对象的含义所在
两个 Forms 属性承载的含义比模块中的其他任何内容都多,而过去每次只能通过一次 get_object_xml 调用来获取它们。
对象的属性类通常体现其角色:Forms 项目通常会根据类的用途为其命名,因此,只有因为继承的类,一个推送按钮才是 LOV 按钮,一个文本项才是 filter 字段。get_block 会报告每个项和块的 propertyClass,并根据模块实际声明的类进行确认。
窗口的模态性决定了如何解读表单——模态窗口是对话框,因此,填充它的代码和使用结果的代码位于一次交互的两端。get_module_overview(verbosity: "detailed") 会返回两个名称列表背后的窗口和画布对象:模态性、大小、工具栏画布,以及每个画布所在的窗口。
Forms 未写入的属性会返回 null,这表示对象沿用 Forms 默认值——绝不是 false。对于具有属性类的项,这甚至不代表默认值:类会提供该值,而在真实表单中,项的大部分行为都由此决定。因此,get_block(verbosity: "detailed") 会同时返回两者——items[].dml,即项自身写入的内容;以及 effectiveDml,即应用类后的相同属性(先是项,再是类,然后是该类所基于的对象,并递归查找已获取的其他模块)。只有当整条继承链解析完成时,项才会列入 effectiveDml,因此其中的 null 确实表示 Forms 默认值。其他每个项都会列在 unresolvedItems 中,并附上原因;如果获取模块可以解决问题,还会附上需要获取的模块。propertyClasses 和结果中的 hint 会提供每个类的相同信息,并指出相应的 fetch_module 调用。items: [...] 可将上述内容限定为问题所涉及的项。
get_block(columns: true) 会添加块的数据源列(没有任何项提供的列),以及其中哪些列在数据库中是必填项——插入时如果没有触发器为其赋值,插入操作就会失败。
块的主从关系决定了可以通过什么方式查询它,因此 get_block 在所有详细程度下都会提供这些关系:block.relations 是它作为主块的关系(包含连接条件、deferred、autoQuery、deleteRecord、preventMasterlessOperations);detailOf 则是将它指定为从块的关系,并标明每个关系写入时所对应的主块。带有 preventMasterlessOperations 的从块只能通过其主块查询——这是表单强制执行的约束,而不是数据库约束,因此移植时必须自行强制执行。
围绕结果进行阅读
所有索引项都是缓存所持有文件中的行范围,而这些范围过去只会给出路径,无法解析该路径。现在,每个指向文件的结果都会附带一个 source:
"source": {
"uri": "oracleforms://ORDERS.fmb/plsql/triggers/ORDERS.ORDER_ID.WHEN-VALIDATE-ITEM.sql",
"file": "plsql/triggers/ORDERS.ORDER_ID.WHEN-VALIDATE-ITEM.sql",
"startLine": 1, "endLine": 3
}
read_source 可以读取这两种形式,因此可以查看搜索命中的上下文,也可以继续读取被截断的片段。同一批文件也可作为 MCP 资源读取——转换后的文本形式对应 oracleforms://{module}/converted,单个提取出的 PL/SQL 代码块对应 oracleforms://{module}/plsql/{category}/{name}——每个此类结果还会附带一个 resource_link 内容块,因此客户端无需解析 JSON 即可访问。读取内容同时受到行数和字符数限制;资源读取内容被截断时,返回文本会注明这一点,并指出用于继续读取的调用。
路径始终相对于缓存,URI 始终与目录结构无关:对话容器映像或通过 HTTP 通信的客户端来说,主机绝对路径毫无意义。
子类化(继承)对象
Forms 允许块、项、触发器或程序单元从另一个模块子类化而来,也可以通过对象组(通常来自 .olb)复制过来。无论哪种方式,子对象都只存储自身覆盖的内容,因此它在自己的文件中的 PL/SQL 主体确实是空的,而实际运行的代码位于父对象中。如果不加处理地直接提供,这不仅是不完整,而且是错误的——空主体看起来就像“这个按钮什么也不做”。
因此,每个包含 PL/SQL 的结果也会包含 bodySource:
bodySource | 含义 |
|---|---|
own | 主体定义在此模块中(常见情况) |
inherited | 对象是子类化而来的;此处的 text 为空,inherited 指明代码所在位置 |
resolved | 此次调用展示的是父对象的主体(resolvedFrom 指明来源) |
empty | 对象确实没有主体 |
inherited 引用使用父对象的术语,并采用工具所用的结构,因此可以直接调用——此表单中的 BAR_LIST.SELECT,在父对象中表示为 name: SELECT、ownerPath: BAR:
{
"name": "WHEN-BUTTON-PRESSED", "block": "BAR_LIST", "item": "SELECT",
"text": "", "bodySource": "inherited",
"inherited": { "module": "TOOLBAR", "file": "toolbar.fmb", "name": "SELECT", "ownerPath": "BAR" },
"hint": "… Call fetch_module(module=\"TOOLBAR.fmb\"), then get_trigger(module=\"TOOLBAR.fmb\", name=\"WHEN-BUTTON-PRESSED\", ownerPath=\"BAR.SELECT\")."
}
对于对象组成员,name 是对象自身的名称,objectGroup 则指明包含它的组——对于库文件,该组未记录在此模块中,因此不会为它提供 ownerPath。同一模块中的父对象属于属性类:它提供属性而非定义,不会隐藏任何内容,因此不会被有意地报告为继承。
get_trigger 和 get_program_unit 接受 resolve: true,可在一次调用中沿指针查找。它们只读取已缓存的模块——如果访问未缓存的模块,就意味着要转换该模块,而只读工具不能这样做;如果无法解析,则返回指针和 fetch_module 提示,而不是报错。get_block 也会以相同方式标记子类化的块和项;get_object_xml 则会将指针解析到请求的层级(Forms 会在外层所属对象上写入原始的 ParentModule 属性,因此仅凭片段无法确定它)。
跨模块追踪
关于某个表单的问题很少只涉及它本身。模态窗口由调用它的对象打开,它返回的值通过 :GLOBAL 传递,而它的工具栏则完全定义在第三个表单中——因此,search_source 只搜索指定模块,无法回答这三个问题中的任何一个:
search_modules "PICKER" → which forms call it
search_modules ":GLOBAL.picked_ref" → every module that writes or reads it
search_modules 'ParentFilename="toolbar.fmb"' scope=xml → every module that subclasses it
每个匹配项都会列出其模块(moduleSpec,即其他工具所用的 NAME.ext 字符串)、相对于缓存的文件路径、行号、代码片段,以及可打开该文件的 uri。默认情况下,匹配不区分大小写,因为 Forms 代码在同一代码库中可能会将同一个模块名写成 PICKER、picker 和 Call_Form('picker');需要精确匹配时,传入 ignoreCase: false。
搜索范围仅包括已获取的模块——访问未获取的模块就意味着要转换它,而只读工具不能这样做。因此,覆盖范围是答案的一部分,而非默认假设:
{
"query": ":GLOBAL.picked_ref",
"cachedModules": 12, "scannedModules": 12, "skippedNotCached": 3090, "skippedStale": 1,
"hint": "3090 matching module(s) are not cached and were not searched — list_modules(status=\"not_cached\") names them, and fetch_module adds one to the search. 1 cached module(s) changed on disk …"
}
如需让整个目录都可搜索,可在会话开始前预热缓存,而不是逐个调用 fetch_module:--prefetch 会将服务作为批处理任务运行,转换并索引每个匹配的模块,然后退出;它会在 stderr 上按每个模块输出一行,如果有任何模块转换失败,则退出码为 1。已是最新状态的模块只需进行指纹检查,因此重新运行会从中断处继续。
server --forms-dir /srv/forms --prefetch-all
server --forms-dir /srv/forms --prefetch ORDER --prefetch-type form
两项限制都会强制执行,且支持续传:每页最多返回 maxResults 个匹配项,并限制一次调用读取的模块数量——否则,即使查询没有匹配项,也会在作答前读取缓存中的所有已转换文件。任一限制触发时,都会设置 truncated,并返回一个 nextCursor,供后续调用连同相同参数一起传入。游标与这些参数绑定,因此不会悄无声息地继续执行另一项搜索。
注释
助手记录并关联到某个元素的元信息,而非从该元素读取的信息 —
包括语义说明、标签、分类和交叉引用关系。它们会持久化到耐久存储中,并与派生索引分开保存(不在协议的 _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 提示词。
快速开始
选择适合你的客户端的方式:Claude
Code 使用插件,Claude Desktop 使用
.mcpb 捆绑包,其他情况使用
Docker 镜像,或者选择从源码构建。该服务已发布到
官方 MCP Registry,名称为
io.github.aoreshkov/oracle-forms-mcp。
Claude Code(单命令插件)
/plugin marketplace add aoreshkov/oracle-forms-mcp
/plugin install oracle-forms@oracle-forms-mcp
Claude Code 会提示你选择 Forms 目录,然后在首次使用时将发布的服务获取并校验校验和后放入插件的数据目录 — 无需克隆、构建或编辑 JSON。执行 /reload-plugins 后即可使用这些工具;插件的 trace-form 技能会讲解上述遍历流程 — 包括提问顺序,以及容易造成误导的读数 — 从而避免从 grep 开始追踪。
需要 JDK 25+(
javac -version),因为插件的引导程序以 Java 的单文件源码模式运行。除非你将插件的java选项设置为另一个 JDK 的java可执行文件,否则它会使用PATH中的 Java。如果只有 JRE,请使用下面的.mcpb捆绑包或claude mcp add。
详情、配置选项和备用方案请参阅 插件 README。
从源码构建
构建需要 JDK 25+(如果缺少,Gradle 会自动配置),运行需要 JRE 25+。构建并安装:
gradlew :server:installDist
通过 stdio 注册到 Claude Code:
claude mcp add oracle-forms -- server/build/install/server/bin/server --forms-dir C:\path\to\forms
server 脚本会依次使用 OFMCP_JAVA_HOME 指定的 Java 安装、JAVA_HOME 指定的安装,否则使用
PATH 中的 java。因此,如果 JRE 不在 PATH 中,只需设置一个变量即可:
claude mcp add --env OFMCP_JAVA_HOME=C:\Java\jdk-25 --transport stdio oracle-forms -- server/build/install/server/bin/server --forms-dir C:\path\to\forms
Claude Desktop(一键安装包)
从最新版本下载 oracle-forms-mcp-<version>.mcpb 并打开。Claude
Desktop 会将其安装为连接器,并通过原生文件夹选择器提示你选择 Forms 目录——无需编辑 JSON,也无需本地构建。
需要 JRE 25+。 安装包包含服务,不包含 Java 运行时。它会使用连接器设置中选定的 Java 安装,否则使用
JAVA_HOME指定的安装,再否则使用PATH中的java。 MCPB 清单格式只能声明 Node 和 Python 运行时,因此无法在安装时进行检查:如果连接器无法启动,请在设置中选择 Java 安装,或在终端中确认java -version可以正常运行。
除非机器上安装了 Oracle Forms(ORACLE_HOME),可用于实时转换 .fmb/.pll,否则同样适用 Docker 中的复制模式注意事项。
其他 MCP 客户端(Cursor、VS Code、不使用安装包的 Claude Desktop)——通过 Docker 镜像
已发布的镜像可在无需本地构建的情况下通过 stdio 运行服务。将卷挂载指向你的 Forms 目录(复制模式:预先转换的 *_fmb.xml/*.pld 文件必须与模块放在一起——参见 Docker)。
Claude Desktop(claude_desktop_config.json)和 Cursor(~/.cursor/mcp.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 键:
{
"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 会移除容器,但不会移除命名卷。删除该卷后,助手记录的备注、标签和关系将无法保留到下次运行。关于绑定挂载方案及其一次性 chown,请参见 Docker。
更喜欢使用原生启动器?将 "command": "docker", "args": [...] 替换为
"command": "/abs/path/to/server/build/install/server/bin/server", "args": ["--forms-dir", "/abs/path/to/forms"]。
使用内置样例文件,无需安装 Oracle 即可试用:
server --forms-dir sample-forms
HTTP 传输:
server --forms-dir C:\forms --transport http --port 3000 # endpoint: http://127.0.0.1:3000/mcp
HTTP 传输仅监听 127.0.0.1。它本身没有身份验证,因此绑定地址决定了哪些调用方可以使用所有工具,包括会运行转换器的 fetch_module,以及注释写入操作。--host(或 OFMCP_HTTP_HOST)可将其绑定到其他地址;只要该地址不是回环地址,服务启动时就会记录警告。Host/Origin 检查可防止浏览器通过 DNS 重绑定访问服务,但无法阻止网络中的客户端自行发送 Host: 127.0.0.1。
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
具名卷或匿名卷会继承映像的非 root 所有权(uid 10001),可以直接使用。主机绑定挂载则不同——Docker 不会更改目标路径的所有权——因此请先在主机上运行一次 chown 10001 /host/cache,或者使用 --cache-dir / --annotations-dir 将写入重定向到容器用户有权写入的路径。
在容器中运行 HTTP 传输。 容器内的 loopback 是容器自身的,因此服务必须在容器中监听 0.0.0.0。将端口发布到主机的 loopback 上,使其无法从网络访问:
docker run -p 127.0.0.1:3000:3000 -v /path/to/forms:/forms ghcr.io/aoreshkov/oracle-forms-mcp \
--forms-dir /forms --transport http --host 0.0.0.0 --port 3000 # http://127.0.0.1:3000/mcp
直接使用 -p 3000:3000 会将端口发布到主机的所有网络接口,使所有能够访问主机的用户都能访问每个工具。
选项
--forms-dir <path> Directory containing the Forms modules (or pass it positionally)
--convert-command <cmd> Site-supplied converter command (with its arguments) instead of frmf2xml
--compile-command <cmd> Separate converter command for .pll libraries (0.11.0+)
--converted-dir <path> Directory the converted XML/.pld is written into (default: the cache)
--transport stdio|http Transport (default: stdio)
--host <address> HTTP bind address (default: 127.0.0.1; pass 0.0.0.0 inside a container)
--port <int> HTTP port (default: 3000)
--allowed-host / --allowed-origin Extra HTTP hosts/origins (localhost-only by default)
--cache-dir <path> Cache override (default: OS cache dir + /oracle-forms-mcp)
--annotations-dir <path> Durable annotation store (default: <cache dir>/annotations)
--conversion-timeout <sec> Kill a stuck conversion (default: 120)
--prefetch <pattern> Fetch every module whose name contains <pattern>, then exit
--prefetch-all Fetch every module in the forms directory, then exit
--prefetch-type <type> Limit --prefetch to form, menu, library or object_library
--json With a prefetch: the summary on stdout as a JSON object
--help / --version Print the full help page, or the version, and exit
server --help 是完整参考,其中列出了每个选项及其默认值和对应变量,以及它们之间的规则。--port、--allowed-host 和 --allowed-origin 需要使用 --transport http,且 --prefetch 和 --prefetch-all 不能同时使用。退出状态码 0 表示成功,1 表示预取过程中有模块失败,2 表示命令行用法错误,70 表示意外故障;发生意外故障时,堆栈跟踪会先输出到 stderr。
转换器选项和 HTTP 绑定地址也可以通过环境变量设置,供使用变量而非参数配置服务的客户端使用(docker run -e、MCP 配置中的 env 块)。如果同时设置了标志和对应变量,则始终以标志为准:
| 标志 | 变量 |
|---|---|
--convert-command | OFMCP_CONVERT_COMMAND |
--compile-command | OFMCP_COMPILE_COMMAND |
--converted-dir | OFMCP_CONVERTED_DIR |
--host | OFMCP_HTTP_HOST |
Claude Code 插件(/plugin → Oracle Forms)也提供 --convert-command 和 --converted-dir 配置;.mcpb 捆绑包(Claude Desktop 的连接器设置)和 Docker 映像的注册表条目则提供全部三个配置。无论通过哪种渠道安装,都可以将服务指向自己的转换器和输出目录,无需手动编辑 JSON。任何配置项都可以留空:空值始终视为“未配置”。
保留转换后的 XML
默认情况下,模块的转换后文本形式位于其缓存条目中。将 --converted-dir 指向你自己的目录,即可保留 XML,方便阅读、比较差异或交给其他工具处理:
server --forms-dir C:\forms --converted-dir C:\forms-xml
所有模块共用这个扁平目录,每个文件都使用 Oracle 为其命名的方式——
orders_fmb.xml、mainmenu_mmb.xml、utils.pld——因此重新获取模块时会替换对应文件,而不是不断累积副本。如果目录不存在,程序会创建它;该目录不能是 forms 目录本身(文件名会与从那里读取的预转换模块冲突);但可以是 forms 目录的子目录,因为扫描不会递归进行。
这是转换器写入的目录,而不是之后移动文件的目的地:转换器会以该目录作为工作目录运行,因此只需将写入固定位置的包装程序指向该目录即可。程序首先根据 Oracle 的命名方式(orders_fmb.xml)确定输出所属模块;对于可自由命名输出的转换器,则退而使用“本次运行写入的最新匹配文件”。运行开始前就已存在的文件——其他模块的文本形式、被终止运行留下的部分输出——绝不会被计入;共用该目录的转换操作会串行执行,包括重命名,因此不会混淆两个模块。无论转换器选择什么名称,文件都会被重命名为规范名称,而索引始终以稳定的 converted/<name> 路径记录该文件——因此无论是否设置此选项,索引都保持有效。
使用自己的转换器
如果你的网站封装了 Forms 工具——例如使用自己的环境设置、登录处理、打过补丁的
frmf2xml,或完全不同的转换器——可以将服务指向它:
server --forms-dir C:\forms --convert-command C:\tools\fmb2xml.bat
该值是完整命令行,而不只是可执行文件,因此需要自行接收参数的包装器——例如解释器、容器或兼容层——可以直接配置:
server --forms-dir /srv/forms --convert-command "/opt/forms/convert.sh --xml --quiet"
server --forms-dir C:\forms --convert-command "\"C:\tools\my tools\f2x.bat\" /nologon"
server --forms-dir /srv/forms --convert-command '["wine", "/opt/forms/frmf2xml.exe", "{}"]'
支持两种语法,且都不会交给 shell——命令会在此处拆分,并通过 argv 列表启动:
- 带引号的字符串。 按空白拆分,使用
"…"或'…'将包含空格的部分括在一起。反斜杠按字面处理,因此 Windows 路径无需重复书写反斜杠。 - JSON 数组——
["wine", "f2x.exe", "-xml"]——每个元素对应一个参数,形式与 MCP 客户端使用的command/args相同。引号处理比较麻烦时,建议使用这种形式;注意 JSON 本身要求用\\表示反斜杠。
如果该值指向一个现有文件,则会将其整体视为路径,包括其中的空格。因此,在此选项支持参数之前配置的纯路径,无需加引号也能继续使用。
模块的绝对路径会放在 {} 所在的位置;如果命令中没有 {},则会将其追加为最后一个参数,这与之前的 <command> <module> 约定相同。命令运行时的工作目录设为已转换文件所在目录——默认是该模块的缓存目录——并且应在其中写入文本格式文件。无法指定工作目录的工具会收到输出文件路径:{out} 会替换为其绝对路径(<converted dir>/orders_fmb.xml、<converted dir>/utils.pld)。{out} 只会被替换,不会被追加,因此不含它的命令运行方式与之前完全相同。这与驱动 frmf2xml 的方式一致,因此已有的包装脚本无需修改。请输出解析器可读取的相同格式:.fmb/.mmb/.olb 使用 XML,.pll 使用 .pld 转储文件。建议采用 Oracle 的 <name>_fmb.xml 命名方式,但并非必须——写入工作目录的任何 .xml(库文件则为 .pld)都会被读取。
程序本身可以是路径(绝对路径或相对于服务启动位置的路径),也可以是裸名称,后者会在 PATH 中查找。
使用 --converted-dir 时,该工作目录就是你指定的目录,因此,如果包装器会写入自己的固定位置,而不是写入其启动位置,只需将 --converted-dir 指向该位置即可。会写入自身工作目录的脚本则无论如何都无需调整。
优先级为 --convert-command → ORACLE_HOME → 复制模式,因此,即使机器上已安装 Forms,显式配置的命令仍会优先生效(.pll 库文件可以不按此顺序处理——请参阅下文)。空值视为未设置。与 ORACLE_HOME 一样,命令会在首次转换时解析并验证,而不是在启动时进行,因此过期的设置仍不会妨碍读取已缓存的模块;此时的错误会指出需要修正的标志。
输出必须是新写入的。 由于 Forms 时代的工具返回的退出代码不可靠,因此会根据输出文件来判断运行结果;早于本次运行时间的文件会被视为之前失败尝试留下的文件。使用
copy、xcopy或cp -p复制预先生成的文件会保留源文件的时间戳,并被判定为“未生成输出文件”。请改用重定向或重写(type src > out、cat src > out),或对结果执行touch。
该命令仅供运维人员配置——任何工具参数都无法选择或扩展它。工具调用方提供模块名称,该名称会在转换器接收前根据扫描到的 Forms 目录解析;命令会直接通过 argv 列表启动,而非经由 shell,因此模块路径中的内容或你自己的引号都无法变成第二条命令。
使用专属命令转换 PL/SQL 库
自 0.11.0 起。 Oracle 没有可将 .pll 转换为 XML 的工具:frmf2xml 仅接受表单、菜单和对象库,而 frmcmp 会将库转储为 .pld 文本。因此,基于 frmf2xml 构建的 --convert-command 会在处理每个库时失败。--compile-command 为 .pll 模块提供了专属命令行,语法和占位符相同,其他所有模块类型则保持原样:
server --forms-dir /srv/forms --converted-dir /srv/forms-xml --convert-command "/opt/forms/f2xml.sh {}" --compile-command "frmcmp_batch Module={} Module_Type=LIBRARY Script=YES Batch=YES Logon=NO Output_File={out}"
或者,使用 JSON 数组:
--compile-command '["frmcmp_batch", "Module={}", "Module_Type=LIBRARY", "Script=YES", "Batch=YES", "Logon=NO", "Output_File={out}"]'
请写成一行。该值会在不使用 shell 的情况下拆分为参数,因此复制到配置字段中的 \ 换行符续行会作为字面参数传递给工具。
为 frmcmp 指定 Output_File={out}。 如果不指定,frmcmp 会将 .pld 写在模块旁边——也就是表单目录中,无论其工作目录是什么——随后服务端会将其当作预转换文件读取。如果某次运行没有在转换目录中生成文件,却在模块旁边留下了新文件,错误信息会指出该文件,并提示添加 {out};服务端不会删除它。
Logon=NO 也很重要:如果不指定它(或一个有效的 Userid=),frmcmp 会打印用法信息并以 0 退出,但不会写入任何内容。
模块将使用哪个转换器:
--compile-command | --convert-command | ORACLE_HOME | .pll | .fmb / .mmb / .olb |
|---|---|---|---|---|
| — | — | — | 复制模式 | 复制模式 |
| — | — | 已设置 | frmcmp | frmf2xml |
| — | 已设置 | — / 已设置 | 转换命令 | 转换命令 |
| 已设置 | — | — | 编译命令 | 复制模式 |
| 已设置 | — | 已设置 | 编译命令 | frmf2xml |
| 已设置 | 已设置 | — / 已设置 | 编译命令 | 转换命令 |
如果未设置 --compile-command,则不会有任何变化:已能处理库的 --convert-command 仍会接收这些库。对于以复制模式处理的类型,会将其预转换文本形式与缓存条目进行比对,因此重新导出 _fmb.xml 仍会使该表单标记为过期,而其库则会从二进制文件转换。
缓存
%LOCALAPPDATA%\oracle-forms-mcp(Windows)、~/Library/Caches/oracle-forms-mcp(macOS)、$XDG_CACHE_HOME/oracle-forms-mcp(Linux)。每个模块一个目录:
ORDERS.fmb/
converted/orders_fmb.xml converted (or copied) text form
plsql/triggers/*.sql decoded trigger bodies
plsql/program-units/*.sql decoded program units
plsql/menu-items/*.sql menu-item command bodies (menu modules)
index.json the structured index
可随时安全删除;模块只需重新获取。使用
--converted-dir 时,converted/ 文件会写入你指定的目录,其余缓存条目仍保留在此处。
注释不属于此派生缓存。注释存储在单独的 annotations/ 目录中(每个模块对应一个 NAME.ext.json 文件,默认位于 <cache dir>/annotations,也可通过 --annotations-dir 指定其他位置),因此删除模块的缓存条目或重新获取模块,都不会影响你记录的备注、标签和关系。
Oracle 工具说明
frmf2xml会将输出写入进程工作目录;服务端会以转换目录作为 cwd 运行它(--converted-dir,否则使用模块缓存目录),并传入OVERWRITE=YES USE_PROPERTY_IDS=NO。- 优先使用
frmcmp_batch而非frmcmp(无头模式);服务端会传入Script=YES Batch=YES Logon=NO和显式的Output_File——如果不指定,frmcmp会将.pld写在模块旁边,而不是写入其工作目录——并将表单目录加入FORMS_PATH,以便解析附加库。 - 表单工具的退出代码并不可靠——是否成功取决于输出文件是否存在、是否非空,以及是否比调用时间新;失败时会显示工具输出的末尾部分。
.pld文件可能采用客户端 NLS 字符集写入;解析器会先按 UTF-8 读取,失败时回退到 windows-1252(如果出现乱码,请相应设置NLS_LANG)。
隐私政策
Oracle Forms MCP 完全在你的计算机上运行,不收集任何信息。
- 数据收集。 无。服务没有遥测、分析、崩溃报告或账户,也不会自行发起网络请求。
- 读取和存储的内容。 服务会读取你指定的
--forms-dir中的模块,并将派生文件——转换后的文本表单、解码后的 PL/SQL 和index.json——写入本地缓存(或--converted-dir)。你或助手记录的注释会写入本地注释存储。这些内容都保留在你的磁盘上,你可以随时删除。 - 内容去向。 模块内容仅会作为响应发送给你连接的 MCP 客户端,离开服务。该客户端如何处理这些内容——包括将其发送给模型提供商——由客户端自身的隐私政策约束,而非本政策。除非你通过
--host绑定到其他地址,否则http传输仅监听127.0.0.1。 - 与第三方共享。 无。唯一例外是 Claude Code 插件的启动器:它每天最多向 GitHub API 查询一次最新版本,并从 GitHub Releases 下载版本压缩包及其校验和。这些请求会发送至 GitHub,并受 GitHub 隐私声明约束,且不会包含你的模块中的任何内容。固定
server_version或使用OFMCP_SERVER_HOME可避免这些请求。 - 保留期限。 缓存文件和注释文件会一直保留,直到你将其删除;软件不会在其他任何地方保留数据。
- 联系方式。 如有疑问,请前往 GitHub issues;安全问题请按照 SECURITY.md 中的说明报告。
开发
gradlew build # compile + all tests (no Oracle installation needed)
gradlew updateKotlinAbi # refresh the ABI dump (core/api/*.api) after public API changes
gradlew :server:run --args="--forms-dir sample-forms"
gradlew :server:packageMcpb # build the installable .mcpb bundle
转换器行为通过虚假的 ORACLE_HOME(存根脚本)进行测试;完整的复制模式流水线则由 FormsServiceIntegrationTest 使用随附的测试数据进行端到端测试。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。