elicit_proposal
Put a human in the loop to review, edit, and selectively approve a proposed record create-or-update, presented as a diff grid: one or more read-only Source columns, a read-only Current column, and an editable Proposed column. The human corrects the Proposed values, chooses which fields to include, and submits — the result carries only the fields they included.
When to use
Section titled “When to use”The canonical use case is enrichment/sync — e.g. populating Salesforce from
Apollo, where the Source column is “Apollo” and the Current column is
“Salesforce”. For a brand-new record the Current column is blank. Reach for it
whenever the human should see where each value came from and decide
field-by-field; when a single editable column with approve/reject is enough,
elicit_approval form mode is simpler — see
Choosing a tool. The Proposed column reuses
the same requestedSchema form pipeline, so it supports the same primitives —
text, textarea, number/integer, checkbox, single/multi select — and the same
uri / email / date / date-time formats.
| Parameter | Type | Required | Description |
|---|---|---|---|
message |
string | yes | Human-readable summary shown to the reviewer. |
columns |
object | yes | Names the read-only columns — see Columns and fields. |
requestedSchema |
object | yes | The authority for the editable Proposed column, validated the same way as elicit_approval’s form mode (the MCP elicitation requestedSchema subset). Each property’s default is its initial Proposed value — the caller writes the computed proposed value (typically “take Current if present, else Source”) as that property’s default. |
fields |
object | yes | Per-property Source and Current cell values — see Columns and fields. |
context |
any | no | Arbitrary passthrough context (object type, record id, external keys). Echoed back verbatim in the output. The tool does not interpret it — create-vs-update is entirely the caller’s concern, implied by whether current values are present. Not shown to the reviewer by default — pass displayTemplate to surface a header. |
displayTemplate |
string | no | A LiquidJS template rendered above the grid; context is exposed under the context key (e.g. {{ context.recordId }}). Use it for a title or key-info banner. Without it, nothing renders above the grid. |
labels |
object | no | { ok?, cancel? }, each 1–40 characters; renames the footer buttons: ok is the submit button (default Submit Changes), cancel the cancel button (default Cancel). Labels only rename — button placement and styling stay fixed, and the header actions are not configurable. |
timeoutSeconds |
number | no | Default 3600, clamped 60–86400 — how long the request stays decidable. |
{ "message": "Sync NorthMark Strategies from Apollo into Salesforce", "columns": { "sources": [ { "key": "apollo", "label": "Apollo" }, { "key": "crunchbase", "label": "Crunchbase" } ], "current": { "label": "Salesforce" } }, "context": { "objectType": "Account", "recordId": null, "externalKey": "678cd3…" }, "requestedSchema": { "type": "object", "properties": { "name": { "type": "string", "title": "Name", "default": "NorthMark Strategies" }, "website": { "type": "string", "format": "uri", "title": "Website", "default": "https://www.northmarkstrategies.com/" }, "employeeCount": { "type": "integer", "title": "Number of Employees", "default": 230 }, "institutionTypes": { "type": "array", "title": "Institution Types", "items": { "enum": ["Asset Manager", "Private Equity", "Hedge Fund", "REIT"] }, "default": ["Asset Manager", "Private Equity"] } }, "required": ["name"] }, "fields": { "name": { "sources": { "apollo": "NorthMark Strategies", "crunchbase": "NorthMark Strategies Inc." }, "current": null }, "website": { "sources": { "apollo": "https://www.northmarkstrategies.com/" }, "current": null }, "employeeCount": { "sources": { "apollo": 230 }, "current": null }, "institutionTypes": { "sources": { "apollo": ["Asset Manager", "Private Equity"] }, "current": [], "include": true } }}Columns and fields
Section titled “Columns and fields”columns.sources(array, required, ≥1) — the read-only Source columns, in display order. Each entry has akey(referenced byfields[*].sources) and a displaylabel(e.g."Apollo").columns.current.label(string, required) — names the read-only Current column (e.g."Salesforce").fieldsis a map keyed byrequestedSchemaproperty name, supplying that property’s Source and Current cell values:- Every
fieldskey must be a property inrequestedSchema. A property absent fromfieldsis legal — it renders with blank Source/Current cells and itsdefaultas Proposed (a proposed-only field). fields[k].sources— a map keyed by acolumns.sources[].key. A source may omit a field (that Source cell renders blank).fields[k].current— the Current value;null/absent renders blank.fields[k].include(boolean, optional) — overrides the default-included rule below.
- Every
Derived behavior
Section titled “Derived behavior”Several parts of the grid are computed by the review page, not supplied by the caller:
- Cell highlight. A Source cell is boxed (the “differs” outline) when
its value differs from the field’s Current value. With multiple sources,
each Source cell highlights independently, so the human sees which sources
disagree with the record. Absent/
nullCurrent is treated as empty, so on a create every populated Source cell is boxed. Array-valued fields (multiselect) are compared as sets (order-insensitive). - Double-click to copy. Double-clicking a Source cell copies that value into Proposed and turns the row’s Include on (tooltip “Double-click to use as proposed value.”). Double-clicking the Current cell (when it has a value) copies Current into Proposed and turns Include off — a quick way to revert a single field to “no change” (tooltip “Double-click to restore the current value.”).
- Default
include. A row is included by default when its Proposed value (thedefault) differs from Current; otherwise unchecked. An explicitfields[k].includeoverrides this. - Required force-include. A property listed in
requestedSchema.requiredlocks its Include checkbox on wheneverfields[k].currentis empty/absent — i.e. the record would otherwise lack a required value. If Current already satisfies a required field, the human may leave it unincluded. This lock is a client-side affordance only — it stops a human from building an incomplete record in the UI, but the server does not independently re-derive which fields “should” be included; it validates only whatever subset was actually submitted as included.
Header actions
Section titled “Header actions”The review page also offers: Accept All (include every row), Reject
All (uninclude every row except required-locked ones), and Restore
Proposed (reset every Proposed value back to its default), plus an “N of
M included” counter alongside Submit Changes and Cancel (the footer
pair is renamable via labels; the header actions and counter are fixed).
Output
Section titled “Output”Delivered through the same elicit_result
envelope as the other approval-family tools, with action and values
overlaid on top:
// Submit{ "status": "consumed", "decision": "approved", "action": "submit", "context": { "objectType": "Account", "recordId": null, "externalKey": "678cd3…" }, "values": { "name": "NorthMark Strategies", "website": "https://www.northmarkstrategies.com/", "employeeCount": 230, "institutionTypes": ["Asset Manager", "Private Equity"] }}
// Cancel{ "status": "consumed", "decision": "rejected", "action": "cancel", "context": null }action—"submit"when the envelope’sdecisionis"approved","cancel"when"rejected"— a friendly alias; callers may read either.context— echoed verbatim from the input.values— only the fields the human included, with their final (possibly edited) Proposed values, validated against the subset ofrequestedSchemarestricted to the included keys. Unincluded fields are omitted entirely — not present, notnull.- There is no per-field audit block and no
operationflag — every audit datum (included,edited,changed) is derivable by the caller from data it already holds (its own input plusvalues). - A cancelled proposal (the reviewer clicked Cancel on the review page) is
fetchable exactly like a submitted one —
elicit_resultreturns{ action: "cancel", decision: "rejected", … }rather than erroring.
Related
Section titled “Related”- Proposal Flow — the full lifecycle, end to end
elicit_await/elicit_status— wait for the decisionelicit_result— fetchaction+valueselicit_approval— the single-column approve/reject alternative- Templates — the shared LiquidJS engine rules