<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# OuroMcpApp

The view an MCP server returns for agent-composed UI (MCP Apps): the host, such as Claude or ChatGPT, frames it, and it shows the latest tool result's `structuredContent.a2ui` with `A2UISurface`. Read and write buttons call their action's own tool through the host, a consequential one only after the person confirms, and a person-only one takes the person to your app's screen. It follows the host's light or dark theme and tells the host its size.

```tsx
import { OuroMcpApp } from "@/components/ouro/mcp-app/ouro-mcp-app";
```

## Example

```tsx
createRoot(document.getElementById("root")!).render(
  <OuroMcpApp catalog={catalog} handoffUrl={({ action }) => `https://kitchen.example.com/recipes?do=${action}`} />,
);
```

## Usage

Build it into one HTML file with your catalog embedded (`node scripts/mcp-app.mts build`), and serve that as the `ui://` resource.

## Use instead when

- Use A2UISurface instead when the card is in your own app's screen, not in an MCP host.

## Props

Type: `OuroMcpAppProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appInfo` | `{ name: string; version: string }` | { name: "Ouro HX card", version: "1.0.0" } | What the view calls itself in `ui/initialize`. |
| `catalog` (required) | `unknown` |  | The app's A2UI catalog, generated from its actions by core's `ouro-a2ui-catalog.mts catalog`. Tool results carry only its id. |
| `className` | `string` |  | Layout only: margins, width (`max-w-md`). |
| `handoffUrl` | `((handoff: A2UIHandoff) => string \| undefined)` |  | Where a person-only button takes the person: the address of your app's own screen for that action, which the host opens with `ui/open-link`. Without one, or when the host can't open links, the view tells the person where to do it. |
| `host` | `McpAppHostLink` | the window that framed the view, through `windowHostLink()` | The connection to the host. |
| `label` | `A2UISurfaceProps["label"]` |  | The app's own word for an id on a confirm sheet, from its own data, in place of the agent's name. It works for a view you build in code. See A2UISurface's `label`. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `onAction`: Not a prop. A pressed button calls that action's own MCP tool through the host: give the view only the catalog.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `A2UIActionMessage`

The A2UI `action` message a pressed button sends: named after the action's id, with the prepared input as its context.

```ts
interface A2UIActionMessage {
  version: string;
  action: {
    /** The action's id, which is also its MCP tool's name. */
    name: string;
    surfaceId: string;
    sourceComponentId: string;
    timestamp: string;
    /** The button's label, as the person read it. */
    userMessage: string;
    /** The input the agent prepared, by field name. */
    context: Record<string, unknown>;
    /** That the person pressed it, its consent class, and for a consequential action that the person confirmed. */
    metadata: { extensions: { bot_ouroboros_action: { consent: string; by: "person"; confirmed?: true } } };
  };
}
```

### `A2UIHandoff`

Where a person-only button takes the person: the action's id and the declaration's `how`.

```ts
interface A2UIHandoff {
  action: string;
  how: string;
}
```

### `A2UIProblem`

A problem the validator found in the agent's messages, in A2UI's validation error format.

```ts
type A2UIProblem = Problem;
```

### `A2UISurfaceProps`

Props for `A2UISurface`.

```ts
interface A2UISurfaceProps {
  /** The app's A2UI catalog, generated from its actions by core's `ouro-a2ui-catalog.mts catalog`. */
  catalog: unknown;
  /** The agent's A2UI messages, in order. The surface shows where they leave it. */
  messages: readonly unknown[];
  /** Which surface to show. @default the first one the messages create */
  surfaceId?: string | undefined;
  /**
   * Runs a pressed read or write button, and a consequential one once the person confirms. Send the message to
   * the agent, or call the action's own tool with `message.action.context`. Return a promise to show the button
   * loading until it settles.
   */
  onAction: (message: A2UIActionMessage) => void | Promise<void>;
  /**
   * A person-only button never runs from composed UI: take the person to your app's own screen for it.
   */
  onHandoff: (handoff: A2UIHandoff) => void;
  /** The validator's problems, when the messages fail it; the person sees a short alert instead. Hand them to the agent to correct. */
  onProblems?: ((problems: readonly A2UIProblem[]) => void) | undefined;
  /**
   * Names the ids in a confirm sheet's rows from the app's own data, such as a recipe's title for `rcp_lemon_orzo`.
   * Return undefined to keep the name the agent prepared, or the id. The name wins over what the agent prepared,
   * because the agent's text is the one thing the person can't check. Keep it stable, as `messages` is: a new function
   * on each render resolves the messages again.
   *
   * @example
   * label={({ field, value }) => (field === "recipe" ? titles[value] : undefined)}
   */
  label?: ((context: LabelContext) => string | undefined) | undefined;
  /** Layout only: margins, width (`max-w-md`). */
  className?: string | undefined;

  /** Not a prop. A pressed button calls `onAction`, or `onHandoff` for a person-only one: `<A2UISurface onAction={send} onHandoff={openApp}>`. */
  onPress?: { "Use onAction={send} onHandoff={openApp}": never };
}
```
