Skip to content

Commit c0ba6f5

Browse files
committed
refactor: extract buildAllowAttribute to shared library
- Add buildAllowAttribute() to src/app-bridge.ts with JSDoc - Import and use from implementation.ts and sandbox.ts - Remove duplicated local implementations
1 parent 59e01f4 commit c0ba6f5

3 files changed

Lines changed: 33 additions & 32 deletions

File tree

examples/basic-host/src/implementation.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps/app-bridge";
1+
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute } from "@modelcontextprotocol/ext-apps/app-bridge";
22
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
33
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
44
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
@@ -119,19 +119,6 @@ async function getUiResource(serverInfo: ServerInfo, uri: string): Promise<UiRes
119119
}
120120

121121

122-
// Build iframe allow attribute from permissions
123-
function buildAllowAttribute(permissions?: McpUiResourcePermissions): string {
124-
if (!permissions) return "";
125-
126-
const allowList: string[] = [];
127-
if (permissions.camera) allowList.push("camera");
128-
if (permissions.microphone) allowList.push("microphone");
129-
if (permissions.geolocation) allowList.push("geolocation");
130-
if (permissions.clipboardWrite) allowList.push("clipboard-write");
131-
132-
return allowList.join("; ");
133-
}
134-
135122
export function loadSandboxProxy(
136123
iframe: HTMLIFrameElement,
137124
csp?: McpUiResourceCsp,

examples/basic-host/src/sandbox.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { McpUiSandboxProxyReadyNotification, McpUiSandboxResourceReadyNotification } from "../../../dist/src/types";
2+
import { buildAllowAttribute } from "../../../dist/src/app-bridge";
23

34
const ALLOWED_REFERRER_PATTERN = /^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/;
45

@@ -68,24 +69,6 @@ const PROXY_READY_NOTIFICATION: McpUiSandboxProxyReadyNotification["method"] =
6869
// Security: CSP is enforced via HTTP headers on sandbox.html (set by serve.ts
6970
// based on ?csp= query param). This is tamper-proof unlike meta tags.
7071

71-
// Build iframe allow attribute from permissions
72-
function buildAllowAttribute(permissions?: {
73-
camera?: boolean;
74-
microphone?: boolean;
75-
geolocation?: boolean;
76-
clipboardWrite?: boolean;
77-
}): string {
78-
if (!permissions) return "";
79-
80-
const allowList: string[] = [];
81-
if (permissions.camera) allowList.push("camera");
82-
if (permissions.microphone) allowList.push("microphone");
83-
if (permissions.geolocation) allowList.push("geolocation");
84-
if (permissions.clipboardWrite) allowList.push("clipboard-write");
85-
86-
return allowList.join("; ");
87-
}
88-
8972
window.addEventListener("message", async (event) => {
9073
if (event.source === window.parent) {
9174
// Validate that messages from parent come from the expected host origin.

src/app-bridge.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
McpUiRequestDisplayModeRequest,
7777
McpUiRequestDisplayModeRequestSchema,
7878
McpUiRequestDisplayModeResult,
79+
McpUiResourcePermissions,
7980
} from "./types";
8081
export * from "./types";
8182
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
@@ -126,6 +127,36 @@ export function getToolUiResourceUri(tool: {
126127
return undefined;
127128
}
128129

130+
/**
131+
* Build iframe `allow` attribute string from permissions.
132+
*
133+
* Maps McpUiResourcePermissions to the Permission Policy allow attribute
134+
* format used by iframes (e.g., "microphone; clipboard-write").
135+
*
136+
* @param permissions - Permissions requested by the UI resource
137+
* @returns Space-separated permission directives, or empty string if none
138+
*
139+
* @example
140+
* ```typescript
141+
* const allow = buildAllowAttribute({ microphone: {}, clipboardWrite: {} });
142+
* // Returns: "microphone; clipboard-write"
143+
* iframe.setAttribute("allow", allow);
144+
* ```
145+
*/
146+
export function buildAllowAttribute(
147+
permissions: McpUiResourcePermissions | undefined,
148+
): string {
149+
if (!permissions) return "";
150+
151+
const allowList: string[] = [];
152+
if (permissions.camera) allowList.push("camera");
153+
if (permissions.microphone) allowList.push("microphone");
154+
if (permissions.geolocation) allowList.push("geolocation");
155+
if (permissions.clipboardWrite) allowList.push("clipboard-write");
156+
157+
return allowList.join("; ");
158+
}
159+
129160
/**
130161
* Options for configuring AppBridge behavior.
131162
*

0 commit comments

Comments
 (0)