# Cloudflare Agents Can Now Respond to MCP Servers' Confirmation and Input Requests!

Hi, it's me! Today I found a nice update from the Cloudflare Changelog about Agents getting better at talking with MCP servers. When a server pauses mid-tool-call to ask, "hey, can you tell me something first?", your Agent can now actually answer! I'm so excited about this one.

## What was announced?

This is an update from the Cloudflare Changelog: Agents connected to a Model Context Protocol (MCP) server through `addMcpServer` can now respond to elicitation requests.

Elicitation is the mechanism an MCP server uses to ask the user for input while it is still handling a tool call. Two modes are supported now.

- Form mode: collects structured, non-sensitive data through a form
- URL mode: asks for consent before opening an out-of-band flow, like third-party authorization or payment

## The story so far

Before this update, if an Agent did not have an elicitation handler configured, its connection would simply advertise "no elicitation support" to the MCP server. So if a server wanted to pause mid-task to ask, "can you confirm this?" or "please fill in this field," the Agent had no way to receive that request, and the server had to fall back to its own default behavior instead.

## What changes

Now you can register handlers inside the Agent's `onStart()` by calling `configureElicitationHandlers()`, passing separate callbacks for form and URL requests.

```javascript
onStart() {
  this.mcp.configureElicitationHandlers({
    form: (request, serverId) => this.forwardElicitationToBrowser(request, serverId),
    url: (request, serverId) => this.forwardElicitationToBrowser(request, serverId),
  });
}
```

Only the modes you have configured a handler for get advertised during the MCP `initialize` handshake. If you only register a form handler, only form mode is advertised; if you register neither, the connection tells the server it has no elicitation support at all, so the server can fall back gracefully. You only get the capability you actually implement, which feels like a safe design.

## Dive Deep

### Inside form mode

A form-mode request comes with a restricted JSON Schema in `requestedSchema`. Here is what your implementation needs to do:

- Let the user review and edit the content before submitting
- Validate the submitted content against the schema
- Respond with either `{ action: "accept", content }` or `{ action: "cancel" }`
- Never ask for passwords, API keys, tokens, or payment credentials through the form

### Inside URL mode

URL mode is for "hand off to another flow" situations, like third-party authorization or payment. A proper handler needs to:

- Identify which MCP server sent the request
- Show the user the message, the target host, and the full URL
- Get the user's consent before opening the external page
- Keep the URL out of contexts the model can see
- Respond with `{ action: "accept" }` (no content) once consent is given

The documentation is explicit that you should not prefetch the URL or its metadata, and should treat the URL as untrusted input — it is clear that spoofing and phishing were front of mind when this was designed.

### Three possible responses

Both modes support the same three outcomes: `accept` (submitted or consented), `decline` (explicit rejection), and `cancel` (dismissed without choosing). Only an `accept` from form mode comes with `content`.

### Bridging to the browser

Since the actual response usually comes from a person in the browser, the pattern shown in the docs combines an `@callable()` method with `broadcast`, wrapped in a Promise that waits for the answer.

```javascript
forward(request, serverId) {
  const id = crypto.randomUUID();
  const result = new Promise((resolve) => {
    const timeout = setTimeout(() => {
      if (this.pendingElicitations.delete(id)) {
        resolve({ action: "cancel" });
      }
    }, 55_000);
    this.pendingElicitations.set(id, { resolve, timeout });
  });

  this.broadcast(JSON.stringify({
    type: "mcp-elicitation",
    id,
    serverId,
    params: request.params,
  }));
  return result;
}
```

The timeout is set to 55 seconds because the MCP SDK's own request timeout defaults to 60 seconds, so this returns `cancel` just before that happens, keeping cleanup tidy.

### A few more things to know

- You must never advertise a mode you have not implemented a handler for. If the server sends a request for that mode anyway, the connection cannot handle it and returns an error
- Advertised modes persist even after the Durable Object hibernates, and the callbacks reattach the next time `onStart()` runs
- You can also explicitly narrow which modes get advertised through the `addMcpServer` options

```typescript
await this.addMcpServer("portal", "https://portal.example.com/mcp", {
  client: {
    capabilities: {
      elicitation: { form: {} },
    },
  },
});
```

You can pick this up with `npm i agents@latest` (or the equivalent with yarn, pnpm, or bun).

## Wrap-up

- Cloudflare's Agents SDK now supports MCP elicitation
- Form mode collects structured data validated against a JSON Schema, and it is off-limits for passwords or API keys
- URL mode gets user consent before opening an external authorization or payment flow, and treats the URL as untrusted
- Supported modes are advertised at `initialize` based on which handlers you registered, and they survive Durable Object hibernation
- You can update with `npm i agents@latest`

If you are building an Agent that talks to MCP servers and need to pause for user confirmation or extra input mid-flow, this one is for you, especially if you are wiring up OAuth or payment confirmation flows. Worth trying out this week!
