@botiverse/porthole 0.0.1 → 0.1.0
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/LICENSE +217 -0
- package/dist/bridge/bridge.d.ts +39 -0
- package/dist/bridge/bridge.js +184 -0
- package/dist/bridge/types.d.ts +67 -0
- package/dist/bridge/types.js +1 -0
- package/dist/compile/compile.d.ts +35 -0
- package/dist/compile/compile.js +30 -0
- package/dist/compile/policy.d.ts +55 -0
- package/dist/compile/policy.js +106 -0
- package/dist/config/capabilities.d.ts +154 -0
- package/dist/config/capabilities.js +63 -0
- package/dist/config/schema.d.ts +131 -0
- package/dist/config/schema.js +1 -0
- package/dist/config/validate.d.ts +20 -0
- package/dist/config/validate.js +230 -0
- package/dist/conformance/behavior.d.ts +2 -0
- package/dist/conformance/behavior.js +376 -0
- package/dist/conformance/index.d.ts +95 -0
- package/dist/conformance/index.js +4 -0
- package/dist/conformance/kit.d.ts +42 -0
- package/dist/conformance/kit.js +24 -0
- package/dist/conformance/oracles.d.ts +35 -0
- package/dist/conformance/oracles.js +77 -0
- package/dist/conformance/probes.d.ts +9 -0
- package/dist/conformance/probes.js +865 -0
- package/dist/conformance/served.d.ts +68 -0
- package/dist/conformance/served.js +311 -0
- package/dist/guide/examples.d.ts +70 -0
- package/dist/guide/examples.js +69 -0
- package/dist/guide/guide.d.ts +16 -0
- package/dist/guide/guide.js +261 -0
- package/dist/host/create-host.d.ts +8 -0
- package/dist/host/create-host.js +248 -0
- package/dist/host/locate.d.ts +8 -0
- package/dist/host/locate.js +11 -0
- package/dist/host/types.d.ts +95 -0
- package/dist/host/types.js +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +19 -0
- package/dist/isolation/served.d.ts +32 -0
- package/dist/isolation/served.js +97 -0
- package/dist/isolation/types.d.ts +87 -0
- package/dist/isolation/types.js +1 -0
- package/dist/mcp/adapter.d.ts +43 -0
- package/dist/mcp/adapter.js +327 -0
- package/dist/mcp/index.d.ts +27 -0
- package/dist/mcp/index.js +25 -0
- package/dist/mcp/proxy.d.ts +25 -0
- package/dist/mcp/proxy.js +106 -0
- package/dist/mcp/view.d.ts +31 -0
- package/dist/mcp/view.js +72 -0
- package/dist/platform/types.d.ts +44 -0
- package/dist/platform/types.js +1 -0
- package/dist/platform/web.d.ts +8 -0
- package/dist/platform/web.js +72 -0
- package/dist/presets/presets.d.ts +65 -0
- package/dist/presets/presets.js +125 -0
- package/dist/protocol/adapter.d.ts +73 -0
- package/dist/protocol/adapter.js +1 -0
- package/dist/protocol/jsonrpc.d.ts +43 -0
- package/dist/protocol/jsonrpc.js +50 -0
- package/dist/protocol/mcp-apps.d.ts +79 -0
- package/dist/protocol/mcp-apps.js +99 -0
- package/dist/protocol/shell-adapter.d.ts +5 -0
- package/dist/protocol/shell-adapter.js +228 -0
- package/dist/react/dialog.d.ts +20 -0
- package/dist/react/dialog.js +98 -0
- package/dist/react/frame.d.ts +75 -0
- package/dist/react/frame.js +221 -0
- package/dist/react/index.d.ts +13 -0
- package/dist/react/index.js +13 -0
- package/dist/react/live.d.ts +24 -0
- package/dist/react/live.js +57 -0
- package/dist/server/index.d.ts +27 -0
- package/dist/server/index.js +26 -0
- package/dist/server/keys.d.ts +19 -0
- package/dist/server/keys.js +78 -0
- package/dist/server/pages.d.ts +6 -0
- package/dist/server/pages.js +20 -0
- package/dist/server/proxy.d.ts +21 -0
- package/dist/server/proxy.js +107 -0
- package/dist/server/server.d.ts +7 -0
- package/dist/server/server.js +518 -0
- package/dist/server/tickets.d.ts +8 -0
- package/dist/server/tickets.js +28 -0
- package/dist/server/types.d.ts +97 -0
- package/dist/server/types.js +1 -0
- package/dist/shell/document.d.ts +28 -0
- package/dist/shell/document.js +84 -0
- package/dist/shell/plan.d.ts +35 -0
- package/dist/shell/plan.js +34 -0
- package/dist/shell/runtime.d.ts +28 -0
- package/dist/shell/runtime.js +832 -0
- package/dist/theme/variables.d.ts +73 -0
- package/dist/theme/variables.js +171 -0
- package/dist/transport/post-message.d.ts +9 -0
- package/dist/transport/post-message.js +38 -0
- package/dist/transport/types.d.ts +15 -0
- package/dist/transport/types.js +1 -0
- package/package.json +71 -1
- package/src/bridge/bridge.ts +256 -0
- package/src/bridge/types.ts +77 -0
- package/src/compile/compile.ts +68 -0
- package/src/compile/policy.ts +133 -0
- package/src/config/capabilities.ts +138 -0
- package/src/config/schema.ts +140 -0
- package/src/config/validate.ts +284 -0
- package/src/conformance/behavior.ts +430 -0
- package/src/conformance/index.ts +102 -0
- package/src/conformance/kit.ts +63 -0
- package/src/conformance/oracles.ts +126 -0
- package/src/conformance/probes.ts +1093 -0
- package/src/conformance/served.ts +472 -0
- package/src/guide/examples.ts +74 -0
- package/src/guide/guide.ts +424 -0
- package/src/host/create-host.ts +290 -0
- package/src/host/locate.ts +20 -0
- package/src/host/types.ts +95 -0
- package/src/index.ts +118 -0
- package/src/isolation/served.ts +127 -0
- package/src/isolation/types.ts +77 -0
- package/src/mcp/adapter.ts +441 -0
- package/src/mcp/index.ts +50 -0
- package/src/mcp/proxy.ts +117 -0
- package/src/mcp/view.ts +97 -0
- package/src/platform/types.ts +48 -0
- package/src/platform/web.ts +78 -0
- package/src/presets/presets.ts +193 -0
- package/src/protocol/adapter.ts +67 -0
- package/src/protocol/jsonrpc.ts +83 -0
- package/src/protocol/mcp-apps.ts +138 -0
- package/src/protocol/shell-adapter.ts +261 -0
- package/src/react/dialog.tsx +158 -0
- package/src/react/frame.tsx +422 -0
- package/src/react/index.ts +20 -0
- package/src/react/live.ts +76 -0
- package/src/server/index.ts +37 -0
- package/src/server/keys.ts +100 -0
- package/src/server/pages.ts +31 -0
- package/src/server/proxy.ts +131 -0
- package/src/server/server.ts +617 -0
- package/src/server/tickets.ts +30 -0
- package/src/server/types.ts +104 -0
- package/src/shell/document.ts +120 -0
- package/src/shell/plan.ts +66 -0
- package/src/shell/runtime.ts +832 -0
- package/src/theme/variables.ts +190 -0
- package/src/transport/post-message.ts +45 -0
- package/src/transport/types.ts +16 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { PortholeConfig } from "../config/schema.ts";
|
|
2
|
+
import type { FrameLocation, InstanceRef } from "../isolation/types.ts";
|
|
3
|
+
export interface ContentDomain {
|
|
4
|
+
/**
|
|
5
|
+
* The registrable domain content is served under, used for nothing else
|
|
6
|
+
* (`example-usercontent.com`). Dev and tests: `localhost`, so labels are `<label>.localhost`,
|
|
7
|
+
* which browsers resolve to loopback.
|
|
8
|
+
*/
|
|
9
|
+
readonly domain: string;
|
|
10
|
+
/** `http` only for `localhost`. */
|
|
11
|
+
readonly scheme: "https" | "http";
|
|
12
|
+
readonly port?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Who may open an instance, decided by the host before it calls `issue`. */
|
|
15
|
+
export interface Grant {
|
|
16
|
+
/** The host's id for the viewer: recorded with the ticket, never shown to content. */
|
|
17
|
+
readonly viewer: string;
|
|
18
|
+
/** How long the ticket is good for, seconds (default 60). */
|
|
19
|
+
readonly ttlSeconds?: number;
|
|
20
|
+
/**
|
|
21
|
+
* How long the cookie the ticket buys stays good, seconds (default 3600): what a reload, and a
|
|
22
|
+
* url-mode app's every request, rides on.
|
|
23
|
+
*/
|
|
24
|
+
readonly sessionSeconds?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface TicketRecord {
|
|
27
|
+
readonly instance: InstanceRef;
|
|
28
|
+
readonly viewer: string;
|
|
29
|
+
/** Epoch milliseconds. */
|
|
30
|
+
readonly expiresAt: number;
|
|
31
|
+
/** The grant's `sessionSeconds`, if it set one. */
|
|
32
|
+
readonly sessionSeconds?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Where tickets live between `issue` and their one use. Default: in memory (one process). */
|
|
35
|
+
export interface TicketStore {
|
|
36
|
+
put(id: string, record: TicketRecord): Promise<void>;
|
|
37
|
+
/** Returns the ticket once and never again (atomically), or null if unknown or expired. */
|
|
38
|
+
take(id: string): Promise<TicketRecord | null>;
|
|
39
|
+
}
|
|
40
|
+
/** Stored pages (artifact mode). Versions are immutable: a new page is a new version. */
|
|
41
|
+
export interface ArtifactStore {
|
|
42
|
+
get(id: string, version: string): Promise<{
|
|
43
|
+
readonly html: string;
|
|
44
|
+
} | null>;
|
|
45
|
+
}
|
|
46
|
+
/** What a request's cookie lets in: one instance, for one viewer, on one origin, until a time. */
|
|
47
|
+
export interface Access {
|
|
48
|
+
readonly instance: InstanceRef;
|
|
49
|
+
readonly viewer: string;
|
|
50
|
+
/** The content origin the access is for. */
|
|
51
|
+
readonly origin: string;
|
|
52
|
+
/** When the cookie stops being good, epoch milliseconds. */
|
|
53
|
+
readonly expiresAt: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Answers a url-mode app's request once `/server` has checked its access: the host's way to the
|
|
57
|
+
* running app (a proxy to a dev server, say; `proxyTo` is one). The request no longer carries
|
|
58
|
+
* Porthole's cookie; the answer gets `frame-ancestors` added, and loses any cookie named as ours.
|
|
59
|
+
*/
|
|
60
|
+
export type Upstream = (request: Request, access: Access) => Response | Promise<Response>;
|
|
61
|
+
export interface ServerOptions {
|
|
62
|
+
readonly content: ContentDomain;
|
|
63
|
+
/** At least 32 random bytes: keys the labels and signs tickets and cookies. */
|
|
64
|
+
readonly secret: Uint8Array;
|
|
65
|
+
/** The host origins that may embed content (`frame-ancestors`) and talk to a served shell. */
|
|
66
|
+
readonly hostOrigins: readonly string[];
|
|
67
|
+
/**
|
|
68
|
+
* The configs a served shell or page is served under, by the name a `ServedDocument` gives
|
|
69
|
+
* (an artifact defaults to `artifact`, the MCP Apps sandbox proxy to `mcpApp`).
|
|
70
|
+
*/
|
|
71
|
+
readonly configs: Readonly<Record<string, PortholeConfig>>;
|
|
72
|
+
readonly artifacts?: ArtifactStore;
|
|
73
|
+
readonly tickets?: TicketStore;
|
|
74
|
+
/** Url mode: where an external instance's requests go once let in. Absent: nothing serves them. */
|
|
75
|
+
readonly upstream?: Upstream;
|
|
76
|
+
}
|
|
77
|
+
export interface PortholeServer {
|
|
78
|
+
/**
|
|
79
|
+
* Answers a request for any host under the content domain; null for any other request, which
|
|
80
|
+
* the host's own routes then handle. Web-standard `Request`/`Response`.
|
|
81
|
+
*/
|
|
82
|
+
handle(request: Request): Promise<Response | null>;
|
|
83
|
+
/** The origin a storage scope maps to. */
|
|
84
|
+
originFor(scope: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Mints a one-time ticket for `instance` and returns the URL its frame loads. Call it from the
|
|
87
|
+
* host's own authenticated API, once the host has decided the viewer may see the instance.
|
|
88
|
+
*/
|
|
89
|
+
issue(instance: InstanceRef, grant: Grant): Promise<FrameLocation>;
|
|
90
|
+
/**
|
|
91
|
+
* The access a request's cookie carries for a url-mode app on the request's origin, or null:
|
|
92
|
+
* for a host's own WebSocket upgrade path, which a fetch-style handler can't take.
|
|
93
|
+
*/
|
|
94
|
+
access(request: Request): Promise<Access | null>;
|
|
95
|
+
/** The frame source every content origin matches (`https://*.example-usercontent.com`). */
|
|
96
|
+
readonly contentSource: string;
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ShellPlan } from "./plan.ts";
|
|
2
|
+
export interface ShellDocumentOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The origins the shell takes messages from: the embedding page's own (`location.origin`), or
|
|
5
|
+
* every host origin a served document may be embedded by. Exact origins, no wildcards. The
|
|
6
|
+
* sender must also be the frame's parent.
|
|
7
|
+
*/
|
|
8
|
+
readonly origins: readonly string[];
|
|
9
|
+
/** The frame document's title (for assistive technology inside the frame). */
|
|
10
|
+
readonly title?: string;
|
|
11
|
+
/**
|
|
12
|
+
* A whole document to show in place of the shell's own empty one (an MCP Apps view): the shell
|
|
13
|
+
* parses it inert, guards it, imports it, and runs its scripts in order, once its own policy
|
|
14
|
+
* and runtime are in place. It is never handed to the page's parser, which could build what the
|
|
15
|
+
* shell's guards can't see.
|
|
16
|
+
*/
|
|
17
|
+
readonly document?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the CSP goes in a `<meta>` (default). A served document sends it as a header too;
|
|
20
|
+
* both apply, and the stricter wins.
|
|
21
|
+
*/
|
|
22
|
+
readonly cspMeta?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The frame's document: its policy first, then the base styles, an empty root, and the runtime.
|
|
26
|
+
* A given `document` travels in the runtime's plan, as data.
|
|
27
|
+
*/
|
|
28
|
+
export declare function shellDocument(plan: ShellPlan, csp: string, options: ShellDocumentOptions): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SHELL_RUNTIME } from "./runtime.js";
|
|
2
|
+
const EXACT_ORIGIN = /^(https?:\/\/[a-z0-9.-]+(:\d{1,5})?|https?:\/\/\[::1\](:\d{1,5})?)$/u;
|
|
3
|
+
const escapeAttribute = (value) => value.replace(/&/gu, "&").replace(/"/gu, """).replace(/</gu, "<");
|
|
4
|
+
const escapeText = (value) => value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">");
|
|
5
|
+
/** JSON that is safe inside a `<script>`: no `</script>`, no `<!--`. */
|
|
6
|
+
const scriptJson = (value) => JSON.stringify(value)
|
|
7
|
+
.replace(/</gu, "\\u003c")
|
|
8
|
+
.replace(/\u2028/gu, "\\u2028")
|
|
9
|
+
.replace(/\u2029/gu, "\\u2029");
|
|
10
|
+
/** The base styles: the theme's type and color on the body, and nothing drawn by the shell. */
|
|
11
|
+
const BASE_STYLE = `:root { color-scheme: light dark; }
|
|
12
|
+
html, body { margin: 0; padding: 0; background: transparent; }
|
|
13
|
+
body {
|
|
14
|
+
font-family: var(--font-sans, system-ui, sans-serif);
|
|
15
|
+
font-size: var(--font-text-md-size, 14px);
|
|
16
|
+
line-height: var(--font-text-md-line-height, 20px);
|
|
17
|
+
color: var(--color-text-primary, CanvasText);
|
|
18
|
+
overflow-wrap: anywhere;
|
|
19
|
+
-webkit-text-size-adjust: 100%;
|
|
20
|
+
}
|
|
21
|
+
#porthole-root { display: flow-root; }
|
|
22
|
+
#porthole-root > svg { display: block; max-width: 100%; height: auto; }
|
|
23
|
+
:where(button, input, select, textarea) { font: inherit; color: inherit; box-sizing: border-box; }
|
|
24
|
+
:where(button, select, input:is([type="button" i], [type="submit" i], [type="reset" i])) {
|
|
25
|
+
min-height: 44px;
|
|
26
|
+
padding: 0 14px;
|
|
27
|
+
border: var(--border-width-regular, 1px) solid var(--color-border-primary, ButtonBorder);
|
|
28
|
+
border-radius: var(--border-radius-md, 8px);
|
|
29
|
+
background: var(--color-background-secondary, ButtonFace);
|
|
30
|
+
color: var(--color-text-primary, ButtonText);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
:where(input:not([type]), input:is([type="text" i], [type="search" i], [type="number" i], [type="email" i], [type="url" i], [type="tel" i], [type="password" i], [type="date" i], [type="time" i]), textarea) {
|
|
34
|
+
min-height: 44px;
|
|
35
|
+
padding: 0 10px;
|
|
36
|
+
border: var(--border-width-regular, 1px) solid var(--color-border-primary, FieldText);
|
|
37
|
+
border-radius: var(--border-radius-md, 8px);
|
|
38
|
+
background: var(--color-background-secondary, Field);
|
|
39
|
+
}
|
|
40
|
+
:where(textarea) { padding: 8px 10px; }
|
|
41
|
+
:where(input:is([type="checkbox" i], [type="radio" i], [type="range" i])) { accent-color: var(--color-accent, auto); }
|
|
42
|
+
:where(button, input, select, textarea):focus-visible { outline: 2px solid var(--color-ring-primary, Highlight); outline-offset: 1px; }
|
|
43
|
+
:where(button, input, select, textarea):disabled { color: var(--color-text-disabled, GrayText); cursor: default; }`;
|
|
44
|
+
/**
|
|
45
|
+
* The frame's document: its policy first, then the base styles, an empty root, and the runtime.
|
|
46
|
+
* A given `document` travels in the runtime's plan, as data.
|
|
47
|
+
*/
|
|
48
|
+
export function shellDocument(plan, csp, options) {
|
|
49
|
+
for (const origin of options.origins) {
|
|
50
|
+
if (!EXACT_ORIGIN.test(origin)) {
|
|
51
|
+
throw new TypeError(`shellDocument: ${JSON.stringify(origin)} is not an exact origin`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const runtime = SHELL_RUNTIME.replace("__PLAN__", () => scriptJson({
|
|
55
|
+
...plan,
|
|
56
|
+
origins: options.origins,
|
|
57
|
+
...(options.document === undefined ? {} : { document: options.document }),
|
|
58
|
+
}));
|
|
59
|
+
const head = [
|
|
60
|
+
'<meta charset="utf-8">',
|
|
61
|
+
options.cspMeta === false
|
|
62
|
+
? null
|
|
63
|
+
: `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(csp)}">`,
|
|
64
|
+
'<meta name="referrer" content="no-referrer">',
|
|
65
|
+
// Off for good: an engine may prefetch names it sees in links, outside the CSP.
|
|
66
|
+
'<meta http-equiv="x-dns-prefetch-control" content="off">',
|
|
67
|
+
].filter((line) => line !== null);
|
|
68
|
+
return `<!doctype html>
|
|
69
|
+
<html lang="en">
|
|
70
|
+
<head>
|
|
71
|
+
${head.join("\n")}
|
|
72
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
73
|
+
<title>${escapeText(options.title ?? "Content")}</title>
|
|
74
|
+
<style>
|
|
75
|
+
${BASE_STYLE}
|
|
76
|
+
</style>
|
|
77
|
+
</head>
|
|
78
|
+
<body>
|
|
79
|
+
<div id="porthole-root"></div>
|
|
80
|
+
<script>${runtime}</script>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CapabilityName } from "../config/capabilities.ts";
|
|
2
|
+
import type { PortholeConfig } from "../config/schema.ts";
|
|
3
|
+
/**
|
|
4
|
+
* What the shell wires, derived from the config: the JSON the shell's runtime is given. The
|
|
5
|
+
* agreement test reads it against the CSP and the manual, so it lists everything the shell
|
|
6
|
+
* offers content and nothing else.
|
|
7
|
+
*/
|
|
8
|
+
export interface ShellPlan {
|
|
9
|
+
/**
|
|
10
|
+
* `shell`: the shell speaks MCP Apps with its host and draws the content it is sent (inline
|
|
11
|
+
* widgets, artifact pages). `none`: the content is a view that speaks for itself (an MCP Apps
|
|
12
|
+
* view); the shell only hardens the frame before it runs.
|
|
13
|
+
*/
|
|
14
|
+
readonly protocol: "shell" | "none";
|
|
15
|
+
/** What the host sends: a fragment (or SVG) the shell inserts, or a whole document it writes. */
|
|
16
|
+
readonly content: "fragment" | "document";
|
|
17
|
+
/** Each declared capability, and whether the shell refuses a call made without a gesture. */
|
|
18
|
+
readonly capabilities: {
|
|
19
|
+
readonly [N in CapabilityName]?: {
|
|
20
|
+
readonly userActivation: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/** The globals the shell defines on `window` (sugar over the protocol's requests). */
|
|
24
|
+
readonly globals: readonly string[];
|
|
25
|
+
/** The theme variables the shell applies; it drops any other name the host sends. */
|
|
26
|
+
readonly themeVariables: readonly string[];
|
|
27
|
+
/** The window event fired after a theme change. */
|
|
28
|
+
readonly themeEvent: string;
|
|
29
|
+
/** `memory`: an opaque origin has no storage, so a map stands in for the frame's life. */
|
|
30
|
+
readonly storage: "memory" | "native";
|
|
31
|
+
readonly maxErrorReports: number;
|
|
32
|
+
readonly maxPromptChars: number;
|
|
33
|
+
readonly protocolVersion: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function shellPlan(config: PortholeConfig): ShellPlan | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { capabilitySpec } from "../config/capabilities.js";
|
|
2
|
+
import { declaredCapabilities, effectiveGates } from "../compile/policy.js";
|
|
3
|
+
import { MCP_APPS_PROTOCOL_VERSION } from "../protocol/mcp-apps.js";
|
|
4
|
+
export function shellPlan(config) {
|
|
5
|
+
if (config.mode === "url") {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const protocol = config.mode === "mcp-app" ? "none" : "shell";
|
|
9
|
+
const declared = declaredCapabilities(config);
|
|
10
|
+
const capabilities = {};
|
|
11
|
+
for (const name of declared) {
|
|
12
|
+
capabilities[name] = {
|
|
13
|
+
userActivation: effectiveGates(config, name).includes("user-activation"),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const globals = protocol === "shell"
|
|
17
|
+
? declared.flatMap((name) => {
|
|
18
|
+
const spec = capabilitySpec(name);
|
|
19
|
+
return spec.global === undefined ? [] : [spec.global];
|
|
20
|
+
})
|
|
21
|
+
: [];
|
|
22
|
+
return {
|
|
23
|
+
protocol,
|
|
24
|
+
content: config.mode === "artifact" ? "document" : "fragment",
|
|
25
|
+
capabilities,
|
|
26
|
+
globals,
|
|
27
|
+
themeVariables: Object.keys(config.theme.variables),
|
|
28
|
+
themeEvent: config.theme.event,
|
|
29
|
+
storage: config.isolation === "opaque" ? "memory" : "native",
|
|
30
|
+
maxErrorReports: config.limits.maxErrorReports,
|
|
31
|
+
maxPromptChars: config.limits.maxPromptChars,
|
|
32
|
+
protocolVersion: MCP_APPS_PROTOCOL_VERSION,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's runtime: the code that runs first in the frame, before any content. Plain ES2020,
|
|
3
|
+
* no imports and no build step, so every engine (a browser frame now, a native web view later)
|
|
4
|
+
* runs it as it is. `__PLAN__` is replaced with the frame's plan (`ShellPlan` plus the origins it
|
|
5
|
+
* takes messages from, and a document to render, if any).
|
|
6
|
+
*
|
|
7
|
+
* Before any content runs, it:
|
|
8
|
+
* 1. stands in for storage an opaque origin lacks (a map for the frame's life);
|
|
9
|
+
* 2. removes every WebRTC constructor, non-configurably: a peer connection reaches the network
|
|
10
|
+
* outside connect-src (ICE gathering), and not every engine honors the CSP's `webrtc 'block'`;
|
|
11
|
+
* 3. removes every frame-like element content adds, wherever it adds it (the document, any shadow
|
|
12
|
+
* root), since a nested frame runs in a fresh realm where step 2 never happened (and not every
|
|
13
|
+
* engine enforces `frame-src 'none'` for `srcdoc`/`about:blank` children); and the resource
|
|
14
|
+
* hints and refreshes a CSP doesn't govern;
|
|
15
|
+
* 4. closes the parser entry points that could build a shadow root step 3 can't see (declarative
|
|
16
|
+
* shadow DOM through `setHTMLUnsafe`, `parseHTMLUnsafe`, `document.write`).
|
|
17
|
+
* A whole document (an artifact page, an MCP Apps view) is never written in: it is parsed inert,
|
|
18
|
+
* imported, and its scripts run in order, as a fragment's are.
|
|
19
|
+
*
|
|
20
|
+
* Then, when it speaks for the content (`protocol: "shell"`), it asks its host `ui/initialize`,
|
|
21
|
+
* applies the theme the host answers, draws the content it is sent (inert while partial, run once
|
|
22
|
+
* when whole), reports its size and errors, sends link clicks out through `openLink`, and defines
|
|
23
|
+
* a global for each declared capability that has one.
|
|
24
|
+
*
|
|
25
|
+
* Nothing secret is ever in the frame: everything it says, the host treats as the content's own
|
|
26
|
+
* words. It takes messages only from its parent, and only from the plan's origins.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SHELL_RUNTIME: string;
|