MCP server that exposes Oracle Forms modules (.fmb/.mmb/.pll/.olb) — blocks, triggers, PL/SQL, object XML — to AI assistants for legacy modernization and code review.
- ✓Open-source license (Apache-2.0)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
git clone https://github.com/aoreshkov/oracle-forms-mcpMCP Servers overview
# Oracle 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)
An [MCP](https://modelcontextprotocol.io) server that serves the content of Oracle Forms modules
(`.fmb` forms, `.mmb` menus, `.pll` PL/SQL libraries, `.olb` object libraries) found in a
directory, so AI assistants can inspect blocks, items, triggers, program units, and raw object
XML without opening Forms Builder.
<!-- mcp-name: io.github.aoreshkov/oracle-forms-mcp -->
Built as a Kotlin Multiplatform core (pure `@Serializable` models and ports) with a JVM MCP
server on top: declarative tool adapters over a single `FormsService`, stdio and HTTP transports,
and a fingerprint-based on-disk cache. Oracle tool conversion feeds a streaming StAX parser that
turns Forms XML into a structured index.
## Why
Oracle Forms applications from the 1990s–2000s are still running critical business processes, but
their logic is locked inside binary `.fmb`/`.pll` modules that only Forms Builder can open. That
makes them opaque to modern AI tooling and painful to review, document, or migrate.
Oracle Forms MCP turns those modules into structured, queryable content so an AI assistant can:
- **Understand a legacy app** — enumerate blocks, items, triggers, and program units without a Forms IDE.
- **Review & document PL/SQL** — pull decoded trigger and program-unit bodies straight into the model's context.
- **Assist modernization** — feed decades-old business logic to an assistant for migration to APEX,
Java, or a rewrite, and search across every module's source.
- **Capture & retain knowledge** — let the assistant record notes, tags, and cross-references on
individual elements that persist across sessions and re-indexing, building up a durable map of a
form no one fully remembers.
It is aimed at developers and teams doing Oracle Forms **modernization, reverse engineering, code
review, and documentation** — anyone who needs to read Forms logic faster than opening it by hand.
## See it work
<!-- TODO: record a short asciinema/GIF of a Claude session and embed it here:
 (drop the file at docs/demo.gif or an assets/ path) -->
A typical session against the bundled `sample-forms` directory:
```text
You: What does ORDERS.fmb do?
AI → list_modules → ORDERS.fmb (NOT_CACHED), MAINMENU.mmb, UTILS.pll …
AI → fetch_module ORDERS.fmb → converted + indexed (2 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
```
## How it works
1. `list_modules` scans the configured `--forms-dir` (non-recursive) and reports each module's
cache status: `NOT_CACHED`, `CACHED`, `STALE` (source changed on disk), or `SOURCE_MISSING`.
A production forms directory holds thousands of modules, so the answer is filtered and paged:
narrow with `pattern`/`type`/`status`, follow `nextCursor` for the rest, and read
`countsByStatus` for the shape of the whole match.
2. `fetch_module` produces the module's text form in the cache and indexes it:
- **`ORACLE_HOME` set** — binaries are converted with the Oracle tools in
`%ORACLE_HOME%\bin`: `frmf2xml` for `.fmb`/`.mmb`/`.olb` (XML), `frmcmp_batch`
(`Module_Type=LIBRARY Script=YES`) for `.pll` (a `.pld` text dump).
- **`ORACLE_HOME` not set** — pre-converted files are expected next to the modules
(`orders_fmb.xml`, `dupes_fmb.xml`, `picker_fmb.xml`, `toolbar_fmb.xml`,
`mainmenu_mmb.xml`, `objects_olb.xml`, `utils.pld`) and copied into the cache.
3. A single StAX pass parses the XML into a structured index (blocks with items, triggers with
decoded PL/SQL, program units, LOVs, record groups, windows, canvases, …). PL/SQL bodies are
extracted to `.sql` sidecar files; every named XML element gets a line-range reference so
`get_object_xml` can slice it back out of the converted file.
4. The other tools read the cached index. Caching is fingerprint-based (size + mtime + sha256 of
the source file): editing a module marks it `STALE` and read tools ask for a re-fetch.
5. `annotate_element` and `relate_elements` let the assistant write durable meta-information back
about individual elements (notes, tags, summaries, classifications, cross-references). This is
kept in a **separate** store — not the derived index — so it survives re-fetching, and the read
tools surface it inline. An annotation made before a source change is flagged, never dropped.
## Tools
| Tool | What it returns |
|---|---|
| `list_modules` | Modules in the forms dir with type, size, and cache status; filter by `pattern`/`regex`, `type`, `status` and page via `limit`/`cursor` |
| `fetch_module` | Converts + indexes one module (idempotent; progress notifications) |
| `get_module_overview` | Names of every section + counts — the first call after a fetch (`verbosity=detailed` adds window and canvas objects) |
| `list_blocks` | Blocks with base table, item count, trigger count |
| `get_block` | One block: items with type, property class, prompt and trigger names (`verbosity=detailed` adds data type, column, canvas, visible/required/LOV); flags subclassed blocks/items |
| `list_triggers` | Triggers with level/scope; filter by block, item, or level (`verbosity=detailed` adds a PL/SQL preview) |
| `get_trigger` | One trigger's decoded PL/SQL body; `resolve` follows a subclassing pointer into a cached parent module |
| `list_program_units` | Procedures, functions, package specs/bodies with line counts |
| `get_program_unit` | One program unit's PL/SQL (disambiguate spec/body via `unitType`); `resolve` as for `get_trigger` |
| `search_source` | Line search over one fetched module: extracted PL/SQL (`plsql`), the raw XML (`xml`), or both; paginated via `offset`/`nextOffset` |
| `search_modules` | The same search across **every cached module** — cross-form calls, shared `:GLOBAL`s, subclassing pointers; reports the modules it could not reach; paged via an opaque `cursor` |
| `read_source` | A line range of a cached file, by `uri` or `file` — the converted XML or an extracted PL/SQL sidecar |
| `get_object_xml` | The raw XML fragment of any named object — the escape hatch |
### Where an object's meaning lives
Two Forms properties carry more meaning than anything else in a module, and both used to be
reachable only one `get_object_xml` call at a time.
An item's **property class** is usually its role: Forms shops name their classes for what the
object does, so a push button is an *LOV* button and a text item is a *filter* field only because
of the class it inherits. `get_block` reports `propertyClass` on every item and block, confirmed
against the classes the module actually declares.
A window's **modality** decides how a form is read — a modal window is a dialog, so the code that
fills it and the code that consumes the result sit on opposite sides of one interaction.
`get_module_overview(verbosity: "detailed")` returns the window and canvas objects behind two of
its name lists: modality, size, toolbar canvases, and the window each canvas sits on.
A property Forms did not write comes back as `null`, meaning the object keeps the Forms default —
never `false`.
### Reading around a result
Everything indexed is a line range into a file the cache owns, and those ranges used to name a
path with no way to resolve it. Every result that points at a file now carries a `source`:
```jsonc
"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` takes either form back, so a search hit can be read in context and a truncated
fragment continued. The same files are readable as MCP resources —
`oracleforms://{module}/converted` for the converted text form and
`oracleforms://{module}/plsql/{category}/{name}` for one extracted block of PL/SQL — and every
such result also carries a `resource_link` content block, so a client can follow it without
parsing JSON. Reads are capped by lines *and* characters; a cut resource read says so in the text
it returns and names the call that continues it.
Paths stay cache-relative and URIs stay layout-independent: an absolute host path would mean
nothing to a client talking to the container image or over HTTP.
### Subclassed (inherited) objects
Forms lets a block, item, trigger or program unit be **subclassed** from another module, or copied
in with an **object group** (typically from an `.olb`). Either way the child stores only what it
overrides, so its PL/SQL body is genuinely empty in its own file while the code that runs lives in
the parent. Served naively that is not merely incomplete but wrong —What people ask about oracle-forms-mcp
What is aoreshkov/oracle-forms-mcp?
+
aoreshkov/oracle-forms-mcp is mcp servers for the Claude AI ecosystem. MCP server that exposes Oracle Forms modules (.fmb/.mmb/.pll/.olb) — blocks, triggers, PL/SQL, object XML — to AI assistants for legacy modernization and code review. It has 3 GitHub stars and its last recorded update is dated 2026-09-12.
How do I install oracle-forms-mcp?
+
You can install oracle-forms-mcp by cloning the repository (https://github.com/aoreshkov/oracle-forms-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is aoreshkov/oracle-forms-mcp safe to use?
+
Our security agent has analyzed aoreshkov/oracle-forms-mcp and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.
Who maintains aoreshkov/oracle-forms-mcp?
+
aoreshkov/oracle-forms-mcp is maintained by aoreshkov. The last recorded GitHub activity is dated 2026-09-12, with 0 open issues.
Are there alternatives to oracle-forms-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy oracle-forms-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/aoreshkov-oracle-forms-mcp)<a href="https://claudewave.com/repo/aoreshkov-oracle-forms-mcp"><img src="https://claudewave.com/api/badge/aoreshkov-oracle-forms-mcp" alt="Featured on ClaudeWave: aoreshkov/oracle-forms-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!
The fastest path to AI-powered full stack observability, even for lean teams.