Skip to content

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.

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 6086400 — 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.sources (array, required, ≥1) — the read-only Source columns, in display order. Each entry has a key (referenced by fields[*].sources) and a display label (e.g. "Apollo").
  • columns.current.label (string, required) — names the read-only Current column (e.g. "Salesforce").
  • fields is a map keyed by requestedSchema property name, supplying that property’s Source and Current cell values:
    • Every fields key must be a property in requestedSchema. A property absent from fields is legal — it renders with blank Source/Current cells and its default as Proposed (a proposed-only field).
    • fields[k].sources — a map keyed by a columns.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.

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/null Current 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 (the default) differs from Current; otherwise unchecked. An explicit fields[k].include overrides this.
  • Required force-include. A property listed in requestedSchema.required locks its Include checkbox on whenever fields[k].current is 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.

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).

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’s decision is "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 of requestedSchema restricted to the included keys. Unincluded fields are omitted entirely — not present, not null.
  • There is no per-field audit block and no operation flag — every audit datum (included, edited, changed) is derivable by the caller from data it already holds (its own input plus values).
  • A cancelled proposal (the reviewer clicked Cancel on the review page) is fetchable exactly like a submitted one — elicit_result returns { action: "cancel", decision: "rejected", … } rather than erroring.