# `A2UIPhoenix.MCPApp`
[🔗](https://github.com/a2ui-elixir/a2ui/blob/v0.1.1/umbrella/apps/a2ui_phoenix/lib/a2ui_phoenix/mcp_app.ex#L1)

The host side of the MCP catalog's `McpApp` component: what the
component renders from its props, and the checks the host makes on
what the embedded app sends over the MCP Apps bridge (see the A2UI
MCP App component specification). `A2UIPhoenix.Session.mcp_app_request/5`
acts on the requests; `priv/static/a2ui_phoenix.js` runs the bridge in
the browser.

Every payload the app sends is checked before the host acts on it: no
`__proto__`, `constructor` or `prototype` key at any depth, at most 10
levels of nesting, and at most 64 KiB of JSON.

# `change_path`
*since 0.1.0* 

```elixir
@spec change_path(map(), String.t(), String.t() | nil) :: String.t() | nil
```

The path a `ui/notifications/data-model-change` writes: the key's bound
path, extended by `subpath` (a JSON pointer or a plain key), or `nil`
when the key is not bound.

    iex> A2UIPhoenix.MCPApp.change_path(%{"game" => "/game"}, "game", "score")
    "/game/score"
    iex> A2UIPhoenix.MCPApp.change_path(%{"game" => "/game"}, "game", nil)
    "/game"

# `check_payload`
*since 0.1.0* 

```elixir
@spec check_payload(term()) :: :ok | {:error, String.t()}
```

Checks a payload from the app; `{:error, reason}` when it is unsafe.

    iex> A2UIPhoenix.MCPApp.check_payload(%{"a" => [1, 2]})
    :ok
    iex> A2UIPhoenix.MCPApp.check_payload(%{"__proto__" => %{}})
    {:error, "forbidden key '__proto__'"}

# `html`
*since 0.1.0* 

```elixir
@spec html(map()) :: String.t() | nil
```

The app's HTML: `htmlContent`, decoded when it starts with
`url_encoded:`, or `nil` when there is none.

    iex> A2UIPhoenix.MCPApp.html(%{"htmlContent" => "url_encoded:%3Cp%3EHi%3C%2Fp%3E"})
    "<p>Hi</p>"

# `paths`
*since 0.1.0* 

```elixir
@spec paths(map(), A2UICore.Resolution.DataContext.t()) :: %{
  optional(String.t()) =&gt; String.t()
}
```

The data model paths the app binds (`data.paths`), by key, resolved
against `context` (the component's data scope).

# `read`
*since 0.1.0* 

```elixir
@spec read(A2UICore.State.Surface.t(), String.t()) :: term()
```

The value at `path` of the surface's data model (`nil` when absent).

# `reserved_keys`
*since 0.1.0* 

```elixir
@spec reserved_keys() :: [String.t()]
```

The reserved `data.paths` keys: `toolInput`, `toolResult`, `modelContext`.

# `state`
*since 0.1.0* 

```elixir
@spec state(map(), A2UICore.State.Surface.t(), A2UICore.Resolution.DataContext.t()) ::
  map()
```

The current values of the bound paths, by key — what the host keeps
the app in step with. `modelContext` only flows from the app, so it is
left out.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
