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
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
- Host calls the tool —
executedoes not run yet - Server returns
input_required(MRTR) with yourmessageand optionalpreview - User accepts —
executeruns once on a new request - Mutation never replays on the first call
MRTR is defined in Connecting.
Verify
- Call the tool — expect
input_required, not a completed delete - Accept the confirmation — expect
executeto run - Decline or ignore — expect no mutation
Reply to Claude…
ctx.ask
For mid-flight fields inside execute:
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, notconfirm. - 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.