diff --git a/.changeset/fiori-mcp-server-adp-tools.md b/.changeset/fiori-mcp-server-adp-tools.md new file mode 100644 index 00000000000..d6a9f10936a --- /dev/null +++ b/.changeset/fiori-mcp-server-adp-tools.md @@ -0,0 +1,5 @@ +--- +"@sap-ux/fiori-mcp-server": minor +--- + +FEAT: add ADP top-level tools (generate_adaptation_project, open_adaptation_editor, adp_controller_extension) diff --git a/packages/fiori-mcp-server/jest.config.mjs b/packages/fiori-mcp-server/jest.config.mjs index d7d3ac253bf..34c9309b088 100644 --- a/packages/fiori-mcp-server/jest.config.mjs +++ b/packages/fiori-mcp-server/jest.config.mjs @@ -5,7 +5,7 @@ export default { setupFilesAfterEnv: ['/test/jest.setup.ts'], modulePathIgnorePatterns: [...baseConfig.modulePathIgnorePatterns, '/test/data/'], transformIgnorePatterns: [ - 'node_modules/(?!(@huggingface|@sap-ux|@sap-ux-private|@sap/ux-specification|@sap/ux-cds-compiler-facade)/)' + 'node_modules/(?!(@huggingface|@sap-ux|@sap-ux-private|@sap/ux-specification|@sap/ux-cds-compiler-facade|@sap-devx)/)' ], // Routes @sap/ux-cds-compiler-facade to .mjs ESM mock resolver: '/jest.resolver.cjs', @@ -24,6 +24,7 @@ export default { // Stub fe-fpm-writer to prevent the CJS require chain: ux-specification → fe-fpm-writer → // fiori-annotation-api → @sap/ux-cds-compiler-facade which conflicts with the ESM mock '^@sap-ux/fe-fpm-writer$': '/test/__mocks__/@sap-ux/fe-fpm-writer.cjs', + '^@sap-ux/adp-tooling$': '/test/__mocks__/@sap-ux/adp-tooling.cjs', '^@sap/ux-specification$': '/test/__mocks__/@sap/ux-specification.mjs', ...baseConfig.moduleNameMapper, '^@huggingface/transformers$': '/test/__mocks__/@huggingface/transformers.cjs' diff --git a/packages/fiori-mcp-server/package.json b/packages/fiori-mcp-server/package.json index f6b11ac385a..349b56c412b 100644 --- a/packages/fiori-mcp-server/package.json +++ b/packages/fiori-mcp-server/package.json @@ -22,8 +22,9 @@ "license": "Apache-2.0", "main": "dist/index.js", "scripts": { + "build-esbuild-base": "esbuild --bundle --platform=node --target=node20 --outdir=dist --external:vscode --external:@lancedb/lancedb --external:@xenova/transformers --external:@sap-ux/fiori-docs-embeddings --external:@sap-ux/store --external:playwright-core --main-fields=module,main src/index.ts", "build-bundle": "node scripts/bundle.mjs", - "build-dev": "NODE_ENV=development node scripts/bundle.mjs", + "build-dev": "pnpm run build-esbuild-base --sourcemap=inline", "build-compile": "tsc --noEmit", "build": "npm-run-all build-compile build-bundle", "watch": "tsc --watch", @@ -47,7 +48,8 @@ "dist", "!dist/**/*.map", "assets/icon.svg", - "assets/icon.png" + "assets/icon.png", + "skills" ], "bin": { "fiori-mcp": "./dist/index.js" @@ -82,6 +84,7 @@ "@types/json-schema": "7.0.15", "@types/mem-fs": "1.1.2", "@types/mem-fs-editor": "7.0.1", + "@sap-ux/adp-tooling": "workspace:*", "cross-env": "10.1.0", "esbuild": "0.27.4", "i18next": "25.10.10", @@ -89,6 +92,7 @@ "mem-fs-editor": "9.4.0", "npm-run-all2": "8.0.4", "os-name": "4.0.1", + "playwright-core": "1.58.2", "promptfoo": "0.121.15", "xml-formatter": "3.7.0", "ts-node": "10.9.2", diff --git a/packages/fiori-mcp-server/skills/sap-fiori-adp-controller-extension-flow/SKILL.md b/packages/fiori-mcp-server/skills/sap-fiori-adp-controller-extension-flow/SKILL.md new file mode 100644 index 00000000000..25c2d72b5bf --- /dev/null +++ b/packages/fiori-mcp-server/skills/sap-fiori-adp-controller-extension-flow/SKILL.md @@ -0,0 +1,389 @@ +--- +name: sap-fiori-adp-controller-extension-flow +author: sap-fiori-tools +version: 0.0.1 +description: Use when making RTA changes to a SAP Fiori adaptation project via the adaptation editor — adding/removing UI elements, changing properties, renaming labels, hiding controls, or extending controllers and fragments. +--- + +# ADP Controller Extension Flow + +Drive Runtime Authoring (RTA) in the SAP Fiori adaptation editor through the **`run_rta_workflow_step`** MCP tool exposed by `fiori-mcp-server`. The tool handles browser automation server-side; this skill orchestrates the step sequence and the AI decisions between steps. + +> **Tool boundary.** `run_rta_workflow_step` is a **skill-internal** dispatcher. Don't call it ad-hoc — the value of this skill is in the AI decision points between steps (control selection, action selection, payload preparation). Calling out of order will fail with a descriptive error. + +## Prerequisites + +- `fiori-mcp` server running (provides `run_rta_workflow_step`) +- Adaptation editor URL (from the `sap-fiori-adp-project-setup` skill or user-provided) +- A Chromium-based browser the server can launch. Resolution order: + 1. `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` env var (absolute path to a Chromium binary) + 2. `PLAYWRIGHT_BROWSER_CHANNEL` env var (`chrome`, `msedge`, `chrome-beta`, etc.) + 3. System Google Chrome (default channel) + 4. Playwright-managed Chromium (auto-fallback if 1–3 fail) + +### Installing fiori-mcp + +This skill requires the `@sap-ux/fiori-mcp-server` MCP server. It is part of the [SAP UX Tools](https://github.com/SAP/open-ux-tools) open-source project. + +**Install via npm:** + +```bash +npm install -g @sap-ux/fiori-mcp-server +``` + +**Configure in your MCP client** (e.g. Claude Desktop `claude_desktop_config.json`, or VS Code `settings.json`): + +```json +{ + "mcpServers": { + "fiori-mcp": { + "command": "npx", + "args": ["-y", "@sap-ux/fiori-mcp-server"] + } + } +} +``` + +After adding the server, restart your MCP client. The `run_rta_workflow_step` tool should appear in the available tools list. + +### Chromium fallback (no system Chrome) + +If no system Chrome is found and no env override is set, the server falls back to Playwright's bundled Chromium. The bundle is **not** included with `playwright-core`, so it has to be installed once on the host machine: + +```bash +npx playwright install chromium +``` + +When `start` fails with `Executable doesn't exist at .../chromium-XXXX/...`, run that command and retry. Mention to the user that the first install downloads ~120 MB and can take a minute. Subsequent runs reuse the cached browser. + +Detection sequence the skill should follow on a `start` failure that mentions a missing browser: +1. Inspect the error message — if it references a missing Chromium binary, prompt the user to run `npx playwright install chromium` (or run it on their behalf if they consent). +2. After install completes, retry `start` with the same payload. +3. If detection still fails, ask the user to set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to a known Chrome/Chromium binary. + +## Tool Contract + +A single tool, dispatched by the `step` argument. Pass `payload` for steps that need it. Subsequent steps reuse the `sessionId` returned by `start`. + +| step | sessionId | payload | returns | +|------|-----------|---------|---------| +| `start` | — | `{ site: string, frameId?: string }` | `{ sessionId, rtaStarted: true }` | +| `get_overlays` | required | — | `{ overlays: Overlay[] }` | +| `get_actions` | required | `{ controlId }` | `{ actions: Action[] }` | +| `get_context` | required | `{ controlId, actionId }` | `{ context }` | +| `call_action` | required | `{ controlId, actionId, actionPayload }` | `{ success: boolean }` | +| `save` | required | — | `{ saved: boolean }` | +| `stop` | required | — | `{ stopped: true }` | + +`Overlay` = `{ overlayId, controlId, label, controlType }`. +`Action` = `{ id, label?, payload?: [{ name, type, required?, description? }] }`. +For the standard adaptation editor preview iframe, pass `frameId: "preview"` in the `start` payload. + +## Actions Reference + +These are the canonical RTA actions returned by `get_actions`. **Always pick by `id` from this table — never invent or guess action IDs.** If `get_actions` returns an `id` not listed here, treat it as unknown and ask the user before proceeding. + +### `CTX_ADDXML` — Add: Fragment + +Insert an XML fragment as a child of the selected control. This is the action for "add a button / field / column / section" via a fragment file. + +| Field | Type | Required | Description | +|---|---|---|---| +| `fragmentPath` | string | yes | Path to the fragment XML, formatted `fragments/.fragment.xml`. | +| `targetAggregation` | string | yes | Aggregation of the parent control where the fragment is inserted (e.g. `content`, `items`, `headerContent`). Read from `get_context`. | +| `index` | int | yes | Position within the aggregation. Use `0` for first, the current child count for "append at end". | + +### `CTX_EXTEND_CONTROLLER` — Extend Controller + +Attach a controller extension JS file to a view. Use this when fragment event handlers reference methods that need a JS implementation, or whenever the user asks for behavior changes (handlers, lifecycle hooks, formatters). + +| Field | Type | Required | Description | +|---|---|---|---| +| `codeRef` | string | yes | Path to the controller extension, formatted `coding/.js`. | +| `viewId` | string | yes | Any control id inside the view, or the view id itself. The `controlId` of the current selection works. | +| `instanceSpecific` | boolean | no | `true` extends only this view instance; `false`/omitted extends every view that uses this controller. | + +### Disambiguation by intent + +| User intent | Action | +|---|---| +| "add a button / field / column / section / dialog opener" | `CTX_ADDXML` (fragment carries the new control) | +| "make this button do X" / "open a dialog when …" / "change behavior" | `CTX_EXTEND_CONTROLLER` (handler lives in the controller extension) | +| "add a button that opens a dialog" | **Both**, in this order: `CTX_ADDXML` for the button, then `CTX_EXTEND_CONTROLLER` for the press handler. Run as two separate iterations of Steps 4–8. | + +If `get_actions` returns more actions than these for a control, surface them to the user rather than picking — this skill is only authoritative for `CTX_ADDXML` and `CTX_EXTEND_CONTROLLER`. + +## Confidence & HITL Gating + +Three steps in this workflow are AI judgment calls, not deterministic lookups: control selection (Step 3), action selection (Step 5), and payload preparation (Step 7). Wrong choices at these points either edit the wrong UI or silently corrupt the change. To make HITL reliable, **rate every such decision with a self-assessed confidence in `[0, 1]`** and gate behavior on per-decision thresholds. + +### Confidence rubric + +Anchor your self-rating to evidence, not vibes: + +| Confidence | When to assign it | +|---|---| +| **0.95–1.00** | Exact, unambiguous match. Single candidate. Wording in the user's instruction maps 1:1 to one option. | +| **0.85–0.94** | Strong match. Top candidate is clearly best; runner-up is materially worse. All required fields derived from explicit context or instructions. | +| **0.65–0.84** | Likely match. Top candidate is plausible but the runner-up is also reasonable, or one non-critical field had to be inferred. | +| **0.40–0.64** | Weak match. Several plausible candidates, or a required field was inferred from weak signals. | +| **< 0.40** | No real match. Don't pick — list options and ask. | + +### Three bands → three behaviors + +| Band | Range | Behavior | +|---|---|---| +| **High** | ≥ high threshold | Proceed silently. Record the choice + confidence in the final summary. | +| **Medium** | ask threshold ≤ x < high threshold | Proceed but **announce** the choice on one line: `Using (confidence 0.78). Continuing — interrupt to change.` Do not stop. | +| **Low** | < ask threshold | **Stop and ask.** Present the top 2–3 ranked candidates with their confidences. Never guess. | + +### Medium-band lock-in (critical) + +A Medium-band announcement is a **soft commitment, not a draft**. The next tool call must use the announced choice exactly. If downstream evidence later invalidates the choice — for example, `get_actions` doesn't return the action you expected, `get_context` returns a structure that doesn't match, or the action call errors — you MUST: + +1. **Stop.** Do not silently switch to a different control, action, or payload. The user already saw "Using X" and is reasonably expecting X. +2. **Report the contradiction explicitly:** what you announced, what came back, and what that means. +3. **Ask the user how to proceed.** Offer concrete alternatives where possible (e.g. "(a) try a different control, (b) use a different action on the same control, (c) stop"). + +Silently revising a Medium-band choice is the single worst HITL failure mode this skill protects against. The user's "interrupt to change" affordance is real-time only; once you've moved past it, ask explicitly before changing course. + +### Expected-action absence is a hard stop + +When you derived an expected action id in Step 5 from the user's intent (e.g. "add a button" → `CTX_ADDXML`) and the chosen control doesn't expose that action, **this is an ask point, not a search heuristic**. Do not silently iterate to a different control hoping the action appears. Instead: + +1. **Stop.** Tell the user, in plain terms, that the control they're working with doesn't support the action needed for this intent. Name the control and the missing action explicitly. +2. **Propose a similar control if one exists.** Look at the overlay list for candidates of the same or compatible `controlType` (e.g. another `OverflowToolbar`, another `Toolbar`, the parent container) and name **one specific alternative** with the reason it's similar. Do not list five — pick the closest one. +3. **Wait for explicit confirmation.** Do not switch controls until the user replies. If they confirm, restart from Step 4 (`get_actions`) on the new control and verify the expected action is present *before* announcing. +4. **If no similar control is obvious**, ask the user to point at one or to clarify what they meant — don't guess. + +This rule has special weight when the **user explicitly named the control** (e.g. "add a button to the TableToolbar"). In that case the model must not silently substitute a different control under any circumstance — the user said which one, and a missing action means the request itself is impossible as stated, which the user must be told. + +This rule applies even when the chosen control was selected in the High band. A High-band control plus an unexpected action set is a higher-priority signal than the original control-selection confidence. + +Phrasing template: + +> The `` (``) doesn't expose `` — only ``. +> +> The closest similar control on this page is `` (``), because ``. Should I switch to `` and continue, or did you mean a different control? + +### Per-decision thresholds + +| Decision | High ≥ | Ask < | Reasoning | +|---|---|---|---| +| Step 3 — Control selection | 0.80 | 0.60 | Cheap to undo if wrong (the action will fail or look obviously wrong). | +| Step 5 — Action selection | 0.85 | 0.65 | Few options, usually obvious; bump slightly higher because the wrong action causes a wrong *kind* of change. | +| Step 7 — Payload preparation | 0.90 | 0.70 | **Highest risk.** Action can succeed yet produce a broken/misplaced change. Bias toward asking. | + +### Ambiguity overrides confidence (must ask) + +The Medium band is for "I know which one and the runner-up is materially worse, but not by a wide margin." It is **not** for "two candidates look interchangeable." When two or more candidates are roughly equally plausible — same `controlType`, similar labels, both reasonable matches for the user's words — that is a **disambiguation problem, not a confidence problem**. Treat it as a hard ask regardless of the score: + +- Two or more candidates within **0.10** confidence of each other → **stop and ask**, even if the top score is in the High band on paper. +- "I'll pick the more conventional one" or "this is where row actions live" reasoning is not a tiebreaker — it's a guess. List the candidates and ask. + +Concrete example: a ListReport page has both a `TableToolbar` and a `FooterToolbar`. The user said "the toolbar." Both are `sap.m.OverflowToolbar`. Don't pick — ask. + +### Required-field rule (Step 7) + +A required field whose value cannot be derived from (a) the action's payload schema, (b) the element context, or (c) explicit user instructions **caps the whole-payload confidence at 0.55** regardless of how strong the other fields are. That puts payload prep into the "ask" band by default whenever guessing is required. + +### Multi-change runs + +When executing many changes in one session, the cumulative chance of a wrong silent decision grows. **Lower the high threshold of every decision by 0.05 once the run has > 3 changes** so that the 4th and later changes are announced more aggressively. Always re-run `get_overlays` between iterations — confidence drops if the snapshot is stale. + +### Reporting + +The final summary (Step 12) must include, per change: chosen control, action, and the confidence the model assigned to each AI decision. This makes silent high-confidence decisions auditable after the fact. + +## Workflow + +### Step 1 — Start RTA + +Call `run_rta_workflow_step` with `step: "start"`, payload `{ site, frameId: "preview" }`. Verify `rtaStarted: true`. Store the returned `sessionId`. On `false`, wait 3 s and retry once. + +### Step 2 — Get overlays + +Call `step: "get_overlays"`. Returns the editable controls on the page. + +### Step 3 — Select target control (AI decision) + +Match the user's instruction against the overlays: +- "the title" → controls with `Title` or `Header` in `controlType` +- "the table" → `sap.ui.table.Table` or `sap.m.Table` +- "button X" → `sap.m.Button` with matching label +- "toolbar" → `sap.m.Toolbar` or `sap.m.OverflowToolbar` + +**Confidence gating** (see *Confidence & HITL Gating*; thresholds: high ≥ 0.80, ask < 0.60): +- Score every plausible overlay against the instruction; rank them; assign confidence to the top candidate using the rubric. +- **High** → use the chosen `controlId` silently. +- **Medium** → announce: `Using