> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Elicitation

> Request user input from MCP tools with form and URL flows.

Elicitation lets a tool return an `input_required` result. A capable client
collects input and retries the original tool call; the callback runs again with
the response in its request context.

Use form mode for ordinary structured input and URL mode when values must stay
on an external site.

## Handle the input-required round

Every call has a stable correlation key, a user-facing message, and either a
Standard Schema form schema or a URL:

```typescript theme={null}
import { MCPServer } from "mcp-use";
import { z } from "zod";

const server = new MCPServer({
  name: "deployment-server",
  version: "1.0.0",
});

const approvalSchema = z.object({
  approve: z.boolean().describe("Approve this deployment"),
  note: z.string().max(200).optional(),
});

server.tool(
  {
    name: "deploy",
    inputSchema: z.object({
      environment: z.enum(["staging", "production"]),
    }),
    outputSchema: z.object({
      environment: z.string(),
      deployed: z.boolean(),
    }),
  },
  async ({ environment }, ctx) => {
    const approval = await ctx.elicit(
      "deployment-approval",
      `Deploy to ${environment}?`,
      approvalSchema,
    );

    if (approval.status === "required") {
      return approval.result;
    }

    if (approval.status !== "accept" || !approval.data.approve) {
      return {
        isError: true,
        content: [{ type: "text", text: "Deployment was not approved." }],
      };
    }

    // Side effects belong after accepted input. This callback ran once before
    // input was required and now runs again with ctx.inputResponses.
    await deployments.start(environment, approval.data.note);

    const data = { environment, deployed: true };
    return {
      content: [{ type: "text", text: JSON.stringify(data) }],
      structuredContent: data,
    };
  },
);

export default server;
```

The stable key correlates the embedded request with the matching value in
`ctx.inputResponses`. `ctx.elicit()` reads and validates that response for you.
Invalid accepted form data produces another `required` round.

The result statuses are:

| Status     | Meaning                                                           |
| ---------- | ----------------------------------------------------------------- |
| `required` | Return `result` directly so the client can collect input          |
| `accept`   | Valid form data is available as `data`; URL mode has no form data |
| `decline`  | The user explicitly refused                                       |
| `cancel`   | The user dismissed or interrupted the flow                        |

## Form mode

Form mode accepts a Standard Schema that can be converted to JSON Schema. Keep
forms flat and use primitive fields, enums, and arrays of enum values. Add
descriptions and refinements before passing the schema to `ctx.elicit()`.

Do not collect passwords, API keys, payment details, or OAuth secrets in form
mode. Submitted form values pass through the MCP client.

## URL mode

Pass an absolute URL string as the third argument. Keep the external flow's
state in a trusted backend and look it up with a stable, unguessable handle
from the original tool input:

```typescript theme={null}
const flow = await githubAuth.getOrCreateFlow(connectionId);

const authorization = await ctx.elicit(
  "github-authorization",
  "Authorize GitHub access",
  flow.authorizationUrl,
);

if (authorization.status === "required") {
  return authorization.result;
}

if (authorization.status !== "accept") {
  return {
    isError: true,
    content: [{ type: "text", text: "Authorization was not completed." }],
  };
}

const connection = await githubAuth.requireCompleted(flow.id);
```

URL mode asks the client to open an external flow; it does not return secrets or
prove that the flow succeeded. `getOrCreateFlow` must return the same
server-stored flow when the callback reruns with `connectionId`. Verify callback
state and completion on your own backend before performing a protected action.

## Keep multi-round workflows safe

Because the callback re-runs for each input-required round:

* Perform reads and validation before elicitation as needed.
* Perform irreversible side effects only after the relevant `accept`.
* Use a different stable key for each distinct question.
* Use verified `requestState` when a multi-step workflow needs trusted state.
* Treat bare `ctx.inputResponses` as untrusted client input if you read it
  directly.

## Run and test the example

```bash theme={null}
cd libraries/typescript/packages/server/examples/elicitation
pnpm dev
```

Connect a client that supports form and URL elicitation to
`http://localhost:3000/mcp`. Test accept, decline, cancel, an invalid form
response that triggers another round, and the URL callback-verification path.
