@maravilla-labs/platform 0.10.1 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @maravilla-labs/platform
2
2
 
3
+ ## 0.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d9b06d4: MCP UI widgets are now referenced as **static assets** instead of copied to the
8
+ build root. `buildMcp` records `htmlPath` (e.g. `static/mcp-ui/reader.html`) as
9
+ the deploy-relative `htmlAsset` verbatim rather than copying the file to
10
+ `mcp-ui/<name>.html` — a build-root copy was never listed in `routing.assets` nor
11
+ registered as a bundle, so the deploy pipeline never shipped it and the runtime
12
+ couldn't resolve it. Output your single-file widget into your app's `static/`
13
+ directory (built before the app build) so the framework deploys it normally.
14
+
15
+ ## 0.11.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 49afe1b: MCP UI mini-apps: inline single-file widgets (the MCP Apps contract).
20
+
21
+ A `uiTemplate` now declares `htmlPath` (a self-contained single-file widget, e.g.
22
+ built with `vite-plugin-singlefile`) instead of `route`. The build copies it into
23
+ the deploy as `mcp-ui/<name>.html`; the runtime serves its bytes inline as the
24
+ `ui://<tool>/<template>` resource (`text/html;profile=mcp-app`), which Claude /
25
+ ChatGPT render directly. CSP for the resource is declared as a structured
26
+ `McpUiResourceCsp` (`resourceDomains` / `connectDomains` / `frameDomains` /
27
+ `baseUriDomains`) emitted at `_meta.ui.csp`. The widget receives its data over the
28
+ MCP Apps channel (`@modelcontextprotocol/ext-apps`, `ontoolresult` →
29
+ `structuredContent`). Replaces the previous externalUrl/loopback approach, which
30
+ hosts rejected as an "unsupported UI resource content format".
31
+
3
32
  ## 0.10.1
4
33
 
5
34
  ### Patch Changes
package/dist/mcp.d.ts CHANGED
@@ -150,17 +150,44 @@ interface McpServerSpec {
150
150
  public?: boolean;
151
151
  /**
152
152
  * Mini-app UI templates referenced by tools via `ui.template`. Each maps a
153
- * template `name` to a `route` in your app that renders the widget. The runtime
154
- * hands the host that route as an `externalUrl` (`text/uri-list`) resource, so
155
- * the host frames it directly one iframe, no nesting. `csp` overrides the
156
- * resource's framing policy.
153
+ * template `name` to `htmlPath` a **self-contained single-file HTML widget**
154
+ * (JS/CSS inlined; build it with e.g. `vite-plugin-singlefile`). Output the
155
+ * build into your app's **static assets** and point `htmlPath` at that
156
+ * deploy-relative path (e.g. `static/mcp-ui/reader.html`), built BEFORE the app
157
+ * build — so the framework ships it as a normal static asset. The runtime reads
158
+ * those bytes and serves them as the `ui://<tool>/<template>` resource
159
+ * (`text/html;profile=mcp-app`), which the host (Claude, ChatGPT) renders inline
160
+ * — the MCP Apps standard. The widget receives its data over the MCP Apps
161
+ * channel (`@modelcontextprotocol/ext-apps`, `ontoolresult` →
162
+ * `structuredContent`), not via cookies.
163
+ *
164
+ * `csp` declares the resource's Content-Security-Policy as
165
+ * {@link McpUiResourceCsp} (emitted at `_meta.ui.csp`): list the external
166
+ * origins the widget may load — e.g. `resourceDomains` for images/fonts the
167
+ * widget references. Omit for a fully self-contained widget.
157
168
  */
158
169
  uiTemplates?: Array<{
159
170
  name: string;
160
- route: string;
161
- csp?: string;
171
+ htmlPath: string;
172
+ csp?: McpUiResourceCsp;
162
173
  }>;
163
174
  }
175
+ /**
176
+ * Content-Security-Policy for a UI template resource, emitted at the resource's
177
+ * `_meta.ui.csp` (the MCP Apps contract). The host derives CSP directives from
178
+ * these origin lists; omitting a list blocks that class of request.
179
+ */
180
+ interface McpUiResourceCsp {
181
+ /** `connect-src` — fetch / XHR / WebSocket origins. */
182
+ connectDomains?: string[];
183
+ /** `img-src` / `script-src` / `style-src` / `font-src` / `media-src` — static
184
+ * resource origins. Supports wildcard subdomains, e.g. `https://*.example.com`. */
185
+ resourceDomains?: string[];
186
+ /** `frame-src` — origins allowed in nested iframes. */
187
+ frameDomains?: string[];
188
+ /** `base-uri` — allowed document base URIs. */
189
+ baseUriDomains?: string[];
190
+ }
164
191
  interface RegisteredMcpServer {
165
192
  readonly [MCP_SERVER_SYMBOL]: McpServerSpec;
166
193
  }
@@ -180,4 +207,4 @@ declare function isRegisteredMcpTool(value: unknown): value is RegisteredMcpTool
180
207
  /** Type guard for a registered MCP server descriptor. */
181
208
  declare function isRegisteredMcpServer(value: unknown): value is RegisteredMcpServer;
182
209
 
183
- export { MCP_SERVER_SYMBOL, MCP_TOOL_SYMBOL, type McpContentItem, type McpServerSpec, type McpToolContext, type McpToolResult, type McpToolSpec, type RegisteredMcpServer, type RegisteredMcpTool, defineMcpServer, defineMcpTool, isRegisteredMcpServer, isRegisteredMcpTool };
210
+ export { MCP_SERVER_SYMBOL, MCP_TOOL_SYMBOL, type McpContentItem, type McpServerSpec, type McpToolContext, type McpToolResult, type McpToolSpec, type McpUiResourceCsp, type RegisteredMcpServer, type RegisteredMcpTool, defineMcpServer, defineMcpTool, isRegisteredMcpServer, isRegisteredMcpTool };
package/dist/mcp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["/**\n * @fileoverview MCP tool authoring helpers for Maravilla.\n *\n * User apps declare MCP tools in `mcp.ts` or `mcp/*.ts`:\n *\n * ```ts\n * import { defineMcpTool, defineMcpServer } from '@maravilla-labs/platform/mcp';\n *\n * export const server = defineMcpServer({\n * name: 'Acme Tools',\n * version: '1.0.0',\n * instructions: 'Tools for managing Acme orders.',\n * uiTemplates: [{ name: 'order-card', route: '/_mcp/ui/order-card' }],\n * });\n *\n * export const getOrder = defineMcpTool(\n * {\n * name: 'get_order',\n * description: 'Look up an order by id.',\n * inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },\n * scopes: ['acme:read'],\n * ui: { template: 'order-card' },\n * },\n * async (args, ctx) => {\n * const order = await ctx.database.findOne('orders', { _id: args.id });\n * return { content: [{ type: 'text', text: JSON.stringify(order) }] };\n * },\n * );\n * ```\n *\n * These helpers are pure factories. `defineMcpTool` produces a\n * `RegisteredMcpTool` marker object that the build pipeline\n * (`@maravilla-labs/functions` `buildMcp`) detects by its `__maravilla_mcp`\n * property; `defineMcpServer` produces a `RegisteredMcpServer` detected by\n * `__maravilla_mcp_server`. The generated bundle exposes\n * `globalThis.handleMcpTool(toolId, args, ctx)`; the Rust MCP dispatcher\n * (`crates/platform/src/mcp/dispatch.rs`) drives it via a synthetic request\n * whose body is `{ tool_id, args, identity }`.\n */\n\n// ============ Tool handler context ============\n\n/**\n * Context handed to every MCP tool handler. The first seven services mirror\n * the events `EventCtx` (and `globalThis.platform`) exactly. `user`/`client`\n * carry the authenticated identity behind the OAuth token or API key so the\n * handler — and the platform ops it calls — run as the real end-user.\n */\nexport interface McpToolContext {\n /** Per-tenant env vars. */\n env: Record<string, string>;\n /** KV store — same shape as `getPlatform().env.KV` / `platform.kv`. */\n kv?: unknown;\n /** MongoDB-style database — same shape as `getPlatform().env.DB`. */\n database?: unknown;\n /** Object storage. */\n storage?: unknown;\n /** Durable queue producer (`.send(name, payload, opts?)`). */\n queue?: { send: (name: string, payload: unknown, opts?: unknown) => Promise<string> };\n /** Auth service — register/login/logout/user CRUD/etc. */\n auth?: unknown;\n /** Web Push service. */\n push?: unknown;\n /** Full platform object — escape hatch for services not surfaced above. */\n platform?: unknown;\n /** Tenant identifier. */\n tenant: string;\n /** Trace correlation id — propagate through logs. */\n traceId: string;\n /** The end-user behind the token/key, or `null` for a client-only call. */\n user: { id: string; email: string; groups: string[]; scopes: string[] } | null;\n /** The OAuth client (agent) that authenticated, when known. */\n client: { id: string } | null;\n}\n\n// ============ Tool result shapes ============\n\n/** A single content item returned by a tool. */\nexport type McpContentItem =\n | { type: 'text'; text: string }\n | { type: 'json'; json: unknown }\n | { type: 'resource'; resource: Record<string, unknown> };\n\n/**\n * What a tool handler may return:\n * - `{ content }` — explicit MCP content items.\n * - `{ ui }` — render an iframe mini-app template (§7); optional `content`\n * is shown alongside for clients that don't support the UI.\n * - any other value — wrapped as a single `text` content item.\n */\nexport type McpToolResult =\n | { content: McpContentItem[] }\n | { ui: { template: string; data?: unknown }; content?: McpContentItem[] }\n | unknown;\n\n// ============ Registered markers ============\n\nexport const MCP_TOOL_SYMBOL = '__maravilla_mcp' as const;\nexport const MCP_SERVER_SYMBOL = '__maravilla_mcp_server' as const;\n\n/** Spec passed to {@link defineMcpTool}. */\nexport interface McpToolSpec {\n /** Tool name surfaced to the client (defaults to the export name). */\n name: string;\n /** Human-readable description sent in `tools/list`. */\n description: string;\n /** JSON Schema for the tool input, sent verbatim in `tools/list`. */\n inputSchema: Record<string, unknown>;\n /** Required scopes; dispatch enforces `scopes ⊆ identity.scopes`. */\n scopes?: string[];\n /**\n * Per-tool public opt-in. When `true`, this tool is callable with an\n * anonymous identity (no bearer) even on an otherwise-private server — its\n * `scopes` must still be a subset of the caller's, so an anonymous caller can\n * only reach it when it declares no scopes. Defaults to `false`.\n */\n public?: boolean;\n /** Links this tool to a UI template declared on the server (§7). */\n ui?: { template: string };\n}\n\nexport interface RegisteredMcpTool {\n readonly [MCP_TOOL_SYMBOL]: McpToolSpec;\n readonly handler: (args: any, ctx: McpToolContext) => McpToolResult | Promise<McpToolResult>;\n}\n\n/** Spec passed to {@link defineMcpServer}. */\nexport interface McpServerSpec {\n /** Human-readable server name (e.g. \"Acme Tools\"). */\n name: string;\n /** Optional semantic version. */\n version?: string;\n /** Optional natural-language usage instructions for the client/model. */\n instructions?: string;\n /**\n * Server-level public flag. When `true`, unauthenticated MCP requests build\n * an anonymous identity (instead of a 401) so `initialize` / `tools/list` and\n * any no-scope tool work without a bearer. Per-tool `public` opt-in still\n * works on an otherwise-private server. Defaults to `false`.\n */\n public?: boolean;\n /**\n * Mini-app UI templates referenced by tools via `ui.template`. Each maps a\n * template `name` to a `route` in your app that renders the widget. The runtime\n * hands the host that route as an `externalUrl` (`text/uri-list`) resource, so\n * the host frames it directly one iframe, no nesting. `csp` overrides the\n * resource's framing policy.\n */\n uiTemplates?: Array<{ name: string; route: string; csp?: string }>;\n}\n\nexport interface RegisteredMcpServer {\n readonly [MCP_SERVER_SYMBOL]: McpServerSpec;\n}\n\n// ============ Public factory helpers ============\n\n/**\n * Declare an MCP tool. The runtime advertises it in `tools/list` (using\n * `name`, `description`, `inputSchema`) and dispatches `tools/call` to\n * `handler`, enforcing `scopes` against the caller's granted scopes.\n */\nexport function defineMcpTool(\n spec: McpToolSpec,\n handler: (args: any, ctx: McpToolContext) => McpToolResult | Promise<McpToolResult>,\n): RegisteredMcpTool {\n return { [MCP_TOOL_SYMBOL]: spec, handler };\n}\n\n/**\n * Declare the MCP server identity and its iframe mini-app templates.\n * Optional — at most one per app; the last one discovered wins.\n */\nexport function defineMcpServer(spec: McpServerSpec): RegisteredMcpServer {\n return { [MCP_SERVER_SYMBOL]: spec };\n}\n\n/** Type guard used by the build-time discoverer and the runtime registry. */\nexport function isRegisteredMcpTool(value: unknown): value is RegisteredMcpTool {\n return (\n typeof value === 'object' &&\n value !== null &&\n MCP_TOOL_SYMBOL in value &&\n typeof (value as Record<string, unknown>).handler === 'function'\n );\n}\n\n/** Type guard for a registered MCP server descriptor. */\nexport function isRegisteredMcpServer(value: unknown): value is RegisteredMcpServer {\n return (\n typeof value === 'object' &&\n value !== null &&\n MCP_SERVER_SYMBOL in value\n );\n}\n"],"mappings":";AAiGO,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAgE1B,SAAS,cACd,MACA,SACmB;AACnB,SAAO,EAAE,CAAC,eAAe,GAAG,MAAM,QAAQ;AAC5C;AAMO,SAAS,gBAAgB,MAA0C;AACxE,SAAO,EAAE,CAAC,iBAAiB,GAAG,KAAK;AACrC;AAGO,SAAS,oBAAoB,OAA4C;AAC9E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,mBAAmB,SACnB,OAAQ,MAAkC,YAAY;AAE1D;AAGO,SAAS,sBAAsB,OAA8C;AAClF,SACE,OAAO,UAAU,YACjB,UAAU,QACV,qBAAqB;AAEzB;","names":[]}
1
+ {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["/**\n * @fileoverview MCP tool authoring helpers for Maravilla.\n *\n * User apps declare MCP tools in `mcp.ts` or `mcp/*.ts`:\n *\n * ```ts\n * import { defineMcpTool, defineMcpServer } from '@maravilla-labs/platform/mcp';\n *\n * export const server = defineMcpServer({\n * name: 'Acme Tools',\n * version: '1.0.0',\n * instructions: 'Tools for managing Acme orders.',\n * uiTemplates: [{ name: 'order-card', route: '/_mcp/ui/order-card' }],\n * });\n *\n * export const getOrder = defineMcpTool(\n * {\n * name: 'get_order',\n * description: 'Look up an order by id.',\n * inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },\n * scopes: ['acme:read'],\n * ui: { template: 'order-card' },\n * },\n * async (args, ctx) => {\n * const order = await ctx.database.findOne('orders', { _id: args.id });\n * return { content: [{ type: 'text', text: JSON.stringify(order) }] };\n * },\n * );\n * ```\n *\n * These helpers are pure factories. `defineMcpTool` produces a\n * `RegisteredMcpTool` marker object that the build pipeline\n * (`@maravilla-labs/functions` `buildMcp`) detects by its `__maravilla_mcp`\n * property; `defineMcpServer` produces a `RegisteredMcpServer` detected by\n * `__maravilla_mcp_server`. The generated bundle exposes\n * `globalThis.handleMcpTool(toolId, args, ctx)`; the Rust MCP dispatcher\n * (`crates/platform/src/mcp/dispatch.rs`) drives it via a synthetic request\n * whose body is `{ tool_id, args, identity }`.\n */\n\n// ============ Tool handler context ============\n\n/**\n * Context handed to every MCP tool handler. The first seven services mirror\n * the events `EventCtx` (and `globalThis.platform`) exactly. `user`/`client`\n * carry the authenticated identity behind the OAuth token or API key so the\n * handler — and the platform ops it calls — run as the real end-user.\n */\nexport interface McpToolContext {\n /** Per-tenant env vars. */\n env: Record<string, string>;\n /** KV store — same shape as `getPlatform().env.KV` / `platform.kv`. */\n kv?: unknown;\n /** MongoDB-style database — same shape as `getPlatform().env.DB`. */\n database?: unknown;\n /** Object storage. */\n storage?: unknown;\n /** Durable queue producer (`.send(name, payload, opts?)`). */\n queue?: { send: (name: string, payload: unknown, opts?: unknown) => Promise<string> };\n /** Auth service — register/login/logout/user CRUD/etc. */\n auth?: unknown;\n /** Web Push service. */\n push?: unknown;\n /** Full platform object — escape hatch for services not surfaced above. */\n platform?: unknown;\n /** Tenant identifier. */\n tenant: string;\n /** Trace correlation id — propagate through logs. */\n traceId: string;\n /** The end-user behind the token/key, or `null` for a client-only call. */\n user: { id: string; email: string; groups: string[]; scopes: string[] } | null;\n /** The OAuth client (agent) that authenticated, when known. */\n client: { id: string } | null;\n}\n\n// ============ Tool result shapes ============\n\n/** A single content item returned by a tool. */\nexport type McpContentItem =\n | { type: 'text'; text: string }\n | { type: 'json'; json: unknown }\n | { type: 'resource'; resource: Record<string, unknown> };\n\n/**\n * What a tool handler may return:\n * - `{ content }` — explicit MCP content items.\n * - `{ ui }` — render an iframe mini-app template (§7); optional `content`\n * is shown alongside for clients that don't support the UI.\n * - any other value — wrapped as a single `text` content item.\n */\nexport type McpToolResult =\n | { content: McpContentItem[] }\n | { ui: { template: string; data?: unknown }; content?: McpContentItem[] }\n | unknown;\n\n// ============ Registered markers ============\n\nexport const MCP_TOOL_SYMBOL = '__maravilla_mcp' as const;\nexport const MCP_SERVER_SYMBOL = '__maravilla_mcp_server' as const;\n\n/** Spec passed to {@link defineMcpTool}. */\nexport interface McpToolSpec {\n /** Tool name surfaced to the client (defaults to the export name). */\n name: string;\n /** Human-readable description sent in `tools/list`. */\n description: string;\n /** JSON Schema for the tool input, sent verbatim in `tools/list`. */\n inputSchema: Record<string, unknown>;\n /** Required scopes; dispatch enforces `scopes ⊆ identity.scopes`. */\n scopes?: string[];\n /**\n * Per-tool public opt-in. When `true`, this tool is callable with an\n * anonymous identity (no bearer) even on an otherwise-private server — its\n * `scopes` must still be a subset of the caller's, so an anonymous caller can\n * only reach it when it declares no scopes. Defaults to `false`.\n */\n public?: boolean;\n /** Links this tool to a UI template declared on the server (§7). */\n ui?: { template: string };\n}\n\nexport interface RegisteredMcpTool {\n readonly [MCP_TOOL_SYMBOL]: McpToolSpec;\n readonly handler: (args: any, ctx: McpToolContext) => McpToolResult | Promise<McpToolResult>;\n}\n\n/** Spec passed to {@link defineMcpServer}. */\nexport interface McpServerSpec {\n /** Human-readable server name (e.g. \"Acme Tools\"). */\n name: string;\n /** Optional semantic version. */\n version?: string;\n /** Optional natural-language usage instructions for the client/model. */\n instructions?: string;\n /**\n * Server-level public flag. When `true`, unauthenticated MCP requests build\n * an anonymous identity (instead of a 401) so `initialize` / `tools/list` and\n * any no-scope tool work without a bearer. Per-tool `public` opt-in still\n * works on an otherwise-private server. Defaults to `false`.\n */\n public?: boolean;\n /**\n * Mini-app UI templates referenced by tools via `ui.template`. Each maps a\n * template `name` to `htmlPath` — a **self-contained single-file HTML widget**\n * (JS/CSS inlined; build it with e.g. `vite-plugin-singlefile`). Output the\n * build into your app's **static assets** and point `htmlPath` at that\n * deploy-relative path (e.g. `static/mcp-ui/reader.html`), built BEFORE the app\n * build — so the framework ships it as a normal static asset. The runtime reads\n * those bytes and serves them as the `ui://<tool>/<template>` resource\n * (`text/html;profile=mcp-app`), which the host (Claude, ChatGPT) renders inline\n * the MCP Apps standard. The widget receives its data over the MCP Apps\n * channel (`@modelcontextprotocol/ext-apps`, `ontoolresult` →\n * `structuredContent`), not via cookies.\n *\n * `csp` declares the resource's Content-Security-Policy as\n * {@link McpUiResourceCsp} (emitted at `_meta.ui.csp`): list the external\n * origins the widget may load — e.g. `resourceDomains` for images/fonts the\n * widget references. Omit for a fully self-contained widget.\n */\n uiTemplates?: Array<{ name: string; htmlPath: string; csp?: McpUiResourceCsp }>;\n}\n\n/**\n * Content-Security-Policy for a UI template resource, emitted at the resource's\n * `_meta.ui.csp` (the MCP Apps contract). The host derives CSP directives from\n * these origin lists; omitting a list blocks that class of request.\n */\nexport interface McpUiResourceCsp {\n /** `connect-src` — fetch / XHR / WebSocket origins. */\n connectDomains?: string[];\n /** `img-src` / `script-src` / `style-src` / `font-src` / `media-src` — static\n * resource origins. Supports wildcard subdomains, e.g. `https://*.example.com`. */\n resourceDomains?: string[];\n /** `frame-src` — origins allowed in nested iframes. */\n frameDomains?: string[];\n /** `base-uri` — allowed document base URIs. */\n baseUriDomains?: string[];\n}\n\nexport interface RegisteredMcpServer {\n readonly [MCP_SERVER_SYMBOL]: McpServerSpec;\n}\n\n// ============ Public factory helpers ============\n\n/**\n * Declare an MCP tool. The runtime advertises it in `tools/list` (using\n * `name`, `description`, `inputSchema`) and dispatches `tools/call` to\n * `handler`, enforcing `scopes` against the caller's granted scopes.\n */\nexport function defineMcpTool(\n spec: McpToolSpec,\n handler: (args: any, ctx: McpToolContext) => McpToolResult | Promise<McpToolResult>,\n): RegisteredMcpTool {\n return { [MCP_TOOL_SYMBOL]: spec, handler };\n}\n\n/**\n * Declare the MCP server identity and its iframe mini-app templates.\n * Optional — at most one per app; the last one discovered wins.\n */\nexport function defineMcpServer(spec: McpServerSpec): RegisteredMcpServer {\n return { [MCP_SERVER_SYMBOL]: spec };\n}\n\n/** Type guard used by the build-time discoverer and the runtime registry. */\nexport function isRegisteredMcpTool(value: unknown): value is RegisteredMcpTool {\n return (\n typeof value === 'object' &&\n value !== null &&\n MCP_TOOL_SYMBOL in value &&\n typeof (value as Record<string, unknown>).handler === 'function'\n );\n}\n\n/** Type guard for a registered MCP server descriptor. */\nexport function isRegisteredMcpServer(value: unknown): value is RegisteredMcpServer {\n return (\n typeof value === 'object' &&\n value !== null &&\n MCP_SERVER_SYMBOL in value\n );\n}\n"],"mappings":";AAiGO,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AA4F1B,SAAS,cACd,MACA,SACmB;AACnB,SAAO,EAAE,CAAC,eAAe,GAAG,MAAM,QAAQ;AAC5C;AAMO,SAAS,gBAAgB,MAA0C;AACxE,SAAO,EAAE,CAAC,iBAAiB,GAAG,KAAK;AACrC;AAGO,SAAS,oBAAoB,OAA4C;AAC9E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,mBAAmB,SACnB,OAAQ,MAAkC,YAAY;AAE1D;AAGO,SAAS,sBAAsB,OAA8C;AAClF,SACE,OAAO,UAAU,YACjB,UAAU,QACV,qBAAqB;AAEzB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maravilla-labs/platform",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "Universal platform client for Maravilla runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/mcp.ts CHANGED
@@ -141,12 +141,40 @@ export interface McpServerSpec {
141
141
  public?: boolean;
142
142
  /**
143
143
  * Mini-app UI templates referenced by tools via `ui.template`. Each maps a
144
- * template `name` to a `route` in your app that renders the widget. The runtime
145
- * hands the host that route as an `externalUrl` (`text/uri-list`) resource, so
146
- * the host frames it directly one iframe, no nesting. `csp` overrides the
147
- * resource's framing policy.
144
+ * template `name` to `htmlPath` a **self-contained single-file HTML widget**
145
+ * (JS/CSS inlined; build it with e.g. `vite-plugin-singlefile`). Output the
146
+ * build into your app's **static assets** and point `htmlPath` at that
147
+ * deploy-relative path (e.g. `static/mcp-ui/reader.html`), built BEFORE the app
148
+ * build — so the framework ships it as a normal static asset. The runtime reads
149
+ * those bytes and serves them as the `ui://<tool>/<template>` resource
150
+ * (`text/html;profile=mcp-app`), which the host (Claude, ChatGPT) renders inline
151
+ * — the MCP Apps standard. The widget receives its data over the MCP Apps
152
+ * channel (`@modelcontextprotocol/ext-apps`, `ontoolresult` →
153
+ * `structuredContent`), not via cookies.
154
+ *
155
+ * `csp` declares the resource's Content-Security-Policy as
156
+ * {@link McpUiResourceCsp} (emitted at `_meta.ui.csp`): list the external
157
+ * origins the widget may load — e.g. `resourceDomains` for images/fonts the
158
+ * widget references. Omit for a fully self-contained widget.
148
159
  */
149
- uiTemplates?: Array<{ name: string; route: string; csp?: string }>;
160
+ uiTemplates?: Array<{ name: string; htmlPath: string; csp?: McpUiResourceCsp }>;
161
+ }
162
+
163
+ /**
164
+ * Content-Security-Policy for a UI template resource, emitted at the resource's
165
+ * `_meta.ui.csp` (the MCP Apps contract). The host derives CSP directives from
166
+ * these origin lists; omitting a list blocks that class of request.
167
+ */
168
+ export interface McpUiResourceCsp {
169
+ /** `connect-src` — fetch / XHR / WebSocket origins. */
170
+ connectDomains?: string[];
171
+ /** `img-src` / `script-src` / `style-src` / `font-src` / `media-src` — static
172
+ * resource origins. Supports wildcard subdomains, e.g. `https://*.example.com`. */
173
+ resourceDomains?: string[];
174
+ /** `frame-src` — origins allowed in nested iframes. */
175
+ frameDomains?: string[];
176
+ /** `base-uri` — allowed document base URIs. */
177
+ baseUriDomains?: string[];
150
178
  }
151
179
 
152
180
  export interface RegisteredMcpServer {