fix(web): use the resources/list entry as the UI metadata fallback

The read RESULT's own `_meta` is not a documented carrier for the sandbox
hints. ext-apps documents the `resources/list` entry's `_meta.ui` as the
static default a host reviews at connection time, with a read content
item's own `_meta.ui` taking precedence (McpUiAppResourceConfig). Swap
the fallback accordingly and thread the listing through a new optional
`getListedResourceMeta` dep, wired in App.tsx from the resources list.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-23 20:42:06 -04:00
commit 0c989a0c34

View file

@ -88,6 +88,15 @@ export interface AppBridgeFactoryDeps {
* frame; the error is also always console.error'd.
*/
onResourceError?: (err: Error) => void;
/**
* The `_meta` of the `resources/list` entry for `uri`, when the host has that
* listing. ext-apps treats the listing-level `_meta.ui` as the static default
* for a UI resource, so an app that declares its CSP only there must still
* get it honored; a read content item's own `_meta.ui` wins over it. Omitted
* (or returning undefined) simply means no default — the host that has no
* resource listing, or whose listing does not include this URI, is unaffected.
*/
getListedResourceMeta?: (uri: string) => object | undefined;
/**
* Advertise `hostCapabilities.elicitation` — "this host can forward a
* form-mode `elicitation/create` to the app and return its result to the
@ -120,10 +129,17 @@ function uiMeta(meta: object | undefined): McpUiResourceMeta | undefined {
/**
* First text content block of a UI resource, plus its `_meta.ui` sandbox hints.
* The spec allows the metadata at either the content-block or the result level,
* so fall back to the result's own `_meta.ui` when the block carries none.
*
* `listedMeta` is the `_meta` of the matching `resources/list` entry, which
* ext-apps documents as the static default a host can review at connection
* time; a `resources/read` content item carrying its own `_meta.ui` takes
* precedence over it (`McpUiAppResourceConfig`). Nothing else is consulted —
* in particular the read RESULT's own `_meta` is not a documented carrier.
*/
function extractHtmlAndMeta(result: ReadResourceResult): {
function extractHtmlAndMeta(
result: ReadResourceResult,
listedMeta?: object,
): {
html: string;
meta: McpUiResourceMeta | undefined;
} {
@ -132,7 +148,7 @@ function extractHtmlAndMeta(result: ReadResourceResult): {
if (typeof text === "string") {
return {
html: text,
meta: uiMeta(content._meta) ?? uiMeta(result._meta),
meta: uiMeta(content._meta) ?? uiMeta(listedMeta),
};
}
}
@ -293,7 +309,10 @@ export function createAppBridgeFactory(
const uri = resolveSourceUri(source);
if (!uri) return;
const result = await deps.readResource(uri);
const { html, meta } = extractHtmlAndMeta(result);
const { html, meta } = extractHtmlAndMeta(
result,
deps.getListedResourceMeta?.(uri),
);
// Build the per-app CSP host-side: filter the requested sources to
// ones the host accepts, render the policy string, and wrap the
// app's HTML in a fixed shell whose first <head> child is the CSP