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 | { submit?, decline? }, each 1–40 characters; renames the footer buttons: submit is the submit button (default Submit), decline the decline button (default Decline). Labels only rename — button placement and styling stay fixed, and the header actions are not configurable. |
expiresInSeconds |
number | no | Default 3600 (1 hour), clamped 60–604800 (1 minute to 7 days) — how long the request stays decidable. |
{ "message": "Sync Acme Holdings 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": "Acme Holdings" }, "website": { "type": "string", "format": "uri", "title": "Website", "default": "https://www.acmeholdings.example/" }, "employeeCount": { "type": "integer", "title": "Number of Employees", "default": 230 }, "industries": { "type": "array", "title": "Industries", "items": { "enum": [ "Software", "Fintech", "Healthcare", "Manufacturing" ] }, "default": [ "Software", "Fintech" ] } }, "required": [ "name" ] }, "fields": { "name": { "sources": { "apollo": "Acme Holdings", "crunchbase": "Acme Holdings Inc." }, "current": null }, "website": { "sources": { "apollo": "https://www.acmeholdings.example/" }, "current": null }, "employeeCount": { "sources": { "apollo": 230 }, "current": null }, "industries": { "sources": { "apollo": [ "Software", "Fintech" ] }, "current": [], "include": true } }}| Field | Apollo | Crunchbase | Salesforce | Proposed | Include |
|---|---|---|---|---|---|
| Name * | Acme Holdings | Acme Holdings Inc. | |||
| Website | https://www.acmeholdings.example/ | ||||
| Number of Employees | 230 | ||||
| Industries | Software; Fintech |
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 and Decline (the footer
pair is renamable via labels; the header actions and counter are fixed).
Output
Section titled “Output”The create call returns the same envelope as the other create tools —
{ elicitationId, status: "pending", reviewUrl, expiresAt, elicitationDelivered } — where elicitationDelivered is true only when
the host advertised the elicitation: { url: {} } capability on a legacy
(pre-2026-07-28) connection; reviewUrl always works regardless.
The outcome is delivered through the same
elicit_result envelope as the other
approval-family tools, with values overlaid on top:
// Submit{ "status": "consumed", "decision": "submitted", "context": { "objectType": "Account", "recordId": null, "externalKey": "678cd3…" }, "values": { "name": "Acme Holdings", "website": "https://www.acmeholdings.example/", "employeeCount": 230, "industries": ["Software", "Fintech"] }}
// Decline{ "status": "consumed", "decision": "declined", "context": null }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 declined proposal (the reviewer clicked Decline on the review page) is
fetchable exactly like a submitted one —
elicit_resultreturns{ decision: "declined", … }rather than erroring.
Related
Section titled “Related”- Proposal Flow — the full lifecycle, end to end
elicit_await/elicit_status— wait for the decisionelicit_result— fetchdecision+valueselicit_approval— the single-column approve/decline alternative- Templates — the shared LiquidJS engine rules