---
title: "Other Surfaces"
description: "Everything that calls an action beyond the UI hooks: native chat UI widget results, the CLI, A2A, MCP, and the two standard actions every template ships."
---

# Other Surfaces

The UI-hook surface is covered on [Data & Sync](/docs/client-data): `useActionQuery()`, `useActionMutation()`, and `callAction()`. This page covers everything else that can call, or be driven by, an action.

## Rendering native chat UI {#native-chat-ui}

Actions can return structured widget data that the in-app chat renders
natively. This is the first-party chat path for reusable tables, charts, setup
summaries, and insight cards. Use [Generative UI](/docs/generative-ui) when the
agent needs to generate arbitrary interactive controls inside the in-app chat,
and use [MCP Apps](/docs/mcp-apps) for inline UI in external MCP hosts.

```ts
import { defineAction } from "@agent-native/core/action";
import { ACTION_CHAT_UI_DATA_CHART_RENDERER } from "@agent-native/core/action-ui";
import {
  createDataChartWidgetResult,
  dataChartWidgetResultSchema,
} from "@agent-native/core/data-widgets";

export default defineAction({
  description: "Summarize response trends.",
  readOnly: true,
  outputSchema: dataChartWidgetResultSchema,
  chatUI: { renderer: ACTION_CHAT_UI_DATA_CHART_RENDERER },
  run: async () =>
    createDataChartWidgetResult({
      title: "Response trends",
      chartSeries: {
        type: "line",
        xKey: "day",
        series: [{ key: "responses", label: "Responses" }],
        data: [
          { day: "Mon", responses: 12 },
          { day: "Tue", responses: 18 },
        ],
      },
    }),
});
```

The built-in discriminants are `"data-table"`, `"data-chart"`, and
`"data-insights"`, with server-safe builders and schemas in
`@agent-native/core/data-widgets`. Use `data-chart` for chart-only answers,
`data-table` for SQL/list results, and `data-insights` only when a combined
summary/chart/table card is the right output. See
[Native Chat UI](/docs/native-chat-ui) for the full result contract, or
[BYO agent runtimes](/docs/native-chat-ui#byo-agent-runtimes) if you're
wiring a custom agent runtime instead of the built-in chat loop. See
[Agent Surfaces](/docs/agent-surfaces) for how the same action can stay
automation-friendly, render in chat, or grow into a full screen.

## Calling it from the CLI {#cli}

Every action is runnable via `pnpm action`:

```bash
pnpm action reply-to-email '{"emailId":"thread-123","body":"Thanks!"}'
```

JSON input is the preferred shape for agents and complex objects. Flags work
the same way for simple manual runs and existing scripts:

```bash
pnpm action reply-to-email --emailId thread-123 --body "Thanks!"
```

For `defineAction()` calls, the CLI evaluates `needsApproval` on prepared, parsed arguments with schema defaults applied and caller `cli`. If stdin and stdout are TTYs, it prints the action name and arguments and prompts `y/N`. Only `y` or `yes` runs the action.

Without both TTYs, the CLI asks the agent to ask the person for approval. After the person approves that specific call, rerun the exact original `pnpm action` arguments with `--approved` appended:

```bash
pnpm action reply-to-email --emailId thread-123 --body "Thanks!" --approved
```

`--approved` is reserved CLI metadata, stripped before input parsing. It takes no value: a call with anything other than another flag or a JSON object after it is refused, and an input named `approved` is written `--approved=<value>`. It is harmless on ungated actions. The CLI can't check who added it, so an agent that adds it without asking skips the prompt. The flag keeps the approval visible in the command the person approves and in the audit log, which marks it unverified. On Windows without a POSIX `SHELL`, the refusal prints the rerun command for PowerShell and, when the quoting differs, for cmd.exe. On Windows, pnpm passes action arguments through cmd.exe from any shell, and cmd.exe expands `%NAME%` even inside quotes. When an argument contains one, the refusal offers no `--approved` rerun: the person runs the command in a terminal and checks the values at the prompt. If the arguments have a quote and one of `&`, `|`, `<`, `>` or `^`, pnpm can't pass them through cmd.exe intact, so the refusal shows no command, and the person approves the call in the app instead. Windows PowerShell 5.1 drops every `"` inside an argument, so on Windows without a POSIX `SHELL`, an argument with a double quote also goes to the prompt instead of an `--approved` rerun.

The local dev bridge sends approval metadata outside `input` in the JSON body:

```ts
{ name, input, approved: true, approvalSource: "flag" }
```

For a gated call without approval, the bridge returns HTTP `409`:

```ts
{
  ok: false,
  errorCode: "approval_required",
  error: 'Action "<name>" requires human approval. Ask the person, then rerun with --approved.',
  arguments: preparedArgs,
  argumentsHash: prepared.argumentsHash,
}
```

After TTY confirmation, the CLI retries with the challenge response’s `argumentsHash`:

```ts
{
  name,
  input,
  approved: true,
  approvalSource: "prompt",
  approvalArgumentsHash: challenge.argumentsHash,
}
```

The bridge runs the call the person reviewed, once, so a default that differs per call, such as a timestamp, keeps the value they saw. A confirmation with no held call to run (it already ran, 10 minutes passed, or the dev server restarted) gets HTTP `409` with `errorCode: "approval_expired"`. Run the command again to review it.

`arguments` contains the prepared arguments. This local dev bridge contract does not change the [public HTTP action API's approval rules](/docs/actions-access-control#needs-approval). An approved gated CLI call records `approval: "cli-prompt"` or `approval: "cli-flag"` on the action's own audit row, with caller `cli`. The audit log shows both as unverified, since only the CLI sees the prompt.

## Calling it from another agent (A2A) {#a2a}

If your app is an [A2A](/docs/a2a-protocol) peer, other agent-native apps discover your actions automatically and can call them by name. Same-origin deploys skip JWT signing. Cross-origin deploys use a shared `A2A_SECRET` instead.

## Exposing it over MCP {#mcp}

With MCP enabled, your actions show up in the framework's MCP server at `/mcp`. Every caller gets a compact catalog by default (app-facing builtins plus the template-declared app actions), and `tool-search` is always present so any other tool stays reachable on demand. The full action surface is served only on explicit opt-in (`--full-catalog` token or `AGENT_NATIVE_MCP_FULL_CATALOG=1`), and [`publicAgent.expose`](/docs/actions-access-control#public-agent) opts a safe read/ingest tool onto the public surface. See [MCP Protocol](/docs/mcp-protocol) for catalog tiers, auth, and the `mcpApp` resource details.

For UI-capable MCP hosts, an action can declare an optional MCP Apps resource via the `mcpApp` field (plus a matching `link`) so capable hosts render the result inline. When `link` and `mcpApp` should point at the same route, `embedRoute()` builds both from one pure path builder:

```ts
import { embedRoute } from "@agent-native/core";

export default defineAction({
  description: "Create an email draft for review.",
  schema: z.object({ body: z.string() }),
  run: async ({ body }) => ({ body }),
  ...embedRoute({
    title: "Review draft",
    openLabel: "Open in Mail",
    path: ({ result }) => ({
      label: "Open draft in Mail",
      url: "/_agent-native/open?app=mail&view=inbox",
    }),
  }),
});
```

Keep `link` as the fallback for CLI and non-UI MCP hosts. It is also the embed's launch target. The embed bridge (the signed embed-start session, transplant vs. controlled-frame rendering, the `ui/*` host bridge, CSP, and height clamping) is owned by [External Agents](/docs/external-agents#mcp-app-bridge).

There is no automated check that a template has `view-screen`, `navigate`, or a create/read/update/list set for its primary resource; the scaffold ships `view-screen` and `navigate`, and `pnpm guard:external-result-contract` fails wait-for-the-user language in an externally exposed action's `description` or `nextRequiredAction` and requires a deep link on a new externally exposed `create-*` action. Presence is a review item.

## Standard actions {#standard-actions}

Every template should include these two for [context awareness](/docs/context-awareness).

### view-screen {#view-screen}

Reads the current navigation state, fetches contextual data, and returns a snapshot of what the user sees. The agent calls this when it needs a fresh look at the screen.

```ts
// actions/view-screen.ts
import { defineAction } from "@agent-native/core/action";
import { readAppState } from "@agent-native/core/application-state";
import { z } from "zod";

export default defineAction({
  description: "Read the current screen state for context.",
  schema: z.object({}),
  http: { method: "GET" },
  run: async () => {
    const navigation = await readAppState("navigation");
    const screen: Record<string, unknown> = { navigation };

    if (navigation?.view === "inbox") {
      screen.emailList = await listEmailsForLabel(navigation.label);
    }

    return screen;
  },
});
```

### navigate {#navigate}

Writes a one-shot navigation command to application state. The UI reads it, navigates, and deletes the entry.

```ts
// actions/navigate.ts
import { defineAction } from "@agent-native/core/action";
import { writeAppState } from "@agent-native/core/application-state";
import { z } from "zod";

export default defineAction({
  description: "Navigate the user to a view.",
  schema: z.object({
    view: z.string(),
    threadId: z.string().optional(),
  }),
  run: async (args) => {
    await writeAppState("navigate", args);
    return { ok: true };
  },
});
```

## What's next

- [**Data & Sync**](/docs/client-data) — `useActionQuery`, `useActionMutation`, and `callAction` from the UI
- [**Overview**](/docs/actions-overview) — what an action is and the hello-world walkthrough
- [**Context Awareness**](/docs/context-awareness) — the `view-screen` + `navigate` pattern in depth
- [**A2A Protocol**](/docs/a2a-protocol) — how other agents discover and call your actions
- [**MCP Protocol**](/docs/mcp-protocol) — catalog tiers, auth, and `mcpApp` resources
- [**Generative UI**](/docs/generative-ui) — rendering transient or persisted generated UI inline in chat
- [**Native Chat UI**](/docs/native-chat-ui) — the full widget result contract
