@salesforce/afv-skills 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +239 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +21 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +90 -0
- package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_excerpt.md +31 -0
- package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_to_config.json +50 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +76 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/sop_parsing_patterns.md +157 -0
- package/skills/automation-sandbox-post-copy-config-generate/references/source_format_handling.md +230 -0
- package/skills/dx-apexguru-scan/SKILL.md +403 -0
- package/skills/dx-apexguru-scan/examples/README.md +54 -0
- package/skills/dx-apexguru-scan/examples/sample-decoded-summary.json +176 -0
- package/skills/dx-apexguru-scan/examples/sample-full-no-runtime-response.json +26 -0
- package/skills/dx-apexguru-scan/examples/sample-succeeded-response.json +15 -0
- package/skills/dx-apexguru-scan/references/api-reference.md +81 -0
- package/skills/dx-apexguru-scan/references/authentication.md +134 -0
- package/skills/dx-apexguru-scan/references/error-handling.md +56 -0
- package/skills/dx-apexguru-scan/references/violation-catalog.md +28 -0
- package/skills/dx-apexguru-scan/scripts/build-zip.sh +87 -0
- package/skills/dx-apexguru-scan/scripts/decode-report.js +389 -0
- package/skills/dx-apexguru-scan/scripts/resolve-token.sh +151 -0
- package/skills/dx-apexguru-scan/scripts/run-scan.sh +153 -0
- package/skills/dx-apexguru-scan/scripts/scan.sh +96 -0
- package/skills/dx-apexguru-scan/scripts/validate-token.js +121 -0
- package/skills/dx-app-analytics-query/SKILL.md +2 -0
- package/skills/dx-devops-pipeline-manage/SKILL.md +263 -0
- package/skills/dx-devops-pipeline-manage/examples/common-workflows.md +177 -0
- package/skills/dx-devops-pipeline-manage/references/cli-commands.md +298 -0
- package/skills/dx-devops-pipeline-manage/references/parsing-patterns.md +134 -0
- package/skills/dx-devops-pipeline-manage/scripts/check-activation-ready.sh +34 -0
- package/skills/dx-devops-pipeline-manage/scripts/validate-org-type.sh +17 -0
- package/skills/dx-devops-pipeline-manage/scripts/verify-operation.sh +82 -0
- package/skills/dx-devops-promote/SKILL.md +214 -0
- package/skills/dx-devops-promote/examples/promotion-workflows.md +212 -0
- package/skills/dx-devops-promote/references/cli-commands.md +303 -0
- package/skills/experience-lwc-base-components-integrate/SKILL.md +176 -0
- package/skills/experience-lwc-base-components-integrate/references/lbc-expert-guidance.md +127 -0
- package/skills/experience-lwc-base-components-integrate/references/lightning-component-index.md +179 -0
- package/skills/experience-lwc-base-components-integrate/references/lightning-components.md +5429 -0
- package/skills/experience-lwc-base-components-integrate/scripts/extract-component-docs.sh +61 -0
- package/skills/experience-lwc-rtl-validate/SKILL.md +149 -0
- package/skills/experience-lwc-rtl-validate/references/rtl-expert.md +892 -0
- package/skills/experience-lwc-rtl-validate/scripts/scan-rtl-css.sh +206 -0
- package/skills/experience-lwc-typescript-migrate/SKILL.md +207 -0
- package/skills/experience-lwc-typescript-migrate/assets/dts-template.ts +15 -0
- package/skills/experience-lwc-typescript-migrate/assets/type-patterns.ts +44 -0
- package/skills/experience-lwc-typescript-migrate/scripts/find-consumers.sh +128 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +323 -0
- package/skills/experience-ui-bundle-localize/references/gotchas.md +249 -0
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +169 -0
- package/skills/experience-ui-bundle-localize/references/interpolation.md +311 -0
- package/skills/experience-ui-bundle-localize/references/label-xml.md +282 -0
- package/skills/experience-ui-bundle-localize/references/verifying.md +219 -0
- package/skills/experience-ui-bundle-localize/scripts/check-i18n-wired.sh +195 -0
- package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +100 -0
- package/skills/experience-ui-bundle-localize/scripts/check-org-api-version.sh +40 -0
- package/skills/experience-ui-bundle-localize/scripts/detect-bundle-type.sh +57 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +3 -0
- package/skills/mobile-apps-create/SKILL.md +1 -3
- package/skills/platform-custom-lightning-type-generate/SKILL.md +3 -0
- package/skills/platform-custom-lightning-type-generate/assets/primitive-types-and-constraints.md +1 -1
- package/skills/platform-mcp-tool-widget-coordinate/SKILL.md +250 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/action-name-source-prompt.md +74 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/apex-invocable-source-prompt.md +90 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/nested-object-source-prompt.md +191 -0
- package/skills/platform-mcp-tool-widget-coordinate/examples/pasted-tool-output-prompt.md +85 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/build-plan-format.md +74 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/mcp-tool-output-discovery.md +184 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/two-clt-modeling.md +128 -0
- package/skills/platform-mcp-tool-widget-coordinate/references/validation-gates.md +181 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Example: Pasted tool-output source
|
|
2
|
+
|
|
3
|
+
A complete walkthrough of the flow when the payload source is a pasted MCP tool-output JSON sample and no Apex class is available (`source = sample`).
|
|
4
|
+
|
|
5
|
+
## The prompt
|
|
6
|
+
|
|
7
|
+
> Here's what my MCP tool returns. Build a widget for it.
|
|
8
|
+
>
|
|
9
|
+
> ```json
|
|
10
|
+
> {
|
|
11
|
+
> "actionName": "GetOrderStatus",
|
|
12
|
+
> "isSuccess": true,
|
|
13
|
+
> "outputValues": {
|
|
14
|
+
> "orderId": "80100000ABC",
|
|
15
|
+
> "orderNumber": "ORD-4471",
|
|
16
|
+
> "status": "Shipped",
|
|
17
|
+
> "itemCount": 3,
|
|
18
|
+
> "orderTotal": 249.95,
|
|
19
|
+
> "expedited": true
|
|
20
|
+
> }
|
|
21
|
+
> }
|
|
22
|
+
> ```
|
|
23
|
+
|
|
24
|
+
## Phase 1 — Input selection
|
|
25
|
+
|
|
26
|
+
- Source: `sample` (the prompt pastes a tool-output envelope; no Apex class referenced).
|
|
27
|
+
- Tool API name: `getOrderStatus` (from `actionName`).
|
|
28
|
+
|
|
29
|
+
## Phase 2 — Payload discovery
|
|
30
|
+
|
|
31
|
+
Read `references/mcp-tool-output-discovery.md`, then parse the `outputValues` object. Infer each `lightning:type` from the JSON value:
|
|
32
|
+
|
|
33
|
+
| Field | JSON value | Inferred CLT `lightning:type` |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| orderId | `"80100000ABC"` (string) | `lightning__textType` |
|
|
36
|
+
| orderNumber | `"ORD-4471"` (string) | `lightning__textType` |
|
|
37
|
+
| status | `"Shipped"` (string) | `lightning__textType` |
|
|
38
|
+
| itemCount | `3` (integer) | `lightning__integerType` |
|
|
39
|
+
| orderTotal | `249.95` (fractional) | `lightning__numberType` |
|
|
40
|
+
| expedited | `true` (boolean) | `lightning__booleanType` |
|
|
41
|
+
|
|
42
|
+
Envelope keys confirmed against the sample: `actionName`, `isSuccess`, `outputValues`. **No extra nesting** — `outputValues.<field>` is flat, so bindings will be `{!$attrs.outputValues.<field>}`.
|
|
43
|
+
|
|
44
|
+
> If the sample had nested the payload one level deeper (e.g. `outputValues.data.orderId`), the response CLT would model that `data` object and every renderer binding would be `{!$attrs.outputValues.data.<field>}`. Call this out in the plan.
|
|
45
|
+
|
|
46
|
+
`payloadFields` = the 6 rows above.
|
|
47
|
+
|
|
48
|
+
## Phase 3 — Build plan (abridged)
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
MCP Tool Widget Build Plan: getOrderStatusWidget
|
|
52
|
+
|
|
53
|
+
PLAN: Render the GetOrderStatus MCP tool output as an order-status card.
|
|
54
|
+
|
|
55
|
+
TOOL / SOURCE:
|
|
56
|
+
Tool API name: getOrderStatus
|
|
57
|
+
Payload source: pasted tool-output sample
|
|
58
|
+
|
|
59
|
+
LIGHTNING TYPES:
|
|
60
|
+
Response CLT: getOrderStatusResponse (6 payload properties)
|
|
61
|
+
Envelope CLT: getOrderStatus
|
|
62
|
+
Renderer (default, bundle root): .../lightningTypes/getOrderStatus/renderer.json
|
|
63
|
+
Envelope: actionName (text), isSuccess (boolean), outputValues (c__getOrderStatusResponse)
|
|
64
|
+
|
|
65
|
+
WIDGET: getOrderStatusWidget
|
|
66
|
+
Renderer binding: each attribute → {!$attrs.outputValues.<field>}
|
|
67
|
+
Properties omitted: none
|
|
68
|
+
|
|
69
|
+
GENERATION ORDER: response CLT → widget → envelope CLT
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Proceed unless the next reply pushes back.
|
|
73
|
+
|
|
74
|
+
## Phase 4 — Generation order
|
|
75
|
+
|
|
76
|
+
1. Response CLT `getOrderStatusResponse` — `lightning__objectType`, 6 properties (numerics: `itemCount` → `lightning__integerType` in the CLT but `lightning__numberType` in the widget schema; `orderTotal` → `lightning__numberType` in both).
|
|
77
|
+
2. Envelope CLT `getOrderStatus` — `actionName`, `isSuccess`, `outputValues` → `c__getOrderStatusResponse`.
|
|
78
|
+
3. Widget `getOrderStatusWidget` — flat schema over the 6 payload fields; body binds `{!$attrs.orderNumber}` etc.
|
|
79
|
+
4. Default renderer at `lightningTypes/getOrderStatus/renderer.json` — `definition: @widget/c/getOrderStatusWidget`, each attribute `{!$attrs.outputValues.<field>}`.
|
|
80
|
+
|
|
81
|
+
## Phase 5 — Validation
|
|
82
|
+
|
|
83
|
+
- `clt-reference-integrity`: envelope `outputValues` → `c__getOrderStatusResponse`, response CLT exists, no `$schema`/`items` → **pass**.
|
|
84
|
+
- `renderer-wires-widget`: bundle-root renderer present, definition `@widget/c/getOrderStatusWidget`, all 6 bindings nested under `outputValues` → **pass**.
|
|
85
|
+
- `field-trace`: source is a sample, so INVOCABLE_FIELDS = `outputValues` keys; INVENTED empty, OMITTED empty → **pass**.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Build Plan Format
|
|
2
|
+
|
|
3
|
+
Use this template in Phase 3 to print the plan before proceeding. Fill every section. Do not abbreviate. Do not print inside a code fence the user might mistake for output — the plan is conversational.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
MCP Tool Widget Build Plan: <widgetName>
|
|
9
|
+
|
|
10
|
+
PLAN: <one line in developer-facing terms, e.g.:
|
|
11
|
+
"Render the GetAccountSummary MCP tool output with an account-summary widget">
|
|
12
|
+
|
|
13
|
+
TOOL / SOURCE:
|
|
14
|
+
Tool API name: <toolApiName>
|
|
15
|
+
Payload source: <action: Actions REST describe of <ActionApiName> | apex: class <ClassName> | sample: pasted tool-output>
|
|
16
|
+
Response class FQN: <namespace>__<ClassName>.<ResponseClass> # apex source only; omit for action/sample
|
|
17
|
+
|
|
18
|
+
LIGHTNING TYPES (two object-based CLTs of equal standing — named for what each models, not by role; both carry root-level "lightning:tags": ["mcp"]):
|
|
19
|
+
Response CLT:
|
|
20
|
+
Name: <responseCLT> # convention: <toolApiName>Response
|
|
21
|
+
Path: <pkgDir>/lightningTypes/<responseCLT>/schema.json
|
|
22
|
+
Properties: <field: lightning:type, ...> # 1:1 with response @InvocableVariable fields
|
|
23
|
+
Envelope CLT:
|
|
24
|
+
Name: <toolCLT> # convention: <toolApiName>
|
|
25
|
+
Path: <pkgDir>/lightningTypes/<toolCLT>/schema.json
|
|
26
|
+
Renderer (default, at bundle root — wires the widget): <pkgDir>/lightningTypes/<toolCLT>/renderer.json
|
|
27
|
+
Envelope properties: actionName (text), isSuccess (boolean), outputValues (c__<responseCLT>), <plus any others>
|
|
28
|
+
|
|
29
|
+
WIDGET:
|
|
30
|
+
Name: <widgetName> # convention: <toolApiName>Widget
|
|
31
|
+
Output:
|
|
32
|
+
<pkgDir>/uiWidgets/<widgetName>/<widgetName>.json
|
|
33
|
+
<pkgDir>/uiWidgets/<widgetName>/schema.json
|
|
34
|
+
<pkgDir>/uiWidgets/<widgetName>/<widgetName>.uiwidget-meta.xml
|
|
35
|
+
Schema source: derived from the response field list (name + primitive type) — a standalone contract, not tied to any Lightning Type
|
|
36
|
+
Renderer binding: each widget attribute maps to {!$attrs.outputValues.<field>}
|
|
37
|
+
Layout intent: <one-line description of the widget composition>
|
|
38
|
+
Properties omitted: <response fields the widget intentionally drops, with rationale — or "none">
|
|
39
|
+
# actionName/isSuccess never appear here — they are envelope-only and never candidates for the widget.
|
|
40
|
+
# Default omissions to declare when present in the response: isSuccess, errorMessage, status, message
|
|
41
|
+
# (operational/status indicators, not display data) — each needs its own one-line rationale, not just the field name.
|
|
42
|
+
|
|
43
|
+
SUB-SKILLS THAT WILL RUN:
|
|
44
|
+
platform-custom-lightning-type-generate (response CLT, then envelope CLT)
|
|
45
|
+
platform-widget-generate (widget bundle)
|
|
46
|
+
(renderer.json authored inline in the envelope CLT by this orchestrator)
|
|
47
|
+
|
|
48
|
+
VALIDATIONS THAT WILL RUN AFTER GENERATION:
|
|
49
|
+
Widget bundle self-validation (run by platform-widget-generate):
|
|
50
|
+
- widget schema.json parses and has the required root keys
|
|
51
|
+
- every leaf in properties has a lightning:type
|
|
52
|
+
- every {!$attrs.X} resolves to a widget schema property
|
|
53
|
+
- <name>.uiwidget-meta.xml is well-formed, root <UiWidgetBundle>, declares <widgetType>JSON</widgetType>
|
|
54
|
+
Cross-skill checks (run by this orchestrator):
|
|
55
|
+
- clt-reference-integrity: envelope CLT outputValues → c__<responseCLT>; response CLT exists; no $schema/items
|
|
56
|
+
- renderer-wires-widget: envelope CLT renderer.json (bundle root) references the widget via @widget/c/<widgetName>,
|
|
57
|
+
binding every widget property as {!$attrs.outputValues.<property>}
|
|
58
|
+
- field-trace (advisory): print response @InvocableVariable fields and widget schema properties; print the diff.
|
|
59
|
+
Invented widget fields fail; omissions not declared above warn.
|
|
60
|
+
|
|
61
|
+
GENERATION ORDER: response CLT → widget → envelope CLT (response CLT must exist before the envelope CLT references it).
|
|
62
|
+
|
|
63
|
+
----------------------------------------------------------------
|
|
64
|
+
Proceeding unless you push back (reply "no", "stop", "change X"). The plan above is the record of intent.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Notes for the model
|
|
70
|
+
|
|
71
|
+
- If the user replies with edits or declines, revise the plan and reprint. Do not assume which sections changed.
|
|
72
|
+
- Approval applies only to the plan as printed. A later request for another tool starts a new planning cycle.
|
|
73
|
+
- "Properties omitted" makes intentional drops explicit — e.g. `status`, `message`, internal IDs that do not belong on the render surface.
|
|
74
|
+
- If the payload source is a pasted sample that nests under `outputValues.data`, record that extra level here — it changes the response CLT and every renderer binding to `{!$attrs.outputValues.data.<field>}`.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# MCP Tool Output Discovery
|
|
2
|
+
|
|
3
|
+
Phase 2 resolves the payload shape that defines the response CLT and the widget schema. There are three sources, in order of preference: an invocable action API name (`action`), a pasted tool-output sample (`sample`), or an Apex Invocable class (`apex`).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Source | Use when | Authority |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `action` | An **invocable action API name** is known — directly, or from a class name that resolves to one. **Preferred.** | The org's Actions REST API describes the action's typed outputs directly — it already excludes the request wrapper and private helpers. |
|
|
10
|
+
| `sample` | The describe 404s or no org is reachable, but a pasted tool-output JSON is available. | Infers types from example values. |
|
|
11
|
+
| `apex` | Only the Apex **class** name is known, and no org and no sample are available — fallback only, may be stale relative to what's deployed. | Parses `@InvocableVariable` fields from source. |
|
|
12
|
+
|
|
13
|
+
Prefer `action` whenever an action name (directly given, or derived from a class name that resolves to a single invocable action) and an authenticated org are available — it is the same schema the platform itself exposes, so it needs no request/helper filtering and gives real field types. Fall back to `sample`, then `apex`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## `action` — resolve from the invocable action name (preferred)
|
|
18
|
+
|
|
19
|
+
The Salesforce **Actions REST API** describes any custom Apex invocable action, including its output variables and their types. This is authoritative: the `outputs` it returns are exactly the `@InvocableVariable` fields on the response class — the request wrapper and private helper classes never appear.
|
|
20
|
+
|
|
21
|
+
### 1. Confirm the action API name
|
|
22
|
+
|
|
23
|
+
For an Apex invocable action the action API name is the **Apex class name** that declares the `@InvocableMethod` (e.g. `GetAccountSummaryTest`), not the method label. If the user gave a label ("Get Account Summary") resolve it to the class name — the class name is what the endpoint path uses.
|
|
24
|
+
|
|
25
|
+
### 2. Describe the action
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sf api request rest '/services/data/v63.0/actions/custom/apex/<ActionApiName>' -o <org>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`<ActionApiName>` is the Apex class name. Use the org's API version (`v63.0` here — match `sourceApiVersion` in `sfdx-project.json` or the org's max). If the org alias is the default, `-o <org>` may be omitted. (`sf api request rest ...` is the form used elsewhere in this repo; `sf org api request rest ...` is an equivalent alias.)
|
|
32
|
+
|
|
33
|
+
To list all custom Apex actions first (when the exact name is unknown) — the action names are under `.actions[].name`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
sf api request rest '/services/data/v63.0/actions/custom/apex' -o <org> | jq -r '.actions[].name'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the describe returns 404 / `NOT_FOUND`, the action is not deployed or is not exposed as a custom Apex action — fall back to `sample` (a pasted tool-output JSON) if one is available, else `apex` (parse the class), and surface the miss.
|
|
40
|
+
|
|
41
|
+
### 3. Read the `outputs` array
|
|
42
|
+
|
|
43
|
+
The describe response has an `outputs` array. Each entry describes one payload field:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"outputs": [
|
|
48
|
+
{ "name": "accountName", "label": "Account Name", "type": "STRING", "maxOccurs": 1 },
|
|
49
|
+
{ "name": "contactCount", "label": "Contact Count", "type": "INTEGER", "maxOccurs": 1 },
|
|
50
|
+
{ "name": "totalOpportunityAmount", "label": "Total Opportunity Amount", "type": "DOUBLE", "maxOccurs": 1 }
|
|
51
|
+
],
|
|
52
|
+
"inputs": [ { "name": "accountId", "type": "ID", "required": true } ]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **Use `outputs` only.** `inputs` is the tool input (the request wrapper) — exclude it, exactly as `apex` excludes the request class.
|
|
57
|
+
- `name` → the CLT/widget property key. `label` → the property `title`.
|
|
58
|
+
- A field with `maxOccurs > 1` is a **collection** (list). For the beta, surface it to the user in the build plan — the widget renders a single response, and list payloads need a nested item CLT (out of scope for the default flow).
|
|
59
|
+
- An entry with **`"type": null` and an `"apexClass"` key instead** (e.g. `{ "name": "flightInfo", "type": null, "apexClass": "SearchFlightsAction$Flight", "maxOccurs": 1 }`) is **not missing data to default to text** — it is the describe's encoding for a field typed as another Apex class. `$` separates the outer class from the inner class, matching the `@apexClassType/<ns>__<OuterClass>$<InnerClass>` convention used elsewhere in this skill. This is the same case as a `List<ApexClass>`/nested-object field from the other two sources — see "Nested-object payload fields" below; the Actions REST API describe never exposes that class's own leaf fields, so retrieving/reading the Apex class named in `apexClass` (via `apex` §1/§3) is the correct next step, not a fallback away from `action`.
|
|
60
|
+
|
|
61
|
+
Extract the field list with `jq` (do NOT wrap in `$()`):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
sf api request rest '/services/data/v63.0/actions/custom/apex/<ActionApiName>' -o <org> \
|
|
65
|
+
| jq -r '.outputs[] | "\(.name)\t\(.type)"'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. Map Actions-API `type` → CLT `lightning:type`
|
|
69
|
+
|
|
70
|
+
| Actions API `type` | CLT `lightning:type` |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `STRING`, `TEXTAREA`, `PICKLIST`, `ID`, `REFERENCE`, `EMAIL`, `PHONE`, `URL` | `lightning__textType` |
|
|
73
|
+
| `INTEGER`, `INT`, `LONG` | `lightning__integerType` |
|
|
74
|
+
| `DOUBLE`, `DECIMAL`, `CURRENCY`, `PERCENT` | `lightning__numberType` |
|
|
75
|
+
| `BOOLEAN` | `lightning__booleanType` |
|
|
76
|
+
| `DATE` | `lightning__dateType` |
|
|
77
|
+
| `DATETIME` | `lightning__dateTimeType` |
|
|
78
|
+
|
|
79
|
+
Casing varies by API version (some return `Int`/`Double`, some `INTEGER`/`DOUBLE`) — match case-insensitively. An unrecognized `type` defaults to `lightning__textType`; note the assumption in the build plan.
|
|
80
|
+
|
|
81
|
+
> **Widget schema vs CLT schema type vocabulary** (applies to every source). The response CLT uses `lightning__integerType` for integers. The widget `schema.json` (per `platform-widget-generate`) has no integer type — all numerics are `lightning__numberType`. So integer fields are `lightning__integerType` in the *response CLT* but `lightning__numberType` in the *widget* schema. Do not copy CLT leaf types verbatim into the widget schema. Renderer bindings are strings and type-agnostic.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## `sample` — parse from a pasted tool-output JSON (fallback)
|
|
86
|
+
|
|
87
|
+
Use when the action describe 404s or no org is reachable, but a pasted tool-output sample is available.
|
|
88
|
+
|
|
89
|
+
Given a pasted envelope sample, read the `outputValues` object and infer each field's type from its value:
|
|
90
|
+
|
|
91
|
+
| JSON value | CLT `lightning:type` |
|
|
92
|
+
|---|---|
|
|
93
|
+
| string | `lightning__textType` |
|
|
94
|
+
| integer (no fraction) | `lightning__integerType` |
|
|
95
|
+
| number (fractional) | `lightning__numberType` |
|
|
96
|
+
| boolean | `lightning__booleanType` |
|
|
97
|
+
|
|
98
|
+
Confirm the envelope keys (`actionName`, `isSuccess`, `outputValues`) against the sample. If the sample nests the payload further (e.g. `outputValues.data.<field>`), the response CLT and the renderer bindings must reflect that extra level (`{!$attrs.outputValues.data.<field>}`) — surface this in the build plan, because it changes every binding.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## `apex` — parse the Invocable class (fallback)
|
|
103
|
+
|
|
104
|
+
Use when no org is reachable and no sample is available, but the `.cls` is in the project.
|
|
105
|
+
|
|
106
|
+
### 1. Locate the class
|
|
107
|
+
|
|
108
|
+
Search the local project first: `<pkgDir>/classes/<ClassName>.cls` where `<pkgDir>` = `<packageDirectories[].path>/main/default`. If absent, retrieve from the org:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
sf project retrieve start --metadata ApexClass:<ClassName>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If it exists nowhere, STOP and surface — the payload shape cannot be enumerated.
|
|
115
|
+
|
|
116
|
+
### 2. Identify the response class
|
|
117
|
+
|
|
118
|
+
The Invocable method is annotated `@InvocableMethod` and returns `List<ResponseType>`. The **response class** is that element type.
|
|
119
|
+
|
|
120
|
+
```apex
|
|
121
|
+
@InvocableMethod(label='Get Account Summary')
|
|
122
|
+
global static List<GetAccountSummaryResponse> getAccountSummary(
|
|
123
|
+
List<GetAccountSummaryRequest> requests
|
|
124
|
+
) { ... }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Response class = `GetAccountSummaryResponse` (the `List<...>` element type). **This is the payload source.**
|
|
128
|
+
- Request class = `GetAccountSummaryRequest` (the parameter element type). **Excluded** — it is the tool input, not output.
|
|
129
|
+
- Private helper classes (e.g. `private class OpportunityMetrics`) are **excluded** — not part of the invocable's externally visible schema. The Apex author signals this with `private`; respect it.
|
|
130
|
+
|
|
131
|
+
### 3. Enumerate `@InvocableVariable` fields
|
|
132
|
+
|
|
133
|
+
Read the response class block and list every field annotated `@InvocableVariable`. These become the response CLT `properties` and the widget schema properties (1:1).
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Print the response class field declarations. Do NOT wrap in $().
|
|
137
|
+
echo "INVOCABLE_FIELDS:"
|
|
138
|
+
grep -A1 '@InvocableVariable' <pkgDir>/classes/<ClassName>.cls \
|
|
139
|
+
| grep -oE '(public|global)\s+[A-Za-z0-9_<>,\s]+\s+[a-zA-Z_][a-zA-Z0-9_]*\s*;' \
|
|
140
|
+
| sed -E 's/.*\s([a-zA-Z_][a-zA-Z0-9_]*)\s*;/\1/' \
|
|
141
|
+
| sort -u
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If the grep misses multi-line annotations, read the `.cls` with the Read tool and list fields manually — but do not skip enumeration. Scope the enumeration to the **response class block only**; do not pick up `@InvocableVariable` fields from the request class.
|
|
145
|
+
|
|
146
|
+
### 4. Map Apex → CLT `lightning:type`
|
|
147
|
+
|
|
148
|
+
| Apex type | CLT `lightning:type` |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `String`, `Id` | `lightning__textType` |
|
|
151
|
+
| `Integer`, `Long` | `lightning__integerType` |
|
|
152
|
+
| `Decimal`, `Double` | `lightning__numberType` |
|
|
153
|
+
| `Boolean` | `lightning__booleanType` |
|
|
154
|
+
| `Date` | `lightning__dateType` |
|
|
155
|
+
| `Datetime` | `lightning__dateTimeType` |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Nested-object payload fields (applies to every source above)
|
|
160
|
+
|
|
161
|
+
The mapping tables above (Actions-API `type`, Apex type, JSON value/schema `type`) cover **primitive** fields. A field can also be typed as **another Apex class** instead of a primitive — e.g. `GetFlightDetailsAction.FlightDetailsResponse.flightInfo`, typed `SearchFlightsAction.Flight`. This is a second, additive branch — check every payload field against it, regardless of which of the three sources produced the field list:
|
|
162
|
+
|
|
163
|
+
- **Actions API**: an output entry with `"type": null` and an `"apexClass": "<OuterClass>$<InnerClass>"` key, rather than a primitive `STRING`/`INTEGER`/etc. `type` (see step 3 above).
|
|
164
|
+
- **`apex`**: an `@InvocableVariable` field whose declared type is not `String`/`Id`/`Integer`/`Long`/`Decimal`/`Double`/`Boolean`/`Date`/`Datetime` but another Apex class.
|
|
165
|
+
- **`sample`**: a property whose JSON value/schema `type` is `"object"` (not a primitive).
|
|
166
|
+
|
|
167
|
+
**Never** model such a field as `{"type":"object"}` (opaque, unrenderable) or inline it as a nested `lightning__objectType` (rejected by the CLT metaschema — same rule as the envelope↔response relationship). Instead:
|
|
168
|
+
|
|
169
|
+
1. Type the response CLT property as `"@apexClassType/<ns>__<OuterClass>$<InnerClass>"` (e.g. `"@apexClassType/c__SearchFlightsAction$Flight"`) — the same convention `platform-custom-lightning-type-generate` documents for Apex-backed CLTs.
|
|
170
|
+
2. Enumerate the referenced Apex class's own fields (its own `@InvocableVariable`/public members) — these become the widget's leaf properties. The object field itself never appears on the widget.
|
|
171
|
+
3. Bind the renderer one level deeper: `{!$attrs.outputValues.<objectField>.<leaf>}` (e.g. `{!$attrs.outputValues.flightInfo.flightId}`), not `{!$attrs.outputValues.<objectField>}`.
|
|
172
|
+
4. A field typed `List<ApexClass>` (a list of nested objects, not a single one) is out of scope for the beta single-response flow — surface it in the build plan like a `maxOccurs > 1` scalar rather than emitting a schema for it.
|
|
173
|
+
|
|
174
|
+
See `references/two-clt-modeling.md` ("Nested-object payload fields") for the worked JSON, and `examples/nested-object-source-prompt.md` for the full end-to-end walkthrough.
|
|
175
|
+
|
|
176
|
+
## Output of Phase 2
|
|
177
|
+
|
|
178
|
+
`payloadFields` — an ordered list of `{ name, title, lightning:type }` describing the response payload (primitive fields), plus any nested-object fields resolved to `@apexClassType/...` per above. This drives:
|
|
179
|
+
|
|
180
|
+
- the **response CLT** `properties` (CLT type vocabulary, `lightning__integerType` allowed, `@apexClassType/...` for nested-object fields),
|
|
181
|
+
- the **widget schema** `properties.attributes.properties` (widget type vocabulary, numerics → `lightning__numberType`; nested-object fields flattened to their leaf properties),
|
|
182
|
+
- the **renderer** attribute bindings (`{!$attrs.outputValues.<name>}` for primitives, `{!$attrs.outputValues.<objectField>.<leaf>}` for nested-object leaves).
|
|
183
|
+
|
|
184
|
+
Whichever source produced the fields, record it in the build plan so the reviewer knows whether the schema came from the live org (`action`), source (`apex`), or an example (`sample`).
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Two-CLT Modeling for an MCP Tool Output
|
|
2
|
+
|
|
3
|
+
A custom MCP server tool backed by an Apex Invocable Action returns the platform's **invocable-action result envelope**. The real payload the tool consumer cares about lives under `outputValues`; the surrounding fields (`actionName`, `isSuccess`, `errors`, `sortOrder`, `version`, …) are envelope metadata.
|
|
4
|
+
|
|
5
|
+
To render this with an HXL widget we model it as **two object-based CLTs** and wire them with a renderer that bridges the nesting. Both are ordinary CLTs of equal standing — nothing in the platform or the metaschema distinguishes an "envelope type" from a "response type." The only reason two files exist is that one CLT (the envelope) must reference the other (the response) by name via `c__<name>`, and a CLT cannot reference itself — so the two need distinct deployed names, nothing more. Don't invent a role-label pair for the two CLTs themselves ("Payload CLT"/"Envelope CLT", "Outer CLT"/"Inner CLT") — name and describe each by what it actually models (see below), and in prose refer to them by that same identifier: "the `<toolApiName>` envelope" / "the `<toolApiName>Response`". ("Payload" and "response" remain fine as ordinary words for the data itself — e.g. "response fields", "the payload the tool consumer cares about" — the rule is about not naming or labeling the *CLTs* by an invented role.)
|
|
6
|
+
|
|
7
|
+
## Naming convention
|
|
8
|
+
|
|
9
|
+
| Artifact | Convention | Example |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Envelope CLT | `<toolApiName>` | `getFlightDetails` |
|
|
12
|
+
| Response CLT | `<toolApiName>Response` | `getFlightDetailsResponse` |
|
|
13
|
+
| Widget | `<toolApiName>Widget` | `getFlightDetailsWidget` |
|
|
14
|
+
|
|
15
|
+
- `<toolApiName>` is derived **deterministically from the Apex class / Invocable Action name**, not the label: lower-camelCase the class name and strip a trailing `Action`, `Test`, or `WidgetAction` suffix if present.
|
|
16
|
+
- `AccountSummaryWidgetAction` → `accountSummary`
|
|
17
|
+
- `GetFlightDetailsAction` → `getFlightDetails`
|
|
18
|
+
- `GetAccountSummaryTest` → `getAccountSummary`
|
|
19
|
+
- Only when no class/action name is available at all (e.g. a bare `sample` with no `actionName` resolvable to a class) fall back to camelCasing the tool/action **label** (e.g. `Get Account Summary` → `accountSummary`).
|
|
20
|
+
- Both CLT names are derived from the **same single `<toolApiName>`** — there is no separate naming decision to make per artifact, and no free-standing role word (no "Result", "OutputValues", "Payload", "Envelope", and no `_CLT` suffix either — a Lightning Type is identified by living under `lightningTypes/`, not by a suffix on its name). `Response` is not a role label; it is literally what the class is (the Invocable Action's declared `List<...Response>` return-element type) — the same word the Apex source itself already uses (e.g. `GetAccountSummaryResponse`, `FlightDetailsResponse`).
|
|
21
|
+
- The envelope CLT is *structurally* generic but **cannot be a single shared CLT** — its `outputValues` must be typed to a tool-specific response CLT via `c__<responseCLT>`. One envelope CLT per tool.
|
|
22
|
+
|
|
23
|
+
> **Naming convention supersedes the earlier hand-verified prototype.** Two bundles were manually fixed and successfully deployed during development using an older `<toolApiName>Result` / `<toolApiName>OutputValues` naming pair — that proved the *structural* pattern (two object-based CLTs, `@apexClassType` for nested fields, two-level renderer bindings) deploys correctly. The naming convention above replaces those two suffixes; every other structural rule in this file (root keys, `unevaluatedProperties`, the `c__` reference, nested-object typing, renderer bindings) is unchanged and still matches what was verified.
|
|
24
|
+
|
|
25
|
+
## Response CLT
|
|
26
|
+
|
|
27
|
+
Object-based CLT whose `properties` are exactly the response `@InvocableVariable` fields (1:1). Root `lightning:type` is `lightning__objectType`. `platform-custom-lightning-type-generate` injects and enforces `"unevaluatedProperties": false` on every object-based CLT (its metaschema rejects a CLT without it) — this orchestrator does not fight that, it matches it in every example and every generated file. Give both CLTs a real, tool-specific `description` (never `""`) — it is what a consumer sees when picking a referenced CLT. Both CLTs also carry a root-level `"lightning:tags": ["mcp"]` — it marks the type as MCP-tool-generated per `platform-custom-lightning-type-generate/assets/primitive-types-and-constraints.md`.
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"title": "Get Account Summary Response",
|
|
32
|
+
"description": "Response fields from the GetAccountSummaryTest invocable-action (GetAccountSummaryResponse)",
|
|
33
|
+
"type": "object",
|
|
34
|
+
"lightning:type": "lightning__objectType",
|
|
35
|
+
"lightning:tags": ["mcp"],
|
|
36
|
+
"unevaluatedProperties": false,
|
|
37
|
+
"properties": {
|
|
38
|
+
"accountName": { "title": "accountName", "lightning:type": "lightning__textType" },
|
|
39
|
+
"accountIndustry":{ "title": "accountIndustry","lightning:type": "lightning__textType" },
|
|
40
|
+
"contactCount": { "title": "contactCount", "lightning:type": "lightning__integerType" },
|
|
41
|
+
"totalOpportunityAmount": { "title": "totalOpportunityAmount", "lightning:type": "lightning__numberType" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Nested-object payload fields (a second, additive case)
|
|
47
|
+
|
|
48
|
+
The example above covers a **flat** payload — every `@InvocableVariable` field is a primitive. Some invocable responses instead have a field whose type is **itself an Apex class** (e.g. `GetFlightDetailsAction.FlightDetailsResponse.flightInfo`, typed `SearchFlightsAction.Flight`). Both shapes are in scope; pick the branch per field:
|
|
49
|
+
|
|
50
|
+
- **Never** type that property as a bare `{"type":"object"}` (opaque, unrenderable, and not what deploys) and **never** inline it as a nested `lightning__objectType` (rejected by the CLT metaschema, same as the envelope↔response relationship below).
|
|
51
|
+
- **Do** type it as `"@apexClassType/<ns>__<OuterClass>$<InnerClass>"` — e.g. `"lightning:type": "@apexClassType/c__SearchFlightsAction$Flight"` — exactly the Apex-backed-CLT convention `platform-custom-lightning-type-generate` already documents for `@apexClassType/namespace__ClassName$InnerClass`.
|
|
52
|
+
- The **widget** and **renderer** then flatten through it — see the nested-binding note at the end of the "Default renderer" section below, and the full walkthrough in `examples/nested-object-source-prompt.md`.
|
|
53
|
+
- A field typed `List<ApexClass>` (a list of nested objects) is out of scope for the beta single-response flow — surface it in the build plan rather than emitting a schema for it, the same way a `maxOccurs > 1` scalar is surfaced.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"title": "Get Flight Details Response",
|
|
58
|
+
"description": "Response fields from the GetFlightDetailsAction invocable-action (FlightDetailsResponse)",
|
|
59
|
+
"type": "object",
|
|
60
|
+
"lightning:type": "lightning__objectType",
|
|
61
|
+
"lightning:tags": ["mcp"],
|
|
62
|
+
"unevaluatedProperties": false,
|
|
63
|
+
"properties": {
|
|
64
|
+
"flightInfo": {
|
|
65
|
+
"title": "Flight Info",
|
|
66
|
+
"lightning:type": "@apexClassType/c__SearchFlightsAction$Flight"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Envelope CLT
|
|
73
|
+
|
|
74
|
+
Object-based CLT that mimics the tool-result envelope. `outputValues` is typed to the response CLT via the referenced-CLT pattern `c__<responseCLT>` — **not** inlined as a nested `lightning__objectType` (nested object typing is rejected by the CLT metaschema; see `platform-custom-lightning-type-generate`).
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"title": "Get Account Summary",
|
|
79
|
+
"description": "Invocable-action result envelope for the GetAccountSummaryTest MCP tool",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"lightning:type": "lightning__objectType",
|
|
82
|
+
"lightning:tags": ["mcp"],
|
|
83
|
+
"unevaluatedProperties": false,
|
|
84
|
+
"properties": {
|
|
85
|
+
"actionName": { "title": "actionName", "lightning:type": "lightning__textType" },
|
|
86
|
+
"isSuccess": { "title": "isSuccess", "lightning:type": "lightning__booleanType" },
|
|
87
|
+
"outputValues": { "title": "outputValues", "lightning:type": "c__getAccountSummaryResponse" }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- The `c__<responseCLT>` string is the referenced type's **registered identifier / FQN**, not its `title`. It must match the response CLT's deployed name.
|
|
93
|
+
- The response CLT must be deployed **before** the envelope CLT.
|
|
94
|
+
- Include only the envelope scalars the widget or the platform needs (`actionName`, `isSuccess`, and `outputValues` at minimum). Add `message` etc. only when rendered.
|
|
95
|
+
|
|
96
|
+
## Default renderer (in the ENVELOPE CLT)
|
|
97
|
+
|
|
98
|
+
The renderer is the **default `renderer.json` at the envelope CLT bundle root**, parallel to `schema.json` — NOT under `lightningDesktopGenAi/`. It assigns the widget and bridges the envelope nesting to the flat widget schema.
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"renderer": {
|
|
103
|
+
"componentOverrides": {
|
|
104
|
+
"$": {
|
|
105
|
+
"definition": "@widget/c/accountSummaryWidget",
|
|
106
|
+
"attributes": {
|
|
107
|
+
"accountName": "{!$attrs.outputValues.accountName}",
|
|
108
|
+
"accountIndustry":"{!$attrs.outputValues.accountIndustry}",
|
|
109
|
+
"contactCount": "{!$attrs.outputValues.contactCount}",
|
|
110
|
+
"totalOpportunityAmount": "{!$attrs.outputValues.totalOpportunityAmount}"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**The binding path is the crux:** each widget attribute (left, flat) maps to the response field nested under the envelope's `outputValues` node (right) via `{!$attrs.outputValues.<field>}`. Because the envelope CLT's `outputValues` is typed to the response CLT, the runtime can resolve `outputValues.<field>` against the response CLT's `properties`.
|
|
119
|
+
|
|
120
|
+
**Nested-object response fields bind one level deeper.** When a response field is itself an Apex-class reference (the `flightInfo` case above), the widget flattens to that class's own leaf fields, and the renderer binding goes two levels deep: `{!$attrs.outputValues.flightInfo.flightId}`, `{!$attrs.outputValues.flightInfo.origin}`, etc. — `outputValues.<objectField>.<leaf>`, not `outputValues.<objectField>` alone (which would bind the widget to an unresolvable object, not a renderable leaf).
|
|
121
|
+
|
|
122
|
+
## Why two CLTs and not one
|
|
123
|
+
|
|
124
|
+
The platform binds the tool's output rendition to the CLT whose shape matches the **tool output schema** — that is the envelope, not the response. So the renderer (and thus the widget assignment) must live in the envelope CLT. But the widget wants a flat attribute contract, so the renderer flattens the nesting via `outputValues.`. The response CLT exists purely to *type* the `outputValues` node so those nested paths resolve. Inlining the response fields as a nested `lightning__objectType` inside the envelope is rejected by the CLT metaschema — hence a separate, referenced response CLT.
|
|
125
|
+
|
|
126
|
+
## The widget schema is generic, not CLT-derived
|
|
127
|
+
|
|
128
|
+
The widget schema is a standalone contract: `properties.attributes.properties` built from the **response field list** (name + primitive `lightning:type`), nothing more. It happens to share the field set with the response CLT for this flow, but it is not typed against the CLT, does not carry `unevaluatedProperties`, and would look identical if the same field list arrived from any other source `platform-widget-generate` supports. The widget schema and body know nothing about the envelope. The widget binds `{!$attrs.accountName}` (flat); only the renderer knows the field actually lives at `outputValues.accountName`. This keeps the widget reusable and lets `platform-widget-generate` author it exactly as it would for any flat response.
|