Skip to main content
Elicitation is Toolshed’s mechanism for ensuring a human approves destructive operations before they execute. When a sandbox script calls a tool marked destructive: true, execution pauses until a human approves or denies the action.

The elicitation lifecycle

1. Script calls tools.github.issues.create(args)

2. Invoke callback calls engine.gate(toolDef, args)

3. Tool is destructive and not session-approved
       │  → creates PendingElicitation with promise
       │  → fires onPause callback

4. Runtime detects pause, returns ExecutionResult with:
       │  { paused: { executionId, toolPath, message, args } }

5. MCP client shows pending action to user

6. User approves → agent calls resume({ execution_id, approved: true })

7. ElicitationEngine.resolve() fulfills the pending promise

8. Execution continues, tool call proceeds

ElicitationEngine

The ElicitationEngine class manages the full lifecycle:
import { ElicitationEngine } from "@toolshed/kernel";

const engine = new ElicitationEngine({
  onPause: (elicitation) => {
    // Notify the user about the pending action
    console.log(`Approval needed: ${elicitation.message}`);
  },
});

Key methods

MethodDescription
gate(tool, args)Gates destructive tool calls. Returns immediately for safe tools.
resolve(executionId, approved, data?)Resolves a pending elicitation. Returns true if found.
getPending(executionId)Get a specific pending elicitation
getAllPending()Get all pending elicitations
hasPending()Check if any elicitations are pending
addSessionApproval(toolPath)Auto-approve future calls to this tool path
isSessionApproved(toolPath)Check if a tool path is session-approved
clearSessionApprovals()Reset all session approvals

Session approvals

Once a user approves a tool, the engine can remember the approval for the session. Subsequent calls to the same tool path auto-approve without pausing:
engine.addSessionApproval("github.issues.create");
// Future calls to github.issues.create will not pause

Using elicit() in plugin handlers

Inside a tool handler, call ctx.elicit() to request approval:
async handler(ctx, input) {
  const approval = await ctx.elicit({
    toolPath: "github.issues.create",
    message: `Create issue "${input.title}" in ${input.owner}/${input.repo}?`,
    args: input,
    type: "approval",
  });

  if (!approval.approved) {
    throw new Error("Issue creation denied by user");
  }

  // proceed...
}
The elicit() function takes a single object parameter:
FieldTypeRequiredDefaultDescription
toolPathstringYesTool requesting approval
messagestringYesHuman-readable description
argsobjectNoArguments being approved
typestringNo"approval""approval" for yes/no, "form" for data collection
Returns an ElicitationResponse:
FieldTypeDescription
executionIdstringUnique elicitation ID
approvedbooleanWhether the user approved
dataobjectAdditional data (for form type)

Elicitation types

  • approval — simple yes/no confirmation. The most common type.
  • form — collect additional data from the user. The response includes a data object with the user’s input.