Skip to main content
Glama
ruya-grp

fusion-query-mcp

by ruya-grp

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
FUSION_HOMENoOverride directory for configuration and pod data. Defaults to ~/.fusion-query/ when unset.
FUSION_PASSNoFusion service account password. May be omitted if credentials are supplied via the pod's .env file.
FUSION_USERNoFusion service account username. May be omitted if credentials are supplied via the pod's .env file.
FUSION_BACKENDNoOverride for fusion.backend: 'rest' or 'soap'.
FUSION_BASE_URLNoOverride for fusion.base_url: the Oracle Fusion pod URL without a trailing slash.
FUSION_MAX_ROWSNoOverride for limits.default_max_rows.
FUSION_AUDIT_PATHNoOverride for audit.path.
FUSION_MCP_CONFIGYesPath to the Fusion MCP configuration file (config.yaml). This must be passed explicitly because an MCP server does not inherit the shell's working directory.
FUSION_ENGINE_MODENoOverride for fusion.engine_mode: 'whole_query' or 'clauses' (legacy lexical path).
FUSION_PARAM_SHAPENoOverride for fusion.param_shape: 'auto', 'flat', or 'item'.
FUSION_FIXTURES_DIRNoOverride for fixtures_dir.
FUSION_HARD_MAX_ROWSNoOverride for limits.hard_max_rows.
FUSION_MAX_SQL_CHARSNoOverride for limits.max_sql_chars (legacy lexical path only).
FUSION_TIMEOUT_SECONDSNoOverride for limits.timeout_seconds.

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
fusion_run_queryA

Run a read-only SQL SELECT -- ONLY on a pod that substitutes lexical parameters.

Check whether this tool can work here before you use it. It ships the statement as a lexical BI Publisher parameter, and a Fusion pod that has lexical substitution disabled (the common SaaS hardening, and the case this server was built for) silently drops it: the data model then executes SELECT * FROM () and Oracle answers ORA-00903. No rewrite of your SQL fixes that, so do not enter a repair loop -- the failure hint says so explicitly when it sees that signature.

The working path on such a pod is the report registry: call fusion_list_reports to see what an administrator has already built, then fusion_run_report to run one with bind values and fusion_validate_report to check it. Schema exploration works either way.

If your pod does honour lexicals, this is the full free-SQL surface. The statement must be a single SELECT or WITH; anything else is rejected before it leaves this machine, and a row cap is appended automatically unless the SQL already limits rows. Give every selected expression a simple alias (SUM(x) AS TOTAL_AMOUNT) -- result columns become XML element names, and unaliased expressions can produce an unparsable document.

Args: sql: The SELECT/WITH statement to execute. datasource: Configured datasource key (default: the server's default). max_rows: Row cap for this call, clamped to the server's hard maximum. timeout_s: Per-call timeout in seconds. select_list: engine_mode: clauses only -- the projection without SELECT. from_clause: engine_mode: clauses only -- the FROM body without FROM. where_clause: engine_mode: clauses only -- including the WHERE keyword. group_clause: engine_mode: clauses only -- including GROUP BY/HAVING. order_clause: engine_mode: clauses only -- including ORDER BY/FETCH.

fusion_list_tablesA

List Fusion tables matching a SQL LIKE pattern, most-populated first.

Runs the registered list_tables report with p_pattern / p_owner as bind values. The report applies no row cap of its own, so limit is applied here after the rows arrive and truncated says honestly whether more came back.

approx_rows comes from all_tables.num_rows, an optimiser statistic that can be stale or NULL -- treat it as a hint about which of several similarly named objects is the real transactional one, never as a count. It is also the quickest way to spot the same-name-different-case pairs this pod carries: the populated one is the real table.

The owner and pattern echoed back are the values that were actually bound, which is not always what you passed: omitting one applies the report's registered default, and the full set is in params_used.

Args: pattern: SQL LIKE pattern, e.g. %INVOICE%. % matches any run of characters. Matching is case-insensitive inside the data model. owner: Schema owner; FUSION for application data. The data model compares it with LIKE, not =, so % searches every schema the service account can see. Omit (null) to bind the report's registered default instead (FUSION in the shipped registry). limit: Maximum rows to return from those the report produced. include_views: Not backed by any report -- see the error it returns. datasource: Legacy composed-SQL fallback only; ignored on the report path.

fusion_describe_tableA

Describe ONE Fusion object: ordered columns, data types, comments.

Call this before writing any query that names a column. Fusion column names rarely match the UI labels, and a wrong guess costs a full ORA-00904 round trip. Comments are populated on this pod and are genuinely informative.

The dual-object rule. This pod carries objects whose names differ only in letter case -- FUSION.po_headers_all and FUSION.PO_HEADERS_ALL are two distinct objects, and the dictionary lookup is case-insensitive, so a lookup for one returns both. Presenting their union would show a 288-column table as 576 columns with every name duplicated: a wrong answer that looks like a right one. So the rows are grouped by their exact (OWNER, TABLE_NAME) and exactly one group is ever described:

  1. If exactly one group spells its name the way you did (case-sensitive on the object, case-insensitive on the owner, since Oracle object names created with quotes really are case-sensitive), that group wins -- chosen_by: exact_name_match. Your spelling is the only unambiguous signal available.

  2. Otherwise the best-documented group wins -- chosen_by: most_documented: first a group that has a table comment, then the one with the most commented columns, then the most columns, then name order for determinism. On this pod the documented twin is the real transactional object; its namesake has no statistics and no comments. And it is decided from data the report already returned, so no extra round trip is needed.

Whenever more than one object matched, ambiguous is true, alternates lists every object that was NOT described, and the summary leads with it. The ambiguity is never resolved silently: if the choice is wrong, re-call with the exact spelling you meant (table="po_headers_all").

Args: table: Object name, optionally OWNER.OBJECT, e.g. AP_INVOICES_ALL. Case is preserved and used for the tie-break above. owner: Schema owner used when table is unqualified. datasource: Legacy composed-SQL fallback only; ignored on the report path.

fusion_search_columnsA

Find which tables carry a column -- the fastest answer to "where does X live?".

Runs the registered search_columns report with p_pattern / p_owner / p_table_pattern as bind values. Results are ordered by the owning table's row-count statistic, so the real transactional table tends to surface above its interface, history and staging namesakes -- and above its same-name-different-case twin, which has no statistics at all.

The report applies no row cap, so limit is applied here and truncated reports honestly when more rows came back than were returned.

Args: pattern: SQL LIKE pattern over the column name, e.g. %SEGMENT1%. owner: Schema owner. Omit (null) to let the report's own default apply. table_pattern: Optional LIKE pattern to restrict the table name too. limit: Maximum rows to return from those the report produced. datasource: Legacy composed-SQL fallback only; ignored on the report path.

fusion_list_podsA

Which Fusion pods this server can talk to, and which one is active.

Pods are directories under pods/, each holding a config.yaml (and a gitignored .env with that pod's credentials). Call this at the start of a session, then fusion_use_pod to bind the session to one pod. With no pods/ directory the server runs in single-config mode and this lists nothing.

fusion_use_podA

Bind this session to one pod: every later call runs against it.

Switches config, credentials, report registry, fixtures, audit log and docs snapshot to the named pod in one move, and stays in effect until the session ends or this is called again. Schema knowledge does NOT carry across pods -- releases and customizations (DFFs, custom objects) differ, so re-verify anything pod-specific after a switch. Every data-bearing response echoes the active pod in its pod field.

Args: pod: Directory name under pods/, e.g. test or prod.

fusion_docs_describe_tableA

Describe a table from the local Oracle-docs snapshot -- instant, no pod call.

Serves the OEDM documentation Oracle publishes for every Fusion table: column business descriptions, primary key, foreign keys (join edges!), indexes, flexfield mappings, and for views their defining SQL. Prefer this over fusion_describe_table for exploration -- it answers in milliseconds instead of 12-14 s and carries meaning the pod's dictionary lacks.

The snapshot is docs-derived, not pod ground truth: a column that exists here but not on the pod will fail loudly in fusion_adhoc_query, never silently. Custom objects and case-twin duplicates exist only on the pod, so fall back to the live tools when this returns not_found.

Args: table: Exact table or view name, e.g. PO_HEADERS_ALL. columns_like: Optional substring to filter columns by name or by words in their description -- use it on wide tables to keep responses small. limit: Maximum number of column entries returned.

fusion_docs_search_columnsA

Search columns across ALL documented tables by name or business meaning.

Full-text search over the local docs snapshot -- both column names and their descriptions -- so "supplier hold reason" finds POZ_SUPPLIER_SITES_ALL_M.PURCHASING_HOLD_REASON without knowing any naming convention. Instant and token-cheap; prefer it over the live fusion_search_columns for discovery, then verify existence on the pod only when you execute.

Args: term: Words to match (AND-combined), or a column-name fragment. limit: Maximum matches returned.

fusion_describe_flexfieldsA

Which DFF segments exist, and which ATTRIBUTEn column holds each one.

Descriptive Flexfields are how Fusion customers add their own fields: the data lands in the generic ATTRIBUTE1..n / ATTRIBUTE_NUMBERn / ATTRIBUTE_DATEn columns of the base table, and only the DFF definition says what each column means. This tool reads that definition (from fnd_df_segments_b + _tl), so a spec that says "supply duration" can be resolved to PO_HEADERS_ALL.ATTRIBUTE2 without asking a human.

Protocol for an unknown table:

  1. fusion_describe_table the base table -- seeing ATTRIBUTE columns means a DFF may be in use.

  2. Call this with the DFF code, which is USUALLY the base table name without _ALL (PO_HEADERS_ALL -> PO_HEADERS); pass a LIKE pattern (PO_HEADERS%) when unsure -- related codes like PO_HEADERS_SH show up too, and % at both ends casts wider.

  3. Project the returned COLUMN_NAME in your query (fusion_adhoc_query), aliased to the SEGMENT_CODE, and filter ATTRIBUTE_CATEGORY by CONTEXT_CODE when the segment belongs to a context other than Global Data Elements (global segments apply to every row).

SEGMENT_PROMPT is the label users see in the UI -- match the user's wording against it. Context Data Element rows describe the context chooser itself (ATTRIBUTE_CATEGORY), not a data segment.

Args: flexfield: LIKE pattern over the DFF code, e.g. PO_HEADERS or %INVOICE%. Case-insensitive. context: Optional LIKE pattern over CONTEXT_CODE to narrow. limit: Maximum segment rows to return.

fusion_api_list_actionsA

List what this service account was OBSERVED able to create on this pod.

Start every "register a X" request here. Each entry carries the resource name and the fields the pod itself named as required -- harvested from its own rejection message, not derived from metadata.

This list is observed, never declared. On the pod this was built against, /describe advertises POST on purchaseOrders and the account gets HTTP 403; that resource is correctly absent here. If a resource you expect is missing, it was either never harvested or genuinely refused -- check with fusion_api_describe, which reports write_access either way.

fusion_api_describeA

The field list for one REST resource -- the script for collecting input.

Returns each settable field with its type, business description, length limit and list-of-values, plus the child collections (lines, attachments) a complete document usually needs.

Read required_fields and required_source before trusting anything else. When required_source says the pod's own message, that list is authoritative. When it says "not probed", the per-field declared_mandatory flags are the only thing available and they are known wrong in BOTH directions on this pod: ExternallyManagedFlag is declared optional and is required; RequisitionHeaderId is declared mandatory and must not be sent at all.

Args: resource: Exact resource name, e.g. purchaseRequisitions. writable_only: Drop fields the caller can never set (generated ids, audit columns). On purchaseRequisitions this is 25 fields rather than 68 -- the other 43 are questions no user can answer. limit: Maximum number of fields returned.

fusion_api_searchA

Find REST resources and fields by name or business meaning.

Instant, local, no pod round trip. Use it to turn a phrase the user said ("the business unit that raises the request") into the field name the API expects (RequisitioningBUId) before asking them anything.

Args: term: Substring to match against resource names, field names and business descriptions. limit: Maximum matches per kind (resources and fields counted apart).

fusion_resolve_valueA

Turn a name the user said into the id the API wants.

RequisitioningBUId wants 300000003588643; the user says "the Egypt BU". Call this for every id-shaped field before putting it in a payload.

Runs as a normal guarded SELECT on the read channel (the pod's list-of-values views are not addressable over REST -- all candidate paths answer HTTP 404), so it is audited like any other query.

Four outcomes, and ambiguous is the important one: when several rows match, the candidates come back for the user to choose between. Never pick one yourself -- two business units both matching "IHC" is exactly the situation where guessing produces a confidently wrong document.

Args: field: The REST attribute name, e.g. RequisitioningBUId. text: What the user said. Matched case-insensitively as a substring.

fusion_prepare_actionA

Check an action and hold it for confirmation. Sends NOTHING to the pod.

Returns exactly what would travel, the guard's verdict, and a token. The action reaches Fusion only when fusion_commit_action presents that token.

Show the preview to the user in their own words and get an explicit yes before committing. That pause exists here because Fusion has none: an authorised, valid POST executes on arrival.

This cannot tell you the action will succeed. There is no dry-run for a Fusion create -- a valid POST commits -- so the pod has not seen this payload yet. What it can do is stop an action the policy forbids, and show you the fields the snapshot says are required before you spend a round trip.

Args: resource: e.g. purchaseRequisitions. Use fusion_api_list_actions to see what this account may actually create. payload: The record to send. Resolve every id with fusion_resolve_value first. verb: POST to create, PATCH to change an existing record. record_id: Required for PATCH -- the record to change.

fusion_commit_actionA

Execute a prepared action. THIS CHANGES DATA IN FUSION.

Requires the token from fusion_prepare_action and confirmed=True, which you may only set after the user has seen the preview and said yes. The token is single-use: a refused attempt needs a fresh prepare, which is also where you add the fields the pod asked for.

When the pod refuses, its own message and the fields it named come back rather than a summary -- that message is the next question to ask the user.

Args: token: From fusion_prepare_action. confirmed: Set True only after explicit user confirmation.

fusion_submit_jobA

Run a Fusion scheduled process (ESS job). THIS STARTS REAL WORK.

For everything that has no REST resource: Import Payables Invoices, Create Accounting, and the rest of Scheduled Processes. Returns a request id; poll it with fusion_job_status.

A submitted job CANNOT be recalled. Unlike a REST create -- which is often refused, changing nothing -- there is no safe failed attempt here: once the request id exists the job is queued. Confirm with the user before setting confirmed.

parameters is POSITIONAL, and the pod's own words on it are: "the order of the parameters is maintained as per the list. The corresponding entry in the list should be blank when a given parameter is not passed." Pass an empty string to skip an argument -- never omit it, or every later argument shifts by one and the job runs against the wrong data without complaining.

Args: package: ESS job package, e.g. /oracle/apps/ess/financials/payables/invoices/transactions. definition: Job definition name, e.g. APXIIMPT. parameters: Positional arguments; "" for any that is skipped. confirmed: Set True only after explicit user confirmation.

fusion_import_bulk_dataA

Load a data file into Fusion (FBDI) and queue the import jobs.

Reads file_path from disk, uploads it to UCM, and submits each job in jobs. THIS LOADS REAL DATA and cannot be recalled.

account is the UCM account, and it is the field that goes wrong quietly: a file filed under the wrong account uploads successfully and is then invisible to the import job, with no error anywhere. Confirm it against the job's documentation, e.g. fin$/payables$/import$.

Each entry in jobs is {"name": "package,definition", "parameters": [...]}. Note that these parameters are joined with COMMAS -- unlike fusion_submit_job, whose parameters are separate elements. A parameter containing a comma is refused rather than silently split; use fusion_submit_job for those.

Args: file_path: Local path of the file to upload. account: UCM account, e.g. fin$/payables$/import$. jobs: Import jobs to run, in order. notification_code: The service's two-digit notification code. confirmed: Set True only after explicit user confirmation.

fusion_soap_list_servicesA

SOAP services snapshotted for this pod, with their operation counts.

Fusion exposes dozens of SOAP services beyond the REST resources -- whole areas of functionality (purchase order change orders, opportunity management, BI Publisher catalog administration) exist only here.

Snapshot them with: fusion-query-apicat --soap /fscmService/XxxService

fusion_soap_describeA

Operations of a snapshotted SOAP service, or one operation in detail.

Parameters come back in the order the contract declares them, which matters: several Fusion operations are positional, and a shifted argument produces a call that runs against the wrong data without complaining.

Args: service: Service path, e.g. /fscmService/ErpIntegrationService. operation: Optional single operation to detail.

fusion_soap_callA

Invoke any snapshotted SOAP operation. THIS MAY CHANGE DATA.

The envelope is built from the service's own contract, not a guess: the namespaces differ per service (some split the operation wrapper into a /types/ namespace, some use one namespace for everything) and a hardcoded shape produces a well-formed envelope the other services reject.

parameters is a name/value map, but it is sent in contract order, not dict order. A parameter the contract declares and you omit is sent as an empty element rather than dropped -- for positional operations, omitting it shifts every later argument by a slot.

Marked destructive because the operation set includes creates, cancels and deletes. Read-only operations go through the same gate; that is the cost of one door rather than a maintained list of which names are safe.

Args: service: Service path, e.g. /fscmService/PurchaseOrderService. operation: Operation name from fusion_soap_describe. parameters: Name/value map of arguments. confirmed: Set True only after explicit user confirmation.

fusion_list_tasksA

Approvals and notifications waiting for the signed-in user.

The worklist lives at /bpm/api/4.0/tasks -- a third channel, reached by neither the REST resources nor ErpIntegrationService. Six plausible routes (fscmRestApi/notifications, worklistTasks under both FSCM and HCM, userNotifications, the TaskQueryService SOAP endpoint) all answer 404.

Follow up with fusion_task_detail before acting: the list does not carry the available outcomes, and those differ per task AND per user.

Args: status: The service's own status values, e.g. ASSIGNED. Note ANY is not accepted by the service and is refused here with an explanation. limit: Maximum tasks returned.

fusion_task_detailA

One task in full, including the outcomes this user may actually submit.

Read outcomes before offering the user a choice. It is derived from the task's own actionList, keeping only entries that are not actionType: System -- the system entries (REASSIGN, ESCALATE, ACQUIRE...) are plumbing, not decisions.

An EMPTY outcomes list is a real and common state, not an error: it means the signed-in account cannot action this task, typically because it is assigned to a group the account has not acquired. Observed on this pod: an absence-approval task offers no business outcome at all while two e-signature tasks offer APPROVE/REJECT to the same account.

Args: number: The task number from fusion_list_tasks.

fusion_act_on_taskA

Approve, reject or otherwise decide a task. THIS DECIDES SOMETHING.

outcome must be one of the values fusion_task_detail reported for this task -- they differ per task and per user, so never assume APPROVE exists. The value is checked against the live task before anything is sent.

There is no replay guard here and none is pretended: an approved task is no longer ASSIGNED, so a second attempt fails at the service rather than approving twice. That is the one place in this server where the pod's own state is the duplicate protection.

Args: number: Task number. outcome: The outcome id, e.g. APPROVE, REJECT, OK. comment: Optional comment recorded with the decision. confirmed: Set True only after explicit user confirmation.

fusion_job_logA

Fetch a finished job's log or output -- i.e. WHY it failed.

Call this whenever fusion_job_status reports ERROR. Without it the only honest thing you can tell the user is "the job failed", which is the least useful true statement available: the reason is in the log.

File contents are not returned inline -- a job log can be megabytes. Pass save_to to write them to a directory and get the paths back.

Args: request_id: The job's request id. file_type: log for the run log, out for the job's output. save_to: Optional directory to write the files into.

fusion_export_bulk_dataA

Run an extract job and leave its output in UCM. Returns a request id.

The mirror of fusion_import_bulk_data. Marked destructive because it queues a scheduled process that cannot be recalled -- not because it changes business data, which it does not.

parameters are joined with COMMAS here (the schema declares parameterList as a single string), unlike fusion_submit_job whose parameters are separate elements. A parameter containing a comma is refused rather than silently split.

Args: job_name: package,definition as one string. parameters: Job arguments, joined with commas. notification_code: The service's two-digit notification code. confirmed: Set True only after explicit user confirmation.

fusion_update_interface_dataA

Replace the rejected rows of a partly-failed import with corrected ones.

The repair path: an import loads ten thousand rows, forty are rejected into the interface tables, and this replaces those forty rather than reloading the whole file.

load_request_id identifies WHICH load's error rows are being corrected. Pass the wrong one and the correction lands on a different batch, so read it back to the user before confirming.

Args: process_name: The import job's process name. load_request_id: Request id of the load being corrected. file_path: Local file holding the corrected rows. account: UCM account for the upload. confirmed: Set True only after explicit user confirmation.

fusion_find_uploaded_filesA

List UCM document ids matching a file prefix in an account.

Read-only, and the cheapest way to answer "did my upload actually land, and in the right place?" -- which matters because a bulk upload's characteristic failure is a file sitting in an account no import job looks at.

Args: prefix: File-name prefix to search for. account: UCM account, e.g. fin$/payables$/import$.

fusion_job_statusA

Check a scheduled process submitted with fusion_submit_job.

finished tells you whether to stop polling. An unrecognised state counts as still running on purpose: abandoning a job because its state is not in our list would be worse than one more poll.

Args: request_id: The id returned by fusion_submit_job.

fusion_list_reportsA

List the BI Publisher reports this server may run. Call this FIRST.

This is the whole of what is runnable. Arbitrary SQL does not work on a pod that refuses lexical substitution, so an administrator builds each report's data model once -- writing its SQL with :bind variables -- and registers it under fusion.reports in config.yaml. Everything an agent can ask this pod is therefore some registered report plus a choice of bind values, which is also the security boundary: no report, no query.

For each report you get its parameters (the bind names it accepts; anything else is rejected before a round trip) and its defaults (the values used for the ones you omit). If the answer you need has no report, say so and hand the user the SQL and parameters it would need -- that is a one-time administrator task, not something this server can work around.

fusion_run_reportA

Run one registered BI Publisher report with bind values.

The statement lives in the report's data model, written once by an administrator; you choose only the values for the binds it declares. That is the same trust boundary the Fusion UI enforces, and on a pod that refuses lexical substitution it is the only path that reaches the database at all.

Discover names and parameters with fusion_list_reports. An unknown report name, or a parameter the report does not declare, is rejected here -- before any round trip -- with a message listing what IS accepted; correct the call from that list rather than guessing, because a dropped parameter would run the report on its stored defaults and return a plausible wrong answer.

The report applies no row cap of its own, so max_rows is applied after the rows arrive: row_count is what you are given, rows_returned_by_report is what came back, and truncated is true when those differ.

Args: report: Registered report name, as listed by fusion_list_reports. params: Bind values, e.g. {"p_pattern": "%INVOICE%"}. Omitted parameters take the report's registered default. max_rows: Rows to return, clamped to the server's hard maximum. timeout_s: Per-call timeout in seconds.

fusion_adhoc_queryA

Run one ad-hoc SELECT by authoring an ephemeral report for it.

This pod refuses lexical substitution, so caller SQL cannot travel through a fixed report -- instead this call turns the SQL into a report: a data-model/report pair is created in the catalog, run once with your bind values (parameter-echo verification included), and deleted again. Guarded (read-only, deny-list), audited with the real SQL, and gated behind fusion.allow_adhoc_queries in config.yaml.

Rules the statement must follow (they are the data-model rules):

  • Single SELECT/WITH, no trailing semicolon, read-only.

  • Alias every projection with a simple name -- aliases become the XML element names. SELECT * is rejected; name the columns.

  • Reference parameters as :bind names and pass a value for every one in binds. Literals work too, but binds keep the audit trail honest.

  • Explore first: fusion_list_tables / fusion_describe_table / fusion_search_columns / fusion_describe_flexfields, and read fusion_get_hints for the _TL/_F/_ALL join traps before joining anything.

Cost note: two catalog uploads + two deletes per call. If the same shape of question will be asked again, mint it once with fusion_author_report and run it as a registered report from then on.

Args: sql: The SELECT. Every projection aliased; binds as :p_name. binds: A value for every bind the SQL references, e.g. {"p_from": "2026-01-01"}. No defaults exist here. columns: Only needed when the select list defeats alias parsing; normally derived from the SQL. max_rows: Rows to return, clamped to the server's hard maximum. timeout_s: Per-call timeout in seconds.

fusion_author_reportA

Mint a PERSISTENT report from SQL and register it for future runs.

Use this when a question will be asked again: an ad-hoc query costs catalog round trips every time, a registered report only once. The SQL becomes a BI Publisher data model + report under /Custom/MCP/, and the entry lands in the dynamic registry file (not config.yaml), immediately runnable via fusion_run_report / fusion_validate_report.

Same statement rules as fusion_adhoc_query: single guarded SELECT, every projection aliased, parameters as :bind names (discovered from the SQL automatically). Write description for the NEXT agent: say what the report returns and what each parameter means -- it is read through fusion_list_reports.

Strongly recommended: pass verify_binds with values that should return rows. The report is then run once through the normal pipeline (echo check included) before being registered, so a broken report is refused instead of registered.

Gated behind fusion.allow_report_authoring in config.yaml. A name that exists in config.yaml is never touched; force=True only overwrites reports this tool itself created.

Args: name: Registry key, lower_snake_case, e.g. open_pos_by_bu. sql: The SELECT with :bind parameters, every projection aliased. description: For the next agent -- returns what, parameters mean what. defaults: Values used for binds a caller omits, e.g. {"p_type": "%"}. verify_binds: Bind values for a one-off verification run after upload. force: Overwrite this tool's own earlier report of the same name.

fusion_bootstrapA

Create the exploration reports this pod is missing, from shipped SQL.

Call this when fusion_list_tables / fusion_describe_table / fusion_search_columns / fusion_describe_flexfields fail on a pod that is otherwise healthy: each of them runs a BI Publisher report that has to exist in the catalog first, and a fresh pod has none of them. A passing fusion_health_check does NOT cover this -- the connection can be perfect while the catalog is empty.

No SQL is accepted from you. The four statements are files inside this package, held to their declared columns and binds by offline tests; this tool only chooses whether to send them. It is therefore much narrower than fusion_author_report, and gated separately (fusion.allow_bootstrap).

Safe to call twice: an object already in the catalog is reported as skipped, never replaced and never deleted. Replacing one is a human decision, made with fusion-query-bootstrap --force in a shell.

Each new report is run once with pillar-neutral probe binds and its parameter echo checked. A report whose echo fails is left in the catalog but NOT registered, and reported as failed -- it would otherwise answer the same thing whatever it was asked.

Args: only: Bootstrap just these reports, e.g. ["list_tables"]. Default: all four. folder: Catalog folder to create them in. data_source: BI Publisher data source. Default ApplicationDB_FSCM; an HCM-only pod needs its own, e.g. ApplicationDB_HCM.

fusion_validate_queryA

Validate free SQL -- ONLY on a pod that substitutes lexical parameters.

Same engine as fusion_validate_report, pointed at a statement instead of a registered report. It therefore inherits fusion_run_query's limitation: on a pod with lexical substitution disabled the statement never reaches the database (SELECT * FROM (), ORA-00903), and the response hint says so. Use fusion_validate_report instead there -- validating a registered report's output is the path that works, and validation is the point of this server.

Where free SQL does run, this is what turns "the query ran" into "the query is correct". Supply the facts the user already trusts -- a total read off the Fusion UI, a known document number, an exported spreadsheet -- and every expectation is evaluated (never short-circuited) so one round trip tells you everything that is wrong.

Read the diff on failure; it is a repair signal, not just a verdict. A unique_key failure means join fan-out. An expected row that is absent means either the WHERE is too tight or Fusion row-level Data Security hides it from the service account -- add a broadened cross_check COUNT(*) to tell those apart before rewriting the query.

Two honest limits: a passing fixture proves consistency with the ground truth supplied, not universal correctness (use at least two independent expectation types); and results reflect what the single service account is allowed to see.

Args: sql: The SELECT statement to validate. expectations: Inline expectation objects. Mutually exclusive with fixture. fixture: Name of a saved fixture to validate against. datasource: Configured datasource key. max_rows: Row cap for the validation run; keep it above the expected count. timeout_s: Per-call timeout in seconds.

fusion_validate_reportA

Run a registered report once and check its results against known ground truth.

This is the tool that makes an answer trustworthy rather than merely plausible, and it is the validation path that works on a pod without lexical substitution. Supply the facts the user already trusts -- a total read off the Fusion UI, a known document number, an exported spreadsheet -- and every expectation is evaluated (never short-circuited), so one round trip tells you everything that is wrong rather than the first thing.

Read the diff on failure; it is a repair signal, not just a verdict. Because the SQL lives in the report's data model, some repairs are not yours to make:

  • unique_key failed -- join fan-out inside the report: a _TL join without LANGUAGE = USERENV('LANG'), or an _F/_M join without an effective-date predicate. Report it to whoever owns the data model.

  • an expected row is absent -- either the report's filter is too tight, or Fusion row-level Data Security hides that row from the service account. Vary the bind values to test the first before assuming the second.

  • an aggregate is off -- cancelled/draft rows still included, mixed currencies summed together, or fan-out multiplying the amount.

cross_check needs to run an independent SQL statement, which this pod refuses. It comes back as one failed expectation explaining exactly that, while every other expectation is still evaluated normally -- so do not read its failure as a data problem.

Two honest limits: a passing fixture proves consistency with the ground truth supplied, not universal correctness (use at least two independent expectation types); and results reflect what the single service account is allowed to see.

Args: report: Registered report name, as listed by fusion_list_reports. params: Bind values for the report. Omitted parameters take its defaults. expectations: Inline expectation objects. Mutually exclusive with fixture. fixture: Name of a saved fixture to validate against. max_rows: Rows to validate over; keep it above the expected row count, as the report itself applies no cap and this one is applied here. timeout_s: Per-call timeout in seconds.

fusion_save_fixtureA

Persist a validated set of expectations as a reusable regression test.

Worth doing every time a validation passes: Fusion quarterly patches change views and add columns, and a saved fixture turns "this was right in August" into something you can re-run in November.

For a report validation, record the report and its bind values in description (and the report's call descriptor in sql) so the fixture says what it was proved against.

Values in configured redacted columns are masked before the file is written.

Args: name: Fixture name; letters, digits, _ and - only. expectations: The expectation objects to persist. description: Where the ground truth came from -- record the source and date. datasource: Datasource (or report) the fixture applies to. sql: The validated query or report call, stored alongside for reference. overwrite: Replace an existing fixture of the same name.

fusion_list_fixturesA

List saved ground-truth fixtures available for validation.

fusion_get_fixtureA

Read one saved fixture, including its expectations and stored SQL.

Args: name: The fixture name as reported by fusion_list_fixtures.

fusion_get_hintsA

Curated Oracle Fusion schema knowledge: the traps that corrupt results silently.

Read the relevant topic before trusting a joined result. A missing _TL language filter or _F effective-date predicate does not raise an error -- it multiplies the rows, and the report looks like it worked. That guidance now applies to the SQL an administrator writes into a report's data model, which is where every join on this pod lives.

Args: topic: One of the knowledge-base topics (effective_dating, translations, multi_org, lookups, flexfields, aliasing, po, ap, inv, om, gl, suppliers, getting_started). Omit for the whole base.

fusion_health_checkA

Verify the whole path to the pod: credentials, report, bind values, parsing.

Probes something that actually works: it runs the registered list_tables report with a narrow pattern, so a green answer proves credentials, the catalog path, the bind-parameter shape and the XML parser all at once. On a pod with no reports registered it falls back to the legacy lexical probe (SELECT 1 AS N FROM DUAL), which is also what detects whether that pod wants the flat or the item-wrapped parameterNameValues JSON when param_shape is auto.

status is degraded -- not ok -- when the round trip succeeded but the report returned no rows: the transport works and the report is wrong, which is a different problem from an unreachable pod.

Args: datasource: Configured datasource key to probe (legacy path, and the datasource summary). An unknown name is reported as an error. report: Report to probe with instead of list_tables. pattern: Value for the probe report's p_pattern bind.

Prompts

Interactive templates invoked by user choice

NameDescription
fusion-query-workflowThe protocol for discovering, running and validating Fusion reports.

Resources

Contextual data attached and managed by the client

NameDescription
Oracle Fusion query hintsThe full Fusion schema knowledge base as Markdown.

TDQS

A4.2/5.0

Scored across 40 tools

Disambiguation4/5

Most tools have crisp distinct purposes, and the domain prefixes (docs_, api_, soap_) in names do real disambiguation work. However, there are several deliberate parallel pairs — live vs docs-snapshot describe/search, run_query vs adhoc_query vs run_report, validate_report vs validate_query — whose boundaries are only clear after reading the lengthy descriptions, so a skimming agent could easily pick the wrong query or validation path.

Naming Consistency4/5

The set overwhelmingly follows fusion_<verb>_<noun> (or fusion_<module>_<verb>_<noun> for docs_/api_/soap_), which is a strong, predictable convention across 40 tools. A few outliers break the verb-first shape — fusion_health_check, fusion_adhoc_query, fusion_bootstrap — but they remain readable and do not undermine the overall pattern.

Tool Count3/5

40 tools is heavy and exceeds the comfortable band, though the count reflects a genuinely broad scope: SQL, BI reports, REST, SOAP, ESS/FBDI, approvals, pods, and docs each form their own cluster. The live-vs-docs-snapshot duplications and the three overlapping query execution paths inflate the number without adding new capability, making the surface larger than it needs to be.

Completeness4/5

The surface covers the full lifecycle across channels: schema exploration, query execution (report, ad-hoc, free-SQL), validation with fixtures, guarded write actions (REST, SOAP, ESS, FBDI), monitoring (job status/log, health check), and repair (bootstrap, interface-data correction). Minor gaps exist — no fixture deletion, no report deletion, no direct REST record GET by ID, no job cancellation — but all are workable around or explained as inherent constraints.

Maintenance

ActivityMaintained
ResponsivenessNo issues