MCP server serving Oracle Forms module content (.fmb/.mmb/.pll/.olb) — copy-mode only, no Oracle tools bundled
Oracle Forms MCP demonstrates solid definition quality with comprehensive tool coverage (11 tools), well-structured schemas, and detailed descriptions. All tools have descriptions exceeding 100 characters with clear intent. Input schemas are present and typed for all tools. However, several tools lack explicit enum constraints for parameters that should be constrained (e.g., 'verbosity', 'level', 'status' in list_modules), forcing LLMs to guess valid values. Parameter descriptions are generally strong but some lack format/range guidance. Output schemas are documented in code but not all are visible in the provided source. Error handling guidance is minimal, tools describe what they do but not how to recover from failures. Tool composition is excellent: each tool has a single responsibility, and output fields chain well into downstream tool parameters (e.g., module names from list_modules feed into fetch_module).
Persist one annotation about one element of a fetched module — durable meta-information that later sessions see when they read the same element. Two different enums: 'elementKind' says which Forms object is annotated (trigger, program_unit, item, ...), 'kind' says what sort of annotation it is (note, summary, tag, classification), and 'body' carries its text. The element must exist (call fetch_module first). Use ownerPath to disambiguate same-named elements at different scopes.
Convert an Oracle Forms module to its text form and index it, warming the local cache. With ORACLE_HOME set the binary is converted via frmf2xml/frmcmp; otherwise a pre-converted file next to the module is copied. Idempotent — a cached module whose source is unchanged, indexed by this build, returns immediately; one indexed by an older build is re-parsed from the converted file it already has, without re-converting. Call this once per module (and again when list_modules reports STALE) before using the other tools. Returns a summary (block/item/trigger/program-unit counts, attached libraries); its 'hint' names the attached libraries not fetched yet — code the module calls but does not define is usually there — and, separately, any the forms directory does not hold, which nothing here can read: treat a routine you cannot find in one of those as undetermined rather than absent.
One block: base table, its DML properties, its trigger names, its master-detail relations, and every item. The block's own 'dml' answers what the block queries and writes as a whole: 'whereClause' and 'orderByClause' (the SQL Forms appends to every query — a block can be restricted to a subset of its table without one line of PL/SQL), 'dmlDataTargetName', insert/update/delete/queryAllowed, and 'keyMode'/'lockMode'. 'block.relations' are the relations this block is the master of and 'detailOf' those naming it as detail — join condition, deferred/autoQuery, deleteRecord, and preventMasterlessOperations (the block cannot be queried except through that master) — which is what decides what the screen can see. Each item row carries its name, type, property class (which is usually where an item's role is defined — an LOV button is only an LOV button because of the class it inherits), prompt, item-trigger names, and its 'inherited' pointer when it is subclassed. verbosity=detailed adds data type, column, canvas, its own width/height (layout units, usually pixels — not the accepted length, which is maximumLength), the visible/required/LOV properties, each item's own 'dml' (database item, insert/update allowed, enabled, initial value, …) exactly as the item writes them, and 'effectiveDml': the same properties with the item's property class applied. Use 'effectiveDml' to answer what an insert or update writes — on a classed item a property the item does not write comes from its class, which is often defined in another module; an item appears there only when its class resolved, and every item missing from it is listed in 'unresolvedItems' with the reason and the module to fetch_module. 'effectiveGeometry' is that same resolution for the item's size: a classed item typically writes a width and no height at all, so ask the size question there and not of 'items[].width'/'height', which are only what the item itself wrote. items=[...] narrows every list to the named items (e.g. the few whose length or insert rule a question is about). 'truncated' means items, relations, 'unresolvedItems', 'effectiveDml', 'effectiveGeometry' or the column rows were cut to fit one response; the hint says which, and what the cut list does not show. columns=true adds the base table's columns as Forms recorded them, the columns no item supplies, and which of those are mandatory (an insert fails unless a trigger assigns them). A subclassed block carries an 'inherited' pointer to the module that defines it — what is listed here is then only this module's overrides.
Enum parameters lack explicit constraint declarations. 'verbosity', 'level', 'status', 'type', 'elementKind', 'kind', 'author', 'unitType' are described as enums but no enum values are visible in schemas. LLMs cannot validate options and may hallucinate invalid values.
Error handling lacks recovery guidance. Tools describe what they do but do not explain what to do if they fail (e.g., 'module not found, call list_modules first', 'unresolved items, fetch the parent module'). Agents cannot self-correct without explicit hints.
Numeric parameters lack range constraints. 'limit' in list_modules is described as '1-500 (default 50)' in text but no min/max schema constraints are visible. 'startLine', 'endLine' in read_source (inferred) lack documented bounds.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 64 | 2025-06-18+ | v2 |
The notes, tags, summaries, classifications, and relations stored about one element (from earlier annotate_element / relate_elements calls). Each entry is flagged 'staleAgainstSource' when it predates the module's current source. Served even for a STALE module so prior knowledge is never hidden.
Overview of a fetched module: Forms version plus the names of every section — blocks, program units, attached libraries, LOVs, record groups, windows, canvases, alerts, parameters, visual attributes, property classes, editors, menus, object-library tabs — and the trigger count. The natural first call after fetch_module; drill in with list_blocks, list_triggers, list_program_units, then the get_* tools. 'truncated' means at least one section held more names than the response cap and was cut — drill into that section with its own list tool. verbosity=detailed adds a 'detail' section with the window and canvas objects behind those two name lists — window modality and size, toolbar canvases, and the window each canvas sits on.
The raw XML fragment of one named object, sliced from the converted file — the escape hatch for every property the structured tools don't surface. objectType is the Forms2XML element name (Block, Item, Trigger, Relation, Canvas, Window, LOV, RecordGroup, Alert, VisualAttribute, …). Pass 'owner' (e.g. 'ORDERS' or 'ORDERS.ORDER_ID') when the name exists at several scopes; a Relation's owner is its master block. Large fragments are truncated ('truncated', with a 'hint' naming the read_source call that continues). Forms writes the subclassing pointer on the enclosing owner, so a fragment of a subclassed object shows only SubclassSubObject="true"; the resolved pointer is returned beside it as 'inherited'. Raw XML from some converters carries doubly-escaped newlines (a literal '
' inside a body, leaving the whole body on one line); the bodies served by get_trigger and get_program_unit are recovered from that and marked textEncoding=RECOVERED, so prefer them for code and read this for properties.
The PL/SQL body of one program unit. Pass 'unitType' when a package's spec and body share the name (PACKAGE_SPEC vs PACKAGE_BODY). A subclassed unit returns bodySource='inherited' with a pointer to the module that defines it, rather than an empty body that reads as 'no code'. 'source' lines count from the first line of the body — cite them as UNIT:line, not module:line. A .pll is the exception: its units are ranges within the one .pld dump of the whole library. While the module attaches a library that is not fetched, the 'hint' says which: a routine the body calls that the module does not define may live there, and is undetermined until it is read.
The decoded PL/SQL body of one trigger. Pass 'ownerPath' (or 'level', or 'block'/'item') when the same trigger name exists at several scopes — e.g. a KEY-NEXT-ITEM at form, block and item level; level="form" is the form-level one. When the trigger is subclassed from another module its body is empty here and the code that runs lives in the parent: the result then says bodySource='inherited' and carries 'inherited' (the parent module and the path to the trigger there) plus a hint naming the exact call. Never read an empty 'text' as 'this trigger does nothing' without checking bodySource. 'source' addresses the extracted body, which is a file holding this trigger alone: its lines count from the first line of the body, so cite them as TRIGGER:line (e.g. POST-INSERT:75), never as module:line — the .fmb has no such line. While the module attaches a library that is not fetched, the 'hint' says which: a routine the body calls that the module does not define may live there, and is undetermined until it is read.
List the data blocks of a fetched form module with their base table (query data source), item count, and trigger count. Use get_block for a block's full item list. 'total' counts every block; 'truncated' means the rows were cut at the response cap — reach the rest by name with get_block or search_source.
List Oracle Forms modules in the configured directory (.fmb, .mmb, .pll, .olb) with their cache status: NOT_CACHED (call fetch_module first), CACHED (ready to read), STALE (re-fetch), or SOURCE_MISSING (cached but the file is gone). A STALE row carries 'staleReason': SOURCE_CHANGED (the module changed on disk) or INDEX_OUTDATED (an older build of this server indexed it, so its facts may be incomplete; re-fetching only re-parses, it does not re-convert). Also reports whether a pre-converted XML/pld sibling exists. A real forms directory holds thousands of modules, so the answer is filtered and paged: narrow with 'pattern', 'type' and 'status', and when 'truncated' is true call again with 'cursor' set to the returned 'nextCursor'. 'countsByStatus' always summarises the whole name/type-filtered set, so one narrow call still shows the shape of the rest.
List a fetched module's PL/SQL program units (procedures, functions, package specs and bodies) with their line counts. For .pll libraries this is the library's whole content. Fetch a body with get_program_unit. 'total' counts every unit; 'truncated' means the rows were cut at the response cap — reach the rest by name with get_program_unit or search_source.
Output schemas not fully visible in provided source. ToolResults.kt shows SourceLocation and partial structures, but complete response shapes for all 11 tools are not shown. Cannot verify all return fields are documented.
Tool descriptions reference complex domain concepts (Forms2XML, DML, property classes, subclassing) without sufficient context for LLMs unfamiliar with Oracle Forms. Descriptions assume deep domain knowledge.