A2UIPhoenix.MCPApp (A2UIPhoenix v0.1.1)

Copy Markdown View Source

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.

Summary

Functions

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.

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

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

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

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

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

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.

Functions

change_path(paths, key, subpath)

(since 0.1.0)
@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(payload)

(since 0.1.0)
@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(arg1)

(since 0.1.0)
@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(props, context)

(since 0.1.0)
@spec paths(map(), A2UICore.Resolution.DataContext.t()) :: %{
  optional(String.t()) => String.t()
}

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

read(surface, path)

(since 0.1.0)
@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)
@spec reserved_keys() :: [String.t()]

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

state(props, surface, context)

(since 0.1.0)

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.