platform-mcp-tool-widget-coordinate

Orchestrate object-based Lightning Type + HXL widget generation to render the output of a custom MCP server tool backed by an Apex Invocable Action. TRIGGER only when the prompt EXPLICITLY involves rendering an MCP tool result: user says 'MCP server', 'MCP tool', 'custom MCP server', references a to

By forcedotcom · 2,159 installs

npx skills add forcedotcom/sf-skills --skill platform-mcp-tool-widget-coordinate

Source repository · Upstream listing

Rendering a Custom MCP Tool Output With a Widget Coordinate two object based Custom Lightning Types (CLTs) and an HXL widget to render the output of a custom MCP server tool whose implementation is an Apex @InvocableMethod . This skill never authors content directly — it loads and invokes leaf skills in dependency order, gates progress on user approval, and runs validation gates before reporting completion. Ownership boundary — this skill owns only: 1. the MCP tool use case — resolving the tool's output shape and orchestrating the leaf skills in the right order; and 2. the envelope CLT's default renderer.json — the one artifact it authors inline, bridging the envelope to the widget. The CLTs are authored by platform custom lightning type generate , and all widget metadata (schema + body + .uiwidget meta.xml ) is authored and validated by platform widget generate . This skill never writes widget metadata and never modifies the Apex class — it supplies each leaf skill its inputs and wires the result together. Scope Custom MCP server tools backed by an Apex Invocable Action only. The MCP tool returns the platform's invocable action result envelope — an object with actionName , isSuccess , and an outputValues node that carries the tool's real payload. To render this envelope with a widget, model it as two object based CLTs ( lightning objectType ) of equal standing — the only reason there are two is that one must reference the other by name (a CLT cannot reference itself), so they need distinct deployed names. Name and describe each by what it actually models — never by an invented role label pair like "Payload CLT"/"Envelope CLT" or "Inner CLT"/"Outer CLT": The CLT that mimics the tool result envelope, named <toolApiName . Its outputValues property is typed to the other CLT via c <responseCLT (the CLT reference prefix — see the namespace note below). The CLT that is the exact shape of the Invocable Action's response ( @InvocableVariable fields on the @InvocableMethod response class), named <toolApiName Response — "Response" is not an invented role word; it is the word the Apex source itself uses for that class. The widget grounds on the response fields (flat), and the default renderer.json in the envelope CLT bridges the envelope nesting to the flat widget via {!$attrs.outputValues.<field } . Namespace prefix provenance ( c / @apexClassType/<ns … ): the Actions REST describe returns bare, unprefixed class names ( Outer$Inner ) and no namespace — the prefix is the org's CLT namespace , added by this skill. It is c in a namespace less org (the common case, used literally throughout this doc) or the package namespace <ns in a packaged org (read from the class's NamespacePrefix ; default to c only when the org has none). Every c below is this prefix. Out of scope, route elsewhere: Customizing an Apex backed agent action output (Apex backed CLT @apexClassType/... , single CLT, surface specific renderer) → platform lightning type widget coordinate . A standalone widget with no MCP tool / Lightning Type → platform widget generate . Authoring only a CLT or only an Apex class → platform custom lightning type generate / platform apex generate . Beta cardinality: the invocable action result is a bulk array ( content[] ). For the beta release this skill models and renders a single response — the first element of content[] . The CLT envelope models one result object, not the content[] wrapper. How this differs from platform lightning type widget coordinate Dimension agent action flow ( ...lightning type widget coordinate ) this MCP tool flow CLT kind Apex backed ( @apexClassType/... ) Object based ( lightning objectType ) Number of CLTs one two (envelope + response) Field source @AuraEnabled @InvocableVariable on the top level response class (referenced inner classes: public/ @AuraEnabled — see Hard Rule 6) Renderer location lightningTypes/<T /lightningDesktopGenAi/renderer.json (surface specific) lightningTypes/<toolCLT /renderer.json ( default, parallel to schema.json ) Renderer binding flat {!$attrs.<field } nested {!$attrs.outputValues.<field } Phase Graph Phase Purpose Output 1 — Input selection Determine the payload source: an invocable action API name (preferred), an Apex Invocable class, or a pasted tool output JSON sample. source ( action \ apex \ sample ), tool API name 2 — Payload discovery Describe the invocable action via the Actions REST API and read its typed outputs (or parse the response class from source, or outputValues from the sample). payloadFields (name + lightning:type ) 3 — Build plan Print the plan in full; proceed unless the next reply explicitly pushes back. printed plan 4 — Generation Load and invoke leaf skills: response CLT → envelope CLT → widget → inline default renderer in the envelope CLT. files written 5 — Validation Run hard gates (block) and warn gates (advisory). gate report 6 — Summary Files, validations, deploy order, preview readiness. summary Per phase pattern: load the skill fresh → execute its workflow → verify outputs → checkpoint before the next phase. Even if you remember a leaf skill's content, skills evolve — always load fresh. Phase 1 — Input selection Determine where the payload shape comes from. Prefer the sources top to bottom: Source Trigger Phase 2 action action Prompt gives an invocable action API name — directly, or via an Apex class name that resolves to one — AND an authenticated org is available. Preferred. Describe the action via the Actions REST API and read its typed outputs . sample No reachable org (or the describe 404s), but a pasted tool output JSON sample is available. Parse the outputValues object from the sample. apex Only the Apex class is available (no action name resolvable, no reachable org, no sample) — fallback only, may be stale relative to what's deployed. Resolve the response class and enumerate @InvocableVariable fields. Capture the tool API name (used to name all artifacts — see the naming convention below). Source priority: live/authoritative schema sources beat parsing a local class, which beats a pasted example. In order: 1. action if an action API name and an authenticated org are available. The Actions REST API describe is the same schema the platform itself exposes, so it needs no request/helper filtering and gives real field types. 2. sample if a runtime JSON sample is pasted (runtime response — explicit and current). 3. apex if an Apex class exists locally AND none of the above apply (fallback only — may be stale relative to what's actually deployed behind the action). If none are available, STOP and ask the user for an action name, a class, a sample, or a schema. Phase 2 — Payload discovery FIRST Read references/mcp tool output discovery.md (REQUIRED — do NOT run Phase 2 from this summary alone), then execute the procedure for the chosen source. The reference is authoritative for the full per source procedures, the field type mapping tables, and the nested/list handling; the pointers below are only a map to it: action (preferred): describe via sf api request rest '/services/data/v<APIVER /actions/custom/apex/<ActionApiName ' o <org ; use the outputs array only ( ignore inputs — the request wrapper); map each type → CLT lightning:type case insensitively. An entry with "type": null and an "apexClass": "<Outer $<Inner " key is an Apex class typed field (not a describe gap): maxOccurs: 1 → single nested object, maxOccurs 1 → top level list. If the describe 404s, fall back to sample then apex . apex (fallback): locate the class, identify the response class (the @InvocableMethod return List<... element type), enumerate its @InvocableVariable fields ( exclude the request class and private helpers), map Apex type → CLT type. sample (fallback): parse the outputValues object; infer each field's lightning:type from its JSON value. Nested object and list fields (every source, additive to the flat primitive case): a field typed as another Apex class — a single object ( maxOccurs: 1 ), a top level list ( maxOccurs 1 ), or a list inside a wrapper object (the describe returns one maxOccurs: 1 apexClass output and hides the interior list) — is in scope and is never modeled as a bare {"type":"object"} or an inlined lightning objectType . Type it @apexClassType/c <Outer $<Inner in the response CLT (never a CLT level lightning listType / items ), enumerate a referenced/inner class by its public / @AuraEnabled members (Hard Rule 6), and recurse when a leaf is itself a class. The CLT typing and renderer binding depth per shape live in references/mcp tool output discovery.md and references/two clt modeling.md ("Top level list vs list inside wrapper") — also Hard Rules 4, 6, 14 — and the examples/nested object source prompt.md walkthroughs. Capture payloadFields — the ordered list of { name, title, lightning:type } that defines the response CLT and the widget schema. Record which source produced it in the build plan. Staleness: do NOT maintain a cross session cache. Read the local project fresh and re retrieve from the org per session. Phase 3 — Build plan + approval gate Print a build plan using the template in references/build plan format.md . The plan must list: A one line developer facing summary (the PLAN: line). The tool API name and the response class FQN (or "from pasted sample"). The two CLT names (envelope + payload) and the widget name, with absolute paths. The envelope CLT carries exactly actionName (text), isSuccess (boolean), and outputValues (typed c <responseCLT , the load bearing field the renderer bridges through) — actionName / isSuccess are envelope only and never appear on the widget (Hard Rule 5) — plus the response fields the response CLT + widget will carry. The validations that will run after generation. Print the plan in full, then proceed unless the user's next reply explicitly pushes back. Explicit pushback = no , stop , wait , change X , use Y instead , or an equivalent rejection / revision request. Explicit approval is welcome but NOT required — silence, an unrelated follow up, or the natural continuation of a single turn eval all count as implicit approval. The invariant is the plan being visible in the transcript. If pushback arrives, revise and re print before moving on. Phase 4 — Generation Load and invoke leaf skills in this order. For each: load the skill, execute its workflow against the Phase 3 spec, verify the outputs, checkpoint before the next. 1. Response CLT — load platform custom lightning type generate . Author an object based CLT <responseCLT (convention: <toolApiName Response ) whose properties are the payloadFields from Phase 2. Root is lightning objectType , with root level "lightning:tags": ["mcp"] . The response CLT's top level properties are 1:1 with the describe's outputs[] names (or, for apex / sample , the response class's @InvocableVariable fields — the same set the describe would surface). A describe with N sibling outputs → N flat properties ; a describe with one output → one property named after that output . A single property CLT is correct only when the describe itself returns a single output. Never collapse multiple sibling outputs into one invented wrapper property. Naming a lone property to hold several outputs invents a key that is in no describe output and is unresolvable under the action source — the response class name never appears in outputs[] . Each response CLT property name must trace to a describe output name (see field trace