# MCP input

Whether a tools/call (or prompts/get, resources/read) result asks for more input. A result without `resultType` comes from an older server and counts as complete, as the spec says.

- Registry name: `mcp-input` (`@candor/mcp-input`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/mcp-input

## Install

```bash
npx candor-ui add mcp-input
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/mcp-input`.

## Functions

### isMcpInputRequired

```ts
isMcpInputRequired(result: unknown)
```

Whether a tools/call (or prompts/get, resources/read) result asks for more input. A result without `resultType` comes from an older server and counts as complete, as the spec says.

### mcpInputView

```ts
mcpInputView(result: unknown, options: { server?: string | undefined; words?: McpInputWords | undefined } = {})
```

An input-required result as items to show: forms as questions, confirmations and links as decisions, and anything else as a visible "not supported". Undefined when the result doesn't ask for input. `server` is your app's own name for the server, which the person should see beside every request.

### mcpInputResponses

```ts
mcpInputResponses(view: McpInputView, replies: McpInputReply[], options: { own?: Record<string, unknown> | undefined; words?: McpInputWords | undefined } = {})
```

The retry's `inputResponses` for the person's replies, checked against each form's schema first (the spec asks clients to validate). Every form, confirmation and link needs a reply; a form is sent only once every required field has a valid answer. `own` holds responses your app made itself, for requests Candor doesn't show (a sampling or roots answer), keyed by the server's ids; anything left is listed in `notSent`.

### mcpRetryParams

```ts
mcpRetryParams(original: P, view: Pick<McpInputView, "requestState">, inputResponses: Record<string, unknown>)
```

The params for retrying the original request: its own params (name and arguments for tools/call) with `inputResponses` and the result's `requestState` exactly as received. A `requestState` from an earlier round is removed when this result has none, as the spec requires. Send it as a new request with a new JSON-RPC id.

## Types

### McpFieldSchema

A form field's schema, as MCP restricts it: a string, number, boolean or enum. Unknown keys pass through.

```ts
{ /** "string", "number", "integer", "boolean" or "array" (a multi-select enum). */ type?: string | undefined /** A short name for the field. */ title?: string | undefined /** What the field is for, in a sentence. */ description?: string | undefined /** The server's default. Shown as the server's suggestion; Candor never fills it in for the person. */ default?: string | number | boolean | string[]
```

### McpElicitFormRequest

An `elicitation/create` request in form mode: a message and a flat schema of primitive fields.

```ts
{ method: "elicitation/create" params: { /** "form", or left out (which means form). */ mode?: "form" | undefined /** Why the server asks, in its own words. */ message: string requestedSchema: { type: "object"; properties: Record<string, McpFieldSchema>; required?: string[] | undefined } } }
```

### McpElicitUrlRequest

An `elicitation/create` request in URL mode: a page the person opens outside the client (sign-in, payment).

```ts
{ method: "elicitation/create" params: { mode: "url" /** Why the server asks, in its own words. */ message: string /** The page to open, only once the person agrees. */ url: string } }
```

### McpInputRequiredResult

A result that asks for more input before the call can finish. At least one of `inputRequests` and `requestState` is present. Values in `inputRequests` are left `unknown`: a server may send kinds this file doesn't know.

```ts
{ resultType: "input_required" /** The server's requests, keyed by its own ids. */ inputRequests?: Record<string, unknown> | undefined /** Opaque to the client: sent back exactly as received, never read or changed. */ requestState?: string | undefined _meta?: Record<string, unknown> | undefined }
```

### McpElicitResult

The answer to one elicitation, as MCP takes it back. `content` only when accepted in form mode.

```ts
{ action: "accept" | "decline" | "cancel" content?: Record<string, string | number | boolean | string[]> | undefined }
```

### McpQuestion

A question in ClarifyingQuestions' shape (`ClarifyingQuestion`), defined here so this free file needs nothing from a paid one. The question's id is the form field's name.

```ts
| { id: string kind: "choice" prompt: string options: { id: string; label: string; detail?: string | undefined }[] multiple?: boolean | undefined /** The server's default, marked as a suggestion and never pre-selected. */ recommended?: string | undefined /** A short label shown above the question: the field's title when it also has a description. */ tag?: string | undefined /** Always false: an MC
```

### McpQuestionAnswer

A person's answer, in ClarifyingQuestions' shape (`ClarifyingAnswer`).

```ts
{ questionId: string; choice?: string[] | undefined; text?: string | undefined }
```

### McpDecision

A two-answer confirmation in ApprovalDecision's shape (`Decision`), defined here so this free file needs nothing from a paid one. The choices' ids are "accept" and "decline", which is what the retry sends.

```ts
{ id: string question: string /** What to decide on, as label and value: the page's host and its full address. */ facts?: { label: string; value: string }[] | undefined /** Accept first, decline second. */ choices: { id: "accept" | "decline"; label: string; detail?: string | undefined }[] state: "open" }
```

### McpInputItem

One of the server's requests, as Candor shows it. `key` is the server's id for it.

```ts
| { key: string; kind: "questions"; message: string; questions: McpQuestion[]; fields: Record<string, McpFieldSchema>; required: string[] } /** A form with no fields: the server asks to go ahead. Answer it with ApprovalDecision. */ | { key: string; kind: "confirm"; message: string; decision: McpDecision } /** * A page to open outside the client. Show the full `url` and its `host` before the person
```

### McpInputView

An input-required result, ready to show.

```ts
{ /** One per request, in the server's order. */ items: McpInputItem[] /** Sent back exactly as received; absent when the server sent none. */ requestState?: string | undefined /** The server's name as your app knows it (passed in), so the person can tell which server asks. */ server?: string | undefined /** The name the server gives itself in the result's `_meta`. Its own claim: show it as such, 
```

### McpInputReply

A person's reply to one item, by its key.

```ts
| { key: string; action: "accept"; answers?: McpQuestionAnswer[] | undefined } /** Declined outright, or dismissed without a choice. */ | { key: string; action: "decline" | "cancel" }
```

### McpInputProblem

Why an answer can't be sent yet.

```ts
{ /** The item's key. */ key: string /** The field, for a form. */ questionId?: string | undefined /** Which rule it broke. */ code: "unanswered" | "required" | "notAnOption" | "notANumber" | "notAnInteger" | "tooSmall" | "tooLarge" | "tooShort" | "tooLong" | "format" | "tooFew" | "tooMany" /** The same in words, from `mcpInputWords`. */ message: string }
```

### McpInputResponses

What a retry sends, or why it can't be sent yet.

```ts
| { ok: true /** The retry's `inputResponses`, keyed by the server's ids. */ inputResponses: Record<string, unknown> /** Keys of requests nothing was sent for (not supported and not answered by your app). The server may ask again. */ notSent: string[] } | { ok: false; problems: McpInputProblem[] }
```

### McpInputWords

The words, with any you pass in place of the defaults.

```ts
Partial<typeof mcpInputWords>
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
