Core Concepts

Confirmations

Gate destructive tools with confirm() and verify MRTR in a text client.

Destructive or sensitive tools declare confirmation outside execute.

Add a confirm handler

delete-rows/tool.ts
export default defineTool({
  description: "Delete rows matching a query",
  input: z.object({ query: z.string() }),
  async confirm({ query }) {
    const plan = planDelete(query);
    return {
      message: `Delete ${plan.rows} rows matching ${query}?`,
      preview: plan,
    };
  },
  async execute({ query }) {
    const plan = planDelete(query);
    return result(plan, `${plan.rows} rows deleted.`);
  },
});

Understand the flow

  1. Host calls the tool — execute does not run yet
  2. Server returns input_required (MRTR) with your message and optional preview
  3. User accepts — execute runs once on a new request
  4. Mutation never replays on the first call

MRTR is defined in Connecting.

Verify

  1. Call the tool — expect input_required, not a completed delete
  2. Accept the confirmation — expect execute to run
  3. Decline or ignore — expect no mutation
Delete rows matching beta
Reply to Claude…

ctx.ask

For mid-flight fields inside execute:

delete-rows/tool.ts
async execute(input, ctx) {
  const { note } = await ctx.ask(z.object({
    note: z.string().describe("Why are you deleting these rows?"),
  }));
  return applyDelete(input.query, note);
}

Prefer confirm for yes/no gates. Raw ctx.inputRequired remains an escape hatch.

Troubleshooting

  • Mutation runs on first call — Move side effects into execute, not confirm.
  • Token errors in production — Set BITMCP_STATE_KEY. See Installation.
  • View-only confirm path — MRTR must work in terminal hosts, not only in the iframe.

Reference

confirm maps to resultType: "input_required". Text hosts see message. UI hosts may render preview from MRTR structuredContent. execute runs once, after accept.