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

# Murph chat rendering artifacts

> REST-only rendering artifacts and the standard MCP App result returned by ask_murph

A `POST /api/v2/murph/chat` response always carries the assistant's text
`answer`. On turns where Murph runs a tool that has a canonical interactive
surface, the response **also** carries a structured rendering artifact for the
in-product UI and other direct REST clients.

These named REST fields are not an MCP Apps protocol. External hosts such as
Claude or ChatGPT do not receive the `/murph/chat` response when they call the
`ask_murph` MCP tool; they receive the standard MCP tool result described
below.

<Info>
  Each artifact field is optional and nullable. A client that does not render
  MCP Apps can ignore every artifact and show the text `answer` alone — the
  answer is always self-contained.
</Info>

## How a REST client renders an artifact

Every artifact names the MCP App that renders it and the data to hand it:

1. Load the MCP App at the artifact's `resourceUri`.
2. Pass the artifact's `data` object to the app as the tool result's
   `structuredContent`. The app routes on `data.endpoint` to the named `view`.

The host decides where to place the surface — the in-product Murph UI renders
the discovery widget **above** the text answer, for example.

## `discoveryMcpui`

Emitted on a successful `discover_products` tool call. Renders the product
discovery surface — a browsable, selectable list of the discovered products
grouped by storefront, with per-product brief-relevance reasoning.

```json theme={null}
{
  "discoveryMcpui": {
    "kind": "discover-products",
    "resourceUri": "ui://agentic-api/api-call/mcp-app.html",
    "view": "discover-products",
    "canonicalSurface": true,
    "discoveryId": "disc_7f3a…",
    "data": {
      "success": true,
      "status": 200,
      "endpoint": "/api/v2/buyer/discovery/discover-products",
      "method": "POST",
      "data": {
        "discoveryId": "disc_7f3a…",
        "productGroups": [],
        "proposals": []
      }
    }
  }
}
```

| Field              | Type                                       | Notes                                                                                                                                                                                          |
| ------------------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`             | `"discover-products"`                      | Identifies the artifact.                                                                                                                                                                       |
| `resourceUri`      | `"ui://agentic-api/api-call/mcp-app.html"` | The MCP App that renders it.                                                                                                                                                                   |
| `view`             | `"discover-products"`                      | The view the app routes to.                                                                                                                                                                    |
| `canonicalSurface` | `true`                                     | The artifact is the canonical surface for this result; do not rebuild it from the text answer.                                                                                                 |
| `discoveryId`      | string                                     | The discovery session id, when present. Lets the widget re-fetch products (`GET /api/v2/buyer/discovery/{discoveryId}/products`) so it can self-hydrate even if `data` was truncated for size. |
| `data`             | object                                     | The `discover_products` response envelope (`success`, `status`, `endpoint`, `method`, `data`). Hand this to the app as `structuredContent`.                                                    |

When this artifact is present, Murph's text `answer` is a concise orientation
(totals, price range, any recommended plans) plus next steps — it deliberately
does **not** re-list every product, because the widget already shows the full,
selectable set.

## Other rendering artifacts

The chat response uses the same pattern for other canonical surfaces:

| Field            | Emitted on                                                            | `resourceUri`                                      | `view`               |
| ---------------- | --------------------------------------------------------------------- | -------------------------------------------------- | -------------------- |
| `creativeMcpui`  | a successful creative-session call                                    | `ui://agentic-api/api-call/mcp-app.html`           | `creative-iteration` |
| `inventoryMcpui` | a seller inventory-selector / capabilities call                       | `ui://agentic-api/inventory-selector/mcp-app.html` | `inventory-selector` |
| `mcpAppWidget`   | an `open_<widget>` directive (e.g. a seller setup or credential form) | the named widget's `ui://…` resource               | —                    |

Each carries `kind`, `resourceUri`, `canonicalSurface`, and a `data` payload to
hand the app as `structuredContent`; `creativeMcpui` additionally carries a
`refresh` directive describing the tool call that reloads the surface, and
`inventoryMcpui` carries the `esaId` and the initial selector filters.

## `ask_murph` MCP tool result (conformant MCP Apps)

The artifacts above ride the **REST** `/api/v2/murph/chat` response as named
fields — the in-product Murph UI reads them directly. An external MCP Apps host
(Claude, ChatGPT) that calls the **`ask_murph` MCP tool** does not receive that
REST response directly; it receives a standard tool result. Some result kinds
retain named compatibility data inside `structuredContent`, but rendering is
selected by standard result metadata:

* It selects **one primary widget per turn** — this result contract carries a
  single render payload. Precedence, highest-intent first:
  `mcpAppWidget` → `creativeIntentMcpui` → `inventoryMcpui` → `creativeMcpui` →
  `discoveryMcpui` → `sellerAnalytics` → `murphEscalations`.
* The result embeds that widget's `ui://` resource in `content` and sets a
  per-call `_meta` (`ui.resourceUri` and the legacy `ui/resourceUri`) so the host
  loads the right MCP App for this result.
* For api-call-style widgets (discovery, inventory, creative, creative-intent),
  the app reads the flat api-call envelope (`success`, `endpoint`, `params`,
  `data`), so that envelope is spread onto the **top level** of
  `structuredContent` where `ontoolresult` reads it. The now-redundant named
  field is dropped so the payload isn't carried twice.
* Launch widgets (`mcpAppWidget`) and named-field panels (`sellerAnalytics`,
  `murphEscalations`) carry no api-call envelope; their data already sits under
  their named key in `structuredContent`, which the host hands the app.

A turn that produced several widgets still renders **one** in a generic host;
the in-product drawer can show all of them via the REST fields above.

## Sales Agent validation handoff

The Agent page can pass a server-recognized validation context to `ask_murph`.
It supplies identifiers only: the Agent, its returned skill version, an
available profile, and, when present, a Source. It never supplies credentials
or expands the caller's authority.

A transaction validation can return `salesAgentValidation` with
`status: "PENDING_CONFIRMATION"`. Its `runId`, `implementationRevision`,
`generation`, `pendingStepId`, and
`nextAction: "CONFIRM_TRANSACTION_STEP"` identify the one no-spend action
waiting for the owner. `implementationRevision` is read-only evidence. To
continue the run, return `agentId`, `skillVersion`, and
`profile: "transaction"` with the returned `runId` and `pendingStepId`, then
map `generation` to `confirmationGeneration` and `nextAction` to
`confirmation`. The service issues a separate, generation-fenced confirmation
before staging a media buy and before activating the sandbox campaign.

Do not construct or reuse a confirmation from another run. A stale, expired,
cancelled, or consumed confirmation cannot be replayed; start a new validation.
A completed result identifies its implementation revision and cleanup outcome.
It is validation evidence, not a certificate or authorization for a client
connection.

## Protected Sales Agent certification request

A directly authenticated administrator of the Agent owner's organization can
ask Murph to record a request for protected technical certification evaluation.
The request is accepted only when the exact current implementation already has
an unexpired protected fixture and immutable evaluation template. The owner
cannot provide either value, dispatch the work, or report an assertion result.

Murph returns `salesAgentCertificationRequest` when it records that intent:

```json theme={null}
{
  "salesAgentCertificationRequest": {
    "actionUid": "2d6d3a85-0d82-4b8e-a1b2-9af79e998602",
    "implementationRevision": "190742ce-02de-4b4c-a60a-d3326d7c411e",
    "status": "QUEUED_FOR_PROTECTED_EVALUATION"
  }
}
```

`actionUid` identifies the immutable owner request and
`implementationRevision` identifies the implementation it applies to. The
`QUEUED_FOR_PROTECTED_EVALUATION` status means a protected system must still
recheck the pinned fixture and template before it can create or dispatch any
work. It does not mean a provider test ran, an assertion passed, or the Agent
is certified. Creative and complete-suite certification results remain
separate evidence.

## Conversational MCP profile

The existing Murph MCP endpoint also exposes a portable conversation profile.
MCP clients on buyer and seller accounts use the same two tools; existing
`ask_murph` calls keep their current request and response shape.

### `chat`

Call `chat` to begin a conversation or add a turn. The request has one message
and may include up to five Base64 attachments, each no larger than 1 MiB after
decoding.

```json theme={null}
{
  "conversationId": "optional-existing-conversation-id",
  "message": {
    "text": "Show the campaign status",
    "attachments": [
      {
        "filename": "brief.txt",
        "mimeType": "text/plain",
        "base64": "..."
      }
    ]
  }
}
```

A completed response is a normal MCP `CallToolResult`. Read the portable turn
from `structuredContent.conversation`:

```json theme={null}
{
  "content": [{ "type": "text", "text": "Campaign status is ready." }],
  "structuredContent": {
    "conversation": {
      "conversationId": "conversation-id",
      "turnId": "turn-id",
      "status": "completed",
      "messages": []
    }
  }
}
```

`status` is `completed`, `input_required`, or `failed`. Messages contain only
display-safe user and assistant text; tool traces, internal context, and
credentials are never returned. A protocol failure instead sets `isError` and
may return only text or an error object in `structuredContent`.

When a turn declares an app widget, it appears as `structuredContent.widget`.
The result selects the widget through the standard `_meta.ui.resourceUri`
directive. Hosts must use that shared MCP Apps contract; they must not infer a
widget or route from Murph's text.

#### Progress on a long turn

A `chat` turn runs Murph's full tool loop, so an involved question can take
minutes to answer. Set the MCP protocol's `_meta.progressToken` on the
`tools/call` request and the server sends `notifications/progress` messages
while the turn works, so a host can show the caller that a long turn is
running rather than hung.

Each notification carries a short `message` describing the current phase —
"Reading the docs…", "Checking your account…", "Writing it up…" — written in
the language the conversation is already being answered in. Internal tool
names are never sent; the phrases group tools by intent. The first
notification goes out before the turn starts, so a turn that calls no tools
still reports that it began. `progress` increments on each distinct phase and
no `total` is sent, because the number of phases is not known in advance —
render it as indeterminate progress rather than a percentage.

Progress is advisory. The tool result stays the authoritative answer, a client
may ignore the notifications, and a client that sends no progress token gets
exactly the synchronous behaviour it got before.

The portable profile also permits the MCP Tasks extension for a turn that a
server does not complete synchronously. Such a result is the native
`CreateTaskResult` task envelope (`taskId`, `status`, `createdAt`,
`lastUpdatedAt`, `ttl`, and optional `pollInterval`), which a client polls or
cancels using MCP Tasks. Murph's current orchestration completes this profile
call synchronously.

### `get_conversation`

Call `get_conversation` with a conversation ID to read display-safe turns. Set
`after` to the opaque cursor from `nextAfter` to request the following page.

```json theme={null}
{
  "conversationId": "conversation-id",
  "after": "optional-turn-cursor"
}
```

The structured result contains `conversationId`, `turns`, and `nextAfter` when
another page is available. It does not create an agent-initiated message or
subscribe the caller to notifications.

## Token usage

Every `POST /api/v2/murph/chat` response carries a `usage` object reporting the
turn's token consumption, cumulative across the tool-loop iterations of the turn.

```json theme={null}
{
  "usage": {
    "inputTokens": 1840,
    "outputTokens": 320,
    "cacheReadInputTokens": 11200,
    "cacheCreationInputTokens": 640,
    "latencyMs": 4200,
    "model": "claude-sonnet-5"
  }
}
```

| Field                      | Type    | Notes                                                                                                       |
| -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
| `inputTokens`              | integer | Uncached input tokens for the turn. Does **not** include tokens served from or written to the prompt cache. |
| `outputTokens`             | integer | Tokens generated in the response.                                                                           |
| `cacheReadInputTokens`     | integer | Input tokens served from the prompt cache.                                                                  |
| `cacheCreationInputTokens` | integer | Input tokens written to the prompt cache on this turn.                                                      |
| `latencyMs`                | integer | Wall-clock duration of the turn, in milliseconds.                                                           |
| `model`                    | string  | The model that produced the answer.                                                                         |

<Info>
  The model reports cache reads and creations **separately** from `inputTokens`
  — they are not included in it. To get the true total input volume for a turn,
  sum `inputTokens + cacheReadInputTokens + cacheCreationInputTokens`. Murph
  caches its system prompt and tool surface, so on a warm conversation
  `cacheReadInputTokens` is typically the largest of the three.
</Info>

## Related

<CardGroup cols={2}>
  <Card title="Conversation scope" href="/v2/api/murph/conversation-scope" icon="sitemap">
    Set a Murph conversation's account or advertiser scope.
  </Card>

  <Card title="Ask Murph" href="/v2/setup/ask-murph" icon="sparkles">
    How Murph works as the in-product assistant.
  </Card>
</CardGroup>
