@guuey/mcp-apps-host 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,13 @@ role's client-side narrowing and mount contract:
18
18
  result) — never by replaying stored mount material. The read transport is injected
19
19
  (`UiResourceReader`); the host owns auth and user-ownership enforcement,
20
20
  and a deny is byte-identical to a miss.
21
+ - **Generic reader assembly** (`createMcpUiResourceReader`): hand it one
22
+ host-owned `resources/read` callable (a raw MCP client, an authenticated
23
+ proxy — no SDK dependency imposed) and get a `UiResourceReader` back, with
24
+ the trust rules built in: deny == miss == placeholder, and the sandbox
25
+ channel derives from the _requested_ locator uri, never the response
26
+ (`uiResourceChannel`). `@guuey/agent-client`'s `createUiResourceReader` is
27
+ this assembly over guuey's platform proxy.
21
28
  - **Sandbox-trust channels** (`ViewMountChannel`): which sandbox host page a
22
29
  payload may mount in, until per-resource declared-CSP construction lands.
23
30
 
@@ -51,15 +51,23 @@ export type ViewMountChannel = "inline" | "ggui" | "locator";
51
51
  * + its tool result — see the conformance map.) Until a reader is wired, the honest render
52
52
  * is the host's own placeholder, never a stale mount.
53
53
  */
54
- export type ViewMount = {
54
+ export type ViewMount = ResolvedViewMount | LocatorViewMount;
55
+ /**
56
+ * A view with mount material in hand — the arms a host can render directly,
57
+ * and the ONLY arms a `UiResourceReader` resolves (guuey#127): a read either
58
+ * yields mount material or the honest placeholder, never another locator.
59
+ */
60
+ export interface ResolvedViewMount {
55
61
  channel: "inline" | "ggui";
56
62
  /** The payload an mcp-ui host mounts, identical in shape for both channels. */
57
63
  resource: McpUiResourcePayload;
58
- } | {
64
+ }
65
+ /** The durable-identity arm: no mount material, only the uri to re-fetch. */
66
+ export interface LocatorViewMount {
59
67
  channel: "locator";
60
68
  /** The persisted `uiData.resourceUri` (`ui://` scheme) to re-fetch. */
61
69
  resourceUri: string;
62
- };
70
+ }
63
71
  /**
64
72
  * Resolves a `"locator"` mount by a fresh `resources/read` of the uri over
65
73
  * an AUTHENTICATED channel the HOST owns — guuey must enforce its own
@@ -1 +1 @@
1
- {"version":3,"file":"card-mount.d.ts","sourceRoot":"","sources":["../src/card-mount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAuD,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE/G,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAW/D,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,SAAS,GACjB;IACE,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,oBAAoB,CAAC;CAChC,GACD;IACE,OAAO,EAAE,SAAS,CAAC;IACnB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAEvF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC,GAC/C,SAAS,GAAG,SAAS,CAkBvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAUhF"}
1
+ {"version":3,"file":"card-mount.d.ts","sourceRoot":"","sources":["../src/card-mount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAuD,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE/G,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAW/D,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,oBAAoB,CAAC;CAChC;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,SAAS,CAAC;IACnB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAEvF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC,GAC/C,SAAS,GAAG,SAAS,CAkBvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAUhF"}
package/dist/index.d.ts CHANGED
@@ -5,5 +5,6 @@
5
5
  */
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultUiResource, uiLocator, type McpUiResourcePayload, } from "./block-ui.js";
7
7
  export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, type GguiRenderBootstrap, type GguiRenderDescriptor, type GguiShellHtmlOptions, } from "./ggui-render.js";
8
- export { snapshotViewMount, toolResultViewMount, type UiResourceReader, type ViewMount, type ViewMountChannel, } from "./card-mount.js";
8
+ export { snapshotViewMount, toolResultViewMount, type LocatorViewMount, type ResolvedViewMount, type UiResourceReader, type ViewMount, type ViewMountChannel, } from "./card-mount.js";
9
+ export { createMcpUiResourceReader, uiResourceChannel, type CreateMcpUiResourceReaderDeps, type McpResourceReadResult, } from "./reader.js";
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -6,3 +6,4 @@
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultUiResource, uiLocator, } from "./block-ui.js";
7
7
  export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, } from "./ggui-render.js";
8
8
  export { snapshotViewMount, toolResultViewMount, } from "./card-mount.js";
9
+ export { createMcpUiResourceReader, uiResourceChannel, } from "./reader.js";
@@ -0,0 +1,42 @@
1
+ import type { ResolvedViewMount, ViewMountChannel } from "./card-mount.js";
2
+ /**
3
+ * One `resources/read` `contents[]` entry, structurally (SEP-1865). Kept a
4
+ * separate type from {@link McpUiResourcePayload} even though the shapes
5
+ * coincide today: this is the UNVALIDATED wire-side entry a transport hands
6
+ * over (phase 2 grows `_meta` here — per-resource declared CSP, conformance
7
+ * map step 3), while the payload is the narrowed thing a host mounts.
8
+ */
9
+ export interface McpResourceReadResult {
10
+ uri: string;
11
+ mimeType?: string;
12
+ text?: string;
13
+ /** Base64 payload arm — hosts decode via {@link resourceHtml}. */
14
+ blob?: string;
15
+ }
16
+ /**
17
+ * Phase-1 sandbox-trust channel resolution (conformance map, retirement
18
+ * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
19
+ * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
20
+ * anything else through the self-only page (`"inline"` — arbitrary tenant
21
+ * HTML). Retired when servers declare runtime/ws origins per resource
22
+ * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
23
+ * signal a host has BEFORE fetching mount material.
24
+ */
25
+ export declare function uiResourceChannel(resourceUri: string): Exclude<ViewMountChannel, "locator">;
26
+ /** The host-supplied transport {@link createMcpUiResourceReader} assembles over. */
27
+ export interface CreateMcpUiResourceReaderDeps {
28
+ /**
29
+ * One `resources/read` of `uri` on the host's own MCP connection (or an
30
+ * equivalent authenticated proxy), returning the first `contents[]` entry —
31
+ * or `undefined` when the read yields none. Throwing is treated as a miss.
32
+ */
33
+ readResource: (uri: string) => Promise<McpResourceReadResult | undefined>;
34
+ }
35
+ /**
36
+ * Assemble a `UiResourceReader` from a host-supplied `resources/read`
37
+ * transport. The returned reader resolves only the mountable arms — a read
38
+ * either yields mount material or the honest placeholder, never another
39
+ * locator.
40
+ */
41
+ export declare function createMcpUiResourceReader(deps: CreateMcpUiResourceReaderDeps): (resourceUri: string) => Promise<ResolvedViewMount | undefined>;
42
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAE3F;AAED,oFAAoF;AACpF,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;CAC3E;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,6BAA6B,GAClC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAsBjE"}
package/dist/reader.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Phase-1 sandbox-trust channel resolution (conformance map, retirement
3
+ * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
4
+ * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
5
+ * anything else through the self-only page (`"inline"` — arbitrary tenant
6
+ * HTML). Retired when servers declare runtime/ws origins per resource
7
+ * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
8
+ * signal a host has BEFORE fetching mount material.
9
+ */
10
+ export function uiResourceChannel(resourceUri) {
11
+ return resourceUri.startsWith("ui://ggui/") ? "ggui" : "inline";
12
+ }
13
+ /**
14
+ * Assemble a `UiResourceReader` from a host-supplied `resources/read`
15
+ * transport. The returned reader resolves only the mountable arms — a read
16
+ * either yields mount material or the honest placeholder, never another
17
+ * locator.
18
+ */
19
+ export function createMcpUiResourceReader(deps) {
20
+ return async (resourceUri) => {
21
+ let entry;
22
+ try {
23
+ entry = await deps.readResource(resourceUri);
24
+ }
25
+ catch {
26
+ return undefined; // transport failure == deny == miss → placeholder
27
+ }
28
+ if (!entry)
29
+ return undefined;
30
+ // Runtime re-narrowing, not trust in the annotation: transports are
31
+ // host-supplied and may be plain JS. Same field rules as `asResourcePayload`,
32
+ // applied to the typed entry (which may carry extra wire fields — dropped).
33
+ if (typeof entry.uri !== "string")
34
+ return undefined;
35
+ if (typeof entry.text !== "string" && typeof entry.blob !== "string")
36
+ return undefined;
37
+ const resource = {
38
+ uri: entry.uri,
39
+ ...(typeof entry.mimeType === "string" ? { mimeType: entry.mimeType } : {}),
40
+ ...(typeof entry.text === "string" ? { text: entry.text } : {}),
41
+ ...(typeof entry.blob === "string" ? { blob: entry.blob } : {}),
42
+ };
43
+ return { channel: uiResourceChannel(resourceUri), resource };
44
+ };
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/mcp-apps-host",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The MCP Apps (SEP-1865) Host role for guuey's chat surfaces — view-mount narrowing across UI channels, ui:// locator rehydration by resources/read, and the sandbox-trust channel contract. Vendor-neutral: any spec-following MCP App mounts through it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/card-mount.ts CHANGED
@@ -63,17 +63,25 @@ export type ViewMountChannel = "inline" | "ggui" | "locator";
63
63
  * + its tool result — see the conformance map.) Until a reader is wired, the honest render
64
64
  * is the host's own placeholder, never a stale mount.
65
65
  */
66
- export type ViewMount =
67
- | {
68
- channel: "inline" | "ggui";
69
- /** The payload an mcp-ui host mounts, identical in shape for both channels. */
70
- resource: McpUiResourcePayload;
71
- }
72
- | {
73
- channel: "locator";
74
- /** The persisted `uiData.resourceUri` (`ui://` scheme) to re-fetch. */
75
- resourceUri: string;
76
- };
66
+ export type ViewMount = ResolvedViewMount | LocatorViewMount;
67
+
68
+ /**
69
+ * A view with mount material in hand the arms a host can render directly,
70
+ * and the ONLY arms a `UiResourceReader` resolves (guuey#127): a read either
71
+ * yields mount material or the honest placeholder, never another locator.
72
+ */
73
+ export interface ResolvedViewMount {
74
+ channel: "inline" | "ggui";
75
+ /** The payload an mcp-ui host mounts, identical in shape for both channels. */
76
+ resource: McpUiResourcePayload;
77
+ }
78
+
79
+ /** The durable-identity arm: no mount material, only the uri to re-fetch. */
80
+ export interface LocatorViewMount {
81
+ channel: "locator";
82
+ /** The persisted `uiData.resourceUri` (`ui://` scheme) to re-fetch. */
83
+ resourceUri: string;
84
+ }
77
85
 
78
86
  /**
79
87
  * Resolves a `"locator"` mount by a fresh `resources/read` of the uri over
package/src/index.ts CHANGED
@@ -30,7 +30,15 @@ export {
30
30
  export {
31
31
  snapshotViewMount,
32
32
  toolResultViewMount,
33
+ type LocatorViewMount,
34
+ type ResolvedViewMount,
33
35
  type UiResourceReader,
34
36
  type ViewMount,
35
37
  type ViewMountChannel,
36
38
  } from "./card-mount.js";
39
+ export {
40
+ createMcpUiResourceReader,
41
+ uiResourceChannel,
42
+ type CreateMcpUiResourceReaderDeps,
43
+ type McpResourceReadResult,
44
+ } from "./reader.js";
package/src/reader.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The generic SEP-1865 locator reader (guuey#127): resolve a persisted
3
+ * `ui://` locator ({@link LocatorViewMount}) into a mountable
4
+ * {@link ResolvedViewMount} by ONE fresh `resources/read` over a transport
5
+ * the HOST supplies.
6
+ *
7
+ * The transport is a structural callable — deliberately no
8
+ * `@modelcontextprotocol/sdk` dependency — because hosts hold wildly
9
+ * different connections: a raw MCP client (ggui's `with-guuey-web` sample
10
+ * keeps one for guest `tools/call` relay), an authenticated platform proxy
11
+ * (`@guuey/agent-client`'s `createUiResourceReader` is exactly this assembly
12
+ * over guuey's `GET /v1/threads/:threadId/ui-resource` route), or anything
13
+ * else that can perform one read. The host adapts whatever client it holds.
14
+ *
15
+ * Trust rules, identical to the platform reader and now in ONE place:
16
+ *
17
+ * - a transport deny, a miss, and a thrown transport error are all
18
+ * `undefined` → the host renders its placeholder, never an error surface
19
+ * (deny == miss — no oracle for which locators resolve);
20
+ * - enforcement (user-ownership, tenancy, credential handling) belongs
21
+ * INSIDE the transport, before any bytes come back — this assembly never
22
+ * sees a credential;
23
+ * - the sandbox-trust channel derives from the REQUESTED locator uri, never
24
+ * from the response — a server answering with a foreign `ui://ggui/…` uri
25
+ * must not steer its HTML into the ggui-CSP host page.
26
+ */
27
+ import type { McpUiResourcePayload } from "./block-ui.js";
28
+ import type { ResolvedViewMount, ViewMountChannel } from "./card-mount.js";
29
+
30
+ /**
31
+ * One `resources/read` `contents[]` entry, structurally (SEP-1865). Kept a
32
+ * separate type from {@link McpUiResourcePayload} even though the shapes
33
+ * coincide today: this is the UNVALIDATED wire-side entry a transport hands
34
+ * over (phase 2 grows `_meta` here — per-resource declared CSP, conformance
35
+ * map step 3), while the payload is the narrowed thing a host mounts.
36
+ */
37
+ export interface McpResourceReadResult {
38
+ uri: string;
39
+ mimeType?: string;
40
+ text?: string;
41
+ /** Base64 payload arm — hosts decode via {@link resourceHtml}. */
42
+ blob?: string;
43
+ }
44
+
45
+ /**
46
+ * Phase-1 sandbox-trust channel resolution (conformance map, retirement
47
+ * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
48
+ * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
49
+ * anything else through the self-only page (`"inline"` — arbitrary tenant
50
+ * HTML). Retired when servers declare runtime/ws origins per resource
51
+ * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
52
+ * signal a host has BEFORE fetching mount material.
53
+ */
54
+ export function uiResourceChannel(resourceUri: string): Exclude<ViewMountChannel, "locator"> {
55
+ return resourceUri.startsWith("ui://ggui/") ? "ggui" : "inline";
56
+ }
57
+
58
+ /** The host-supplied transport {@link createMcpUiResourceReader} assembles over. */
59
+ export interface CreateMcpUiResourceReaderDeps {
60
+ /**
61
+ * One `resources/read` of `uri` on the host's own MCP connection (or an
62
+ * equivalent authenticated proxy), returning the first `contents[]` entry —
63
+ * or `undefined` when the read yields none. Throwing is treated as a miss.
64
+ */
65
+ readResource: (uri: string) => Promise<McpResourceReadResult | undefined>;
66
+ }
67
+
68
+ /**
69
+ * Assemble a `UiResourceReader` from a host-supplied `resources/read`
70
+ * transport. The returned reader resolves only the mountable arms — a read
71
+ * either yields mount material or the honest placeholder, never another
72
+ * locator.
73
+ */
74
+ export function createMcpUiResourceReader(
75
+ deps: CreateMcpUiResourceReaderDeps,
76
+ ): (resourceUri: string) => Promise<ResolvedViewMount | undefined> {
77
+ return async (resourceUri) => {
78
+ let entry: McpResourceReadResult | undefined;
79
+ try {
80
+ entry = await deps.readResource(resourceUri);
81
+ } catch {
82
+ return undefined; // transport failure == deny == miss → placeholder
83
+ }
84
+ if (!entry) return undefined;
85
+ // Runtime re-narrowing, not trust in the annotation: transports are
86
+ // host-supplied and may be plain JS. Same field rules as `asResourcePayload`,
87
+ // applied to the typed entry (which may carry extra wire fields — dropped).
88
+ if (typeof entry.uri !== "string") return undefined;
89
+ if (typeof entry.text !== "string" && typeof entry.blob !== "string") return undefined;
90
+ const resource: McpUiResourcePayload = {
91
+ uri: entry.uri,
92
+ ...(typeof entry.mimeType === "string" ? { mimeType: entry.mimeType } : {}),
93
+ ...(typeof entry.text === "string" ? { text: entry.text } : {}),
94
+ ...(typeof entry.blob === "string" ? { blob: entry.blob } : {}),
95
+ };
96
+ return { channel: uiResourceChannel(resourceUri), resource };
97
+ };
98
+ }