@mcp-b/mcp-iframe 4.1.0-beta.20260702224704 → 5.0.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
@@ -6,17 +6,20 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)](https://www.typescriptlang.org/)
8
8
 
9
- **@mcp-b/mcp-iframe** provides `<mcp-iframe>`, a Web Component that wraps an iframe and automatically bridges its MCP tools, resources, and prompts to the parent page's `document.modelContext`. Items are namespaced with the element's `id` to avoid collisions.
9
+ **@mcp-b/mcp-iframe** provides `<mcp-iframe>`, a Web Component that wraps an iframe and automatically bridges its MCP tools, resources, and prompts to the parent page's `document.modelContext`. Tool and prompt names include the element's `id`. Resource URIs use an `mcp-iframe:` wrapper that records the element prefix and child URI.
10
10
 
11
11
  ## Installation
12
12
 
13
13
  ```bash
14
- pnpm add @mcp-b/mcp-iframe @modelcontextprotocol/sdk
14
+ pnpm add @mcp-b/mcp-iframe
15
15
  ```
16
16
 
17
- The iframe page must have `@mcp-b/global` (or any `document.modelContext` implementation) installed.
17
+ Both pages must expose `document.modelContext`. The parent needs the MCP-B
18
+ resource and prompt extensions from `@mcp-b/global` when the iframe exposes
19
+ those capabilities. The deprecated `navigator.modelContext` surface is used
20
+ only as a fallback for older runtimes.
18
21
 
19
- ## Usage
22
+ ## Use the default element
20
23
 
21
24
  ```html
22
25
  <mcp-iframe src="./child-app.html" id="my-app"></mcp-iframe>
@@ -32,32 +35,66 @@ The iframe page must have `@mcp-b/global` (or any `document.modelContext` implem
32
35
  </script>
33
36
  ```
34
37
 
35
- Tools registered inside the iframe (e.g. `calculate`) appear on the parent as `my-app_calculate`.
38
+ Importing the package root auto-registers `<mcp-iframe>`. Tools registered inside the iframe
39
+ (for example, `calculate`) appear on the parent as `my-app_calculate`.
40
+
41
+ To register a custom tag without registering the default tag, use the side-effect-free entry point:
42
+
43
+ ```typescript
44
+ import { registerMCPIframeElement } from '@mcp-b/mcp-iframe/element';
45
+
46
+ registerMCPIframeElement('my-mcp-frame');
47
+ ```
36
48
 
37
49
  ## Attributes
38
50
 
39
- | Attribute | Description |
40
- | ------------------ | ------------------------------------------------ |
41
- | `src` | URL of the iframe page |
42
- | `id` | Used as the tool name prefix |
43
- | `target-origin` | Override the postMessage target origin |
44
- | `channel` | Channel ID for transport (default: `mcp-iframe`) |
45
- | `call-timeout` | Timeout in ms for tool calls (default: `30000`) |
46
- | `prefix-separator` | Separator between prefix and name (default: `_`) |
51
+ | Attribute | Description |
52
+ | ------------------ | ---------------------------------------------------------------------------------------------------------- |
53
+ | `src` | URL of the iframe page |
54
+ | `id` | Used as the tool and prompt name prefix |
55
+ | `target-origin` | Override the inferred `postMessage` target origin. Opaque origins require `*` |
56
+ | `channel` | Channel ID for transport (default: `mcp-iframe`) |
57
+ | `call-timeout` | Timeout in ms for tool calls, resource reads, and prompt gets (default: `30000`; invalid values fall back) |
58
+ | `prefix-separator` | Separator between prefix and name (default: `_`; allows letters, numbers, `_`, `.`, and `-`) |
47
59
 
48
60
  Standard iframe attributes (`sandbox`, `allow`, `width`, `height`, etc.) are also mirrored.
49
61
 
62
+ ## Scoping which tools the parent sees
63
+
64
+ A child decides what it advertises. Registering a tool with `exposedTo` narrows it to
65
+ named embedder origins, and the child only puts it on the wire once the connected parent
66
+ matches:
67
+
68
+ ```js
69
+ await document.modelContext.registerTool(
70
+ { name: 'checkout', description: 'Place the order', execute: placeOrder },
71
+ { exposedTo: ['https://shop.example'] }
72
+ );
73
+ ```
74
+
75
+ Embedded anywhere else, that tool never reaches the parent's `exposedTools` and cannot be
76
+ called through the element. Tools registered without `exposedTo` are unaffected and stay
77
+ visible to whichever parent the child's transport already allows.
78
+
79
+ Two limits are worth stating plainly:
80
+
81
+ - `exposedTo` can only **narrow** exposure. The child transport's `allowedOrigins` is still
82
+ what decides who may connect at all, and no allowlist widens past it.
83
+ - Enforcement here is the child's own JavaScript, not the browser. That is weaker than
84
+ native WebMCP, where the user agent enforces `exposedTo` and a compromised child cannot
85
+ opt out. Treat it as scoping, not as a security boundary against the child itself.
86
+
50
87
  ## Events
51
88
 
52
- | Event | Detail |
53
- | -------------------------- | --------------------------------------------------------- |
54
- | `mcp-iframe-ready` | `{ tools, resources, prompts }` - Fired when connected |
55
- | `mcp-iframe-error` | `{ error }` - Fired on connection failure |
56
- | `mcp-iframe-tools-changed` | `{ tools, resources, prompts }` - Fired after `refresh()` |
89
+ | Event | Detail |
90
+ | -------------------------- | ----------------------------------------------------------------------------------------- |
91
+ | `mcp-iframe-ready` | `{ tools, resources, prompts }` - Fired when connected |
92
+ | `mcp-iframe-error` | `{ error }` - Fired on connection or registration refresh failure |
93
+ | `mcp-iframe-items-changed` | `{ tools, resources, prompts }` - Fired after an advertised list change or manual refresh |
57
94
 
58
95
  ## Related Packages
59
96
 
60
- - [`@mcp-b/global`](https://docs.mcp-b.ai/packages/global/reference) - Full MCP-B browser runtime (required in the iframe)
97
+ - [`@mcp-b/global`](https://docs.mcp-b.ai/packages/global/reference) - Full MCP-B browser runtime; any compatible `document.modelContext` implementation works in the iframe
61
98
  - [`@mcp-b/transports`](https://docs.mcp-b.ai/packages/transports/reference) - Transport layer used internally
62
99
 
63
100
  ## License
@@ -0,0 +1,2 @@
1
+ import{DEFAULT_IFRAME_CHANNEL_ID as e,IframeParentTransport as t}from"@mcp-b/transports";import{Client as n,UriTemplate as r}from"@modelcontextprotocol/client";const i=3e4,a=()=>({tools:[],resources:[],resourceTemplates:[],prompts:[]});function o(e){return`registerResource`in e&&typeof e.registerResource==`function`&&`registerPrompt`in e&&typeof e.registerPrompt==`function`}function s(e){return`syncNativeTools`in e&&typeof e.syncNativeTools==`function`}function c(e){return e.replace(/[^a-zA-Z0-9_.-]/g,`_`)}function l(e,t,n=[]){let r=new URL(`mcp-iframe:`);return r.searchParams.set(`source`,e),r.searchParams.set(`uri`,t),[...new Set(n)].reduce((e,t,n)=>`${e}&variable-${n}={${t}*}`,r.href)}function u(e,t,n){return e.has(t)?(console.warn(`[MCPIframe] Ignoring duplicate iframe ${n} "${t}"`),!0):!1}function d(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Array.isArray(t)?t.map(decodeURIComponent):decodeURIComponent(t)]))}const f=[`src`,`srcdoc`,`name`,`sandbox`,`allow`,`allowfullscreen`,`width`,`height`,`loading`,`referrerpolicy`,`credentialless`];var p=class extends HTMLElement{#e=null;#t=null;#n=null;#r=0;#i=!1;static get observedAttributes(){return[...f,`id`,`target-origin`,`channel`,`call-timeout`,`prefix-separator`]}constructor(){super(),this.attachShadow({mode:`open`})}connectedCallback(){this.#e?this.#D():this.#a()}disconnectedCallback(){++this.#r,this.#O()}attributeChangedCallback(e,t,n){if(t!==n){if(e===`target-origin`||e===`channel`){this.#e&&this.isConnected&&this.#D();return}if(e===`call-timeout`){let e=Number(n);n!==null&&(!Number.isSafeInteger(e)||e<=0)&&console.warn(`[MCPIframe] Invalid call-timeout "${n}". Using ${i}.`);return}if(e===`id`||e===`prefix-separator`){if(e===`prefix-separator`&&n!==null){let e=c(n);e!==n&&console.warn(`[MCPIframe] Invalid prefix-separator "${n}". Using "${e}".`)}let t=this.#t;t&&this.#u(t,`Failed to update parent registrations`);return}if(!(!this.#e||!f.includes(e))){if(n===null?this.#e.removeAttribute(e):this.#e.setAttribute(e,n),e===`src`||e===`srcdoc`)++this.#r,this.#O();else if(e===`name`){let e=this.#t;e&&this.#u(e,`Failed to update parent registrations`)}}}}get iframe(){return this.#e}get ready(){return this.#t?.ready??!1}get exposedTools(){return Array.from(this.#t?.toolRegistrations.keys()??[])}get exposedResources(){return Array.from(this.#t?.resourceRegistrations.keys()??[])}get exposedPrompts(){return Array.from(this.#t?.promptRegistrations.keys()??[])}get itemPrefix(){return`${c(this.id||this.getAttribute(`name`)||`iframe`)}${c(this.getAttribute(`prefix-separator`)??`_`)}`}async refresh(){let e=this.#t;if(!e?.ready)throw Error(`Not connected to iframe MCP server`);if(await this.#d(e,!0,`Failed to refresh iframe items`),await this.#f(e),!this.#l(e)||!e.ready)throw Error(`Iframe connection changed before refresh completed`)}#a(){if(!this.#e){this.#e=document.createElement(`iframe`);for(let e of f){let t=this.getAttribute(e);t!==null&&this.#e.setAttribute(e,t)}this.#e.style.border=`none`,this.#e.addEventListener(`load`,()=>void this.#D()),this.shadowRoot?.appendChild(this.#e)}}async#o(e){let t=this.#n;t&&await t.catch(()=>void 0);let n=this.#e;if(e!==this.#r||!n||!this.isConnected)return;let r=this.#s(e,n);this.#n=r;try{await r}finally{this.#n===r&&(this.#n=null)}}async#s(r,i){let o;try{let s=new t({iframe:i,targetOrigin:this.#g(),channelId:this.getAttribute(`channel`)??e}),c={autoRefresh:!1,debounceMs:0,onChanged:()=>{o&&this.#u(o,`Failed to refresh iframe items`)}},l=new n({name:`MCPIframe:${this.id||`anonymous`}`,version:`1.0.0`},{versionNegotiation:{mode:`auto`},listChanged:{tools:c,resources:c,prompts:c}}),u={client:l,ready:!1,refreshRevision:0,refreshQueue:Promise.resolve(),items:a(),toolRegistrations:new Map,resourceRegistrations:new Map,promptRegistrations:new Map};if(o=u,this.#t=u,l.onclose=()=>{u.ready&&this.#O(u)},l.onerror=e=>{this.#h(u,e,`Iframe MCP client error`)},await l.connect(s),r!==this.#r||!this.#l(u)||(await this.#d(u,!1,`Failed to connect`),await this.#f(u),!this.#l(u)))return;u.ready=!0,this.#p(`mcp-iframe-ready`)}catch(e){if(r!==this.#r||!this.isConnected||o&&!this.#l(o))return;o?this.#h(o,e,`Failed to connect`):this.#m(e,`Failed to connect`)}}async#c(e){let t=e.getServerCapabilities(),[n,r,i,a]=await Promise.all([t?.tools?e.listTools(void 0,{cacheMode:`refresh`}).then(e=>e.tools):[],t?.resources?e.listResources(void 0,{cacheMode:`refresh`}).then(e=>e.resources):[],t?.resources?e.listResourceTemplates(void 0,{cacheMode:`refresh`}).then(e=>e.resourceTemplates):[],t?.prompts?e.listPrompts(void 0,{cacheMode:`refresh`}).then(e=>e.prompts):[]]);return{tools:n,resources:r,resourceTemplates:i,prompts:a}}#l(e){return this.#t===e}#u(e,t){this.#d(e,!0,t).catch(()=>void 0)}#d(e,t,n){let r=++e.refreshRevision,i=!1,a=e.refreshQueue.catch(()=>void 0).then(async()=>{let a=()=>this.#l(e)&&r===e.refreshRevision;if(a()){try{let t=await this.#c(e.client);if(!a())return;this.#x(e),i=!0,e.items=t,await this.#_(e,this.itemPrefix,a)}catch(t){if(!a()){i&&this.#x(e);return}throw this.#h(e,t,n),t}if(!a()){this.#x(e);return}t&&e.ready&&this.#p(`mcp-iframe-items-changed`)}});return e.refreshQueue=a,a}async#f(e){for(;this.#l(e);){let t=e.refreshQueue;if(await t,t===e.refreshQueue)return}}#p(e){this.dispatchEvent(new CustomEvent(e,{detail:{tools:this.exposedTools,resources:this.exposedResources,prompts:this.exposedPrompts}}))}#m(e,t){console.error(`[MCPIframe] ${t}:`,e),this.dispatchEvent(new CustomEvent(`mcp-iframe-error`,{detail:{error:e}}))}#h(e,t,n){this.#k(e)&&(this.#m(t,n),this.#A(e))}#g(){let e=this.getAttribute(`target-origin`);if(e!==null){if(!e)throw Error(`target-origin cannot be empty`);return e}if(this.#e?.hasAttribute(`sandbox`)&&!this.#e.sandbox.contains(`allow-same-origin`))throw Error(`Opaque sandboxed iframes require target-origin="*"`);if(this.hasAttribute(`srcdoc`))return window.location.origin;let t=this.getAttribute(`src`);if(!t)return window.location.origin;let n=new URL(t,window.location.href);if(n.protocol===`about:`)return window.location.origin;if(n.origin===`null`)throw Error(`Opaque iframe sources require target-origin="*"`);return n.origin}async#_(e,t,n){let r=document.modelContext??navigator.modelContext;if(!r)throw Error(`Model Context API not available on parent`);if(this.#i&&s(r)){if(await r.syncNativeTools(),!n())return;this.#i=!1}await this.#v(e,r,t,n),n()&&(o(r)?(this.#y(e,r,t),this.#b(e,r,t)):(e.items.resources.length>0||e.items.resourceTemplates.length>0||e.items.prompts.length>0)&&console.warn(`[MCPIframe] Parent modelContext does not provide the MCP-B resource and prompt extensions`))}async#v(e,t,n,r){for(let i of e.items.tools){if(!r())return;let a=`${n}${i.name}`;if(u(e.toolRegistrations,a,`tool`))continue;let o={name:a,...i.title!==void 0&&{title:i.title},description:i.description??`Tool from iframe: ${i.name}`,inputSchema:i.inputSchema,...i.annotations?.readOnlyHint!==void 0&&{annotations:{readOnlyHint:i.annotations.readOnlyHint}},execute:e=>this.#w(i.name,e)},s=new AbortController;e.toolRegistrations.set(a,s);try{await t.registerTool(o,{signal:s.signal})}catch(t){let n=s.signal.aborted;if(n||(this.#i=!0,s.abort()),e.toolRegistrations.get(a)===s&&e.toolRegistrations.delete(a),!n)throw t}}}#y(e,t,n){for(let r of e.items.resources){let i=l(n,r.uri);if(u(e.resourceRegistrations,i,`resource`))continue;let a={uri:i,name:r.name,...r.description!==void 0&&{description:r.description},...r.mimeType!==void 0&&{mimeType:r.mimeType},read:(e,t)=>this.#T(r.uri)},o=t.registerResource(a);e.resourceRegistrations.set(i,o)}for(let i of e.items.resourceTemplates){let a=new r(i.uriTemplate),o=l(n,i.uriTemplate,a.variableNames);if(u(e.resourceRegistrations,o,`resource`))continue;let s={uri:o,name:i.name,...i.description!==void 0&&{description:i.description},...i.mimeType!==void 0&&{mimeType:i.mimeType},read:(e,t)=>this.#T(a.expand(d(t??{})))},c=t.registerResource(s);e.resourceRegistrations.set(o,c)}}#b(e,t,n){for(let r of e.items.prompts){let i=`${n}${r.name}`;if(u(e.promptRegistrations,i,`prompt`))continue;let a={name:i,...r.description!==void 0&&{description:r.description},...r.arguments&&r.arguments.length>0&&{argsSchema:{type:`object`,properties:Object.fromEntries(r.arguments.map(e=>[e.name,{type:`string`,...e.description!==void 0&&{description:e.description}}])),required:r.arguments.filter(e=>e.required).map(e=>e.name)}},get:e=>this.#E(r.name,e)},o=t.registerPrompt(a);e.promptRegistrations.set(i,o)}}#x(e){let t=[...e.toolRegistrations.values()],n=[...e.resourceRegistrations.values(),...e.promptRegistrations.values()];e.toolRegistrations.clear(),e.resourceRegistrations.clear(),e.promptRegistrations.clear(),e.items=a(),t.length>0&&(this.#i=!0);for(let e of t)try{e.abort()}catch(e){console.warn(`[MCPIframe] Failed to unregister a parent tool:`,e)}for(let e of n)try{e.unregister()}catch(e){console.warn(`[MCPIframe] Failed to unregister a parent item:`,e)}}#S(){let e=this.#t;if(!e?.ready)throw Error(`Not connected to iframe MCP server`);return e.client}#C(){let e=Number(this.getAttribute(`call-timeout`));return Number.isSafeInteger(e)&&e>0?e:i}async#w(e,t){return this.#S().callTool({name:e,arguments:t},{timeout:this.#C()})}async#T(e){return this.#S().readResource({uri:e},{timeout:this.#C()})}async#E(e,t){return this.#S().getPrompt({name:e,arguments:t},{timeout:this.#C()})}async#D(){let e=++this.#r;await this.#O(),!(e!==this.#r||!this.isConnected)&&await this.#o(e)}async#O(e=this.#t){!e||!this.#k(e)||await this.#A(e)}#k(e){return this.#l(e)?(this.#t=null,++e.refreshRevision,e.ready=!1,delete e.client.onclose,delete e.client.onerror,this.#x(e),!0):!1}async#A(e){try{await e.client.close()}catch(e){console.warn(`[MCPIframeElement] Error closing client during disconnect:`,e)}}};function m(e=`mcp-iframe`){typeof customElements<`u`&&!customElements.get(e)&&customElements.define(e,p)}export{m as n,p as t};
2
+ //# sourceMappingURL=MCPIframeElement-BbBz_xpV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MCPIframeElement-BbBz_xpV.js","names":["#iframe","#reconnect","#createIframe","#connectionRequestGeneration","#disconnect","#connection","#requestRefresh","#queueRefresh","#waitForRefreshQueue","#isCurrentConnection","#connect","#connectionAttempt","#connectCurrent","#getTargetOrigin","#failConnection","#dispatchItems","#dispatchError","#fetchAllFromIframe","#unregisterAll","#registerAllOnModelContext","#invalidateConnection","#closeConnection","#nativeToolSyncPending","#registerToolsOnModelContext","#registerResourcesOnModelContext","#registerPromptsOnModelContext","#callIframeTool","#readIframeResource","#getIframePrompt","#requireClient","#getCallTimeout"],"sources":["../src/MCPIframeElement.ts"],"sourcesContent":["/**\n * MCPIframe Custom Element\n *\n * A custom element that wraps an iframe and automatically exposes tools,\n * resources, and prompts registered in the iframe's MCP server to the\n * parent page's Model Context API.\n *\n * The iframe should expose its MCP server through `document.modelContext`.\n * Older runtimes that only expose `navigator.modelContext` remain supported.\n *\n * @example\n * ```html\n * <mcp-iframe src=\"./child-app.html\" id=\"my-app\"></mcp-iframe>\n * ```\n *\n * Tool and prompt names use the element's ID as a prefix. Resources use a wrapper URI:\n * - Child registers tool \"calculate\" -> Parent sees \"my-app_calculate\"\n * - Child registers resource \"config://settings\" -> Parent sees an \"mcp-iframe:\" wrapper URI\n * - Child registers prompt \"help\" -> Parent sees \"my-app_help\"\n *\n * Note: The prefix separator defaults to underscore (_) to ensure WebMCP compatibility.\n * The parent model context validates the final prefixed tool name.\n *\n * @example\n * ```typescript\n * const mcpIframe = document.querySelector('mcp-iframe');\n * mcpIframe.addEventListener('mcp-iframe-ready', (e) => {\n * console.log('Tools:', e.detail.tools);\n * console.log('Resources:', e.detail.resources);\n * console.log('Prompts:', e.detail.prompts);\n * });\n * ```\n */\n\nimport { DEFAULT_IFRAME_CHANNEL_ID, IframeParentTransport } from '@mcp-b/transports';\nimport {\n type BrowserMcpServer,\n type PromptDescriptor,\n type ResourceDescriptor,\n} from '@mcp-b/webmcp-ts-sdk';\nimport type {\n CallToolResult,\n InputSchema,\n ModelContext,\n ModelContextTool,\n RegistrationHandle,\n} from '@mcp-b/webmcp-types';\nimport {\n Client,\n UriTemplate,\n type GetPromptResult,\n type Prompt,\n type ReadResourceResult,\n type Resource,\n type ResourceTemplateType,\n type Tool,\n type Variables,\n} from '@modelcontextprotocol/client';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CALL_TIMEOUT = 30000;\nconst DEFAULT_PREFIX_SEPARATOR = '_';\n\ntype McpBRegistrationExtensions = Pick<BrowserMcpServer, 'registerPrompt' | 'registerResource'>;\ntype NativeToolSyncExtension = Pick<BrowserMcpServer, 'syncNativeTools'>;\n\ninterface McpItems {\n tools: Tool[];\n resources: Resource[];\n resourceTemplates: ResourceTemplateType[];\n prompts: Prompt[];\n}\n\ninterface Connection {\n client: Client;\n ready: boolean;\n refreshRevision: number;\n refreshQueue: Promise<void>;\n items: McpItems;\n toolRegistrations: Map<string, AbortController>;\n resourceRegistrations: Map<string, RegistrationHandle>;\n promptRegistrations: Map<string, RegistrationHandle>;\n}\n\nconst emptyItems = (): McpItems => ({\n tools: [],\n resources: [],\n resourceTemplates: [],\n prompts: [],\n});\n\nfunction hasMcpBRegistrationExtensions(\n modelContext: ModelContext\n): modelContext is ModelContext & McpBRegistrationExtensions {\n return (\n 'registerResource' in modelContext &&\n typeof modelContext.registerResource === 'function' &&\n 'registerPrompt' in modelContext &&\n typeof modelContext.registerPrompt === 'function'\n );\n}\n\nfunction hasNativeToolSync(\n modelContext: ModelContext\n): modelContext is ModelContext & NativeToolSyncExtension {\n return 'syncNativeTools' in modelContext && typeof modelContext.syncNativeTools === 'function';\n}\n\n/**\n * Sanitizes a string to contain only valid MCP name characters.\n * Replaces invalid characters with underscores.\n */\nfunction sanitizeMCPNamePart(str: string): string {\n return str.replace(/[^a-zA-Z0-9_.-]/g, '_');\n}\n\nfunction createParentResourceUri(\n source: string,\n childUri: string,\n variables: readonly string[] = []\n): string {\n const parentUri = new URL('mcp-iframe:');\n parentUri.searchParams.set('source', source);\n parentUri.searchParams.set('uri', childUri);\n return [...new Set(variables)].reduce(\n (uri, variable, index) => `${uri}&variable-${index}={${variable}*}`,\n parentUri.href\n );\n}\n\n/**\n * Registration maps are keyed by the parent-visible name or URI, so a second item with the same\n * key can only shadow a live registration that `#unregisterAll` would then never tear down.\n */\nfunction isDuplicateRegistration(\n registrations: ReadonlyMap<string, unknown>,\n key: string,\n kind: string\n): boolean {\n if (!registrations.has(key)) return false;\n console.warn(`[MCPIframe] Ignoring duplicate iframe ${kind} \"${key}\"`);\n return true;\n}\n\nfunction decodeTemplateVariables(variables: Variables): Variables {\n return Object.fromEntries(\n Object.entries(variables).map(([name, value]) => [\n name,\n Array.isArray(value) ? value.map(decodeURIComponent) : decodeURIComponent(value),\n ])\n );\n}\n\n/** Standard iframe attributes that are mirrored to the internal iframe */\nconst IFRAME_ATTRIBUTES = [\n 'src',\n 'srcdoc',\n 'name',\n 'sandbox',\n 'allow',\n 'allowfullscreen',\n 'width',\n 'height',\n 'loading',\n 'referrerpolicy',\n 'credentialless',\n] satisfies readonly string[];\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Names of items currently exposed on the parent model context. */\nexport interface MCPIframeItemsEventDetail {\n tools: string[];\n resources: string[];\n prompts: string[];\n}\n\n/** Custom event detail for mcp-iframe-error */\nexport interface MCPIframeErrorEventDetail {\n error: unknown;\n}\n\nexport interface MCPIframeEventMap {\n 'mcp-iframe-ready': CustomEvent<MCPIframeItemsEventDetail>;\n 'mcp-iframe-error': CustomEvent<MCPIframeErrorEventDetail>;\n 'mcp-iframe-items-changed': CustomEvent<MCPIframeItemsEventDetail>;\n}\n\n// ============================================================================\n// MCPIframeElement\n// ============================================================================\n\n/**\n * MCPIframe Custom Element\n *\n * Wraps an iframe and exposes its MCP tools, resources, and prompts\n * to the parent's Model Context API.\n *\n * @fires mcp-iframe-ready - When connected to iframe's MCP server\n * @fires mcp-iframe-error - When the connection or an item refresh fails\n * @fires mcp-iframe-items-changed - When items are refreshed\n */\nexport class MCPIframeElement extends HTMLElement {\n declare addEventListener: (<K extends keyof MCPIframeEventMap>(\n type: K,\n listener: (this: MCPIframeElement, event: MCPIframeEventMap[K]) => unknown,\n options?: boolean | AddEventListenerOptions\n ) => void) &\n HTMLElement['addEventListener'];\n\n declare removeEventListener: (<K extends keyof MCPIframeEventMap>(\n type: K,\n listener: (this: MCPIframeElement, event: MCPIframeEventMap[K]) => unknown,\n options?: boolean | EventListenerOptions\n ) => void) &\n HTMLElement['removeEventListener'];\n\n #iframe: HTMLIFrameElement | null = null;\n #connection: Connection | null = null;\n #connectionAttempt: Promise<void> | null = null;\n #connectionRequestGeneration = 0;\n #nativeToolSyncPending = false;\n\n static get observedAttributes(): string[] {\n return [\n ...IFRAME_ATTRIBUTES,\n 'id',\n 'target-origin',\n 'channel',\n 'call-timeout',\n 'prefix-separator',\n ];\n }\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n // ==================== Lifecycle ====================\n\n connectedCallback(): void {\n if (this.#iframe) {\n void this.#reconnect();\n } else {\n this.#createIframe();\n }\n }\n\n disconnectedCallback(): void {\n ++this.#connectionRequestGeneration;\n void this.#disconnect();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n if (name === 'target-origin' || name === 'channel') {\n if (this.#iframe && this.isConnected) void this.#reconnect();\n return;\n }\n\n if (name === 'call-timeout') {\n const timeout = Number(newValue);\n if (newValue !== null && (!Number.isSafeInteger(timeout) || timeout <= 0)) {\n console.warn(\n `[MCPIframe] Invalid call-timeout \"${newValue}\". Using ${DEFAULT_CALL_TIMEOUT}.`\n );\n }\n return;\n }\n\n if (name === 'id' || name === 'prefix-separator') {\n if (name === 'prefix-separator' && newValue !== null) {\n const sanitized = sanitizeMCPNamePart(newValue);\n if (sanitized !== newValue) {\n console.warn(`[MCPIframe] Invalid prefix-separator \"${newValue}\". Using \"${sanitized}\".`);\n }\n }\n const connection = this.#connection;\n if (connection) {\n this.#requestRefresh(connection, 'Failed to update parent registrations');\n }\n return;\n }\n\n if (!this.#iframe || !IFRAME_ATTRIBUTES.includes(name)) return;\n if (newValue === null) {\n this.#iframe.removeAttribute(name);\n } else {\n this.#iframe.setAttribute(name, newValue);\n }\n if (name === 'src' || name === 'srcdoc') {\n ++this.#connectionRequestGeneration;\n void this.#disconnect();\n } else if (name === 'name') {\n const connection = this.#connection;\n if (connection) {\n this.#requestRefresh(connection, 'Failed to update parent registrations');\n }\n }\n }\n\n // ==================== Public API ====================\n\n /** The wrapped iframe element */\n get iframe(): HTMLIFrameElement | null {\n return this.#iframe;\n }\n\n /** Whether the element is connected to the iframe's MCP server */\n get ready(): boolean {\n return this.#connection?.ready ?? false;\n }\n\n /** List of exposed tool names (with prefix) */\n get exposedTools(): string[] {\n return Array.from(this.#connection?.toolRegistrations.keys() ?? []);\n }\n\n /** List of exposed parent-side resource wrapper URIs */\n get exposedResources(): string[] {\n return Array.from(this.#connection?.resourceRegistrations.keys() ?? []);\n }\n\n /** List of exposed prompt names (with prefix) */\n get exposedPrompts(): string[] {\n return Array.from(this.#connection?.promptRegistrations.keys() ?? []);\n }\n\n /** The item name prefix (id + separator), sanitized for MCP compatibility */\n get itemPrefix(): string {\n const rawId = this.id || this.getAttribute('name') || 'iframe';\n const id = sanitizeMCPNamePart(rawId);\n const separator = sanitizeMCPNamePart(\n this.getAttribute('prefix-separator') ?? DEFAULT_PREFIX_SEPARATOR\n );\n return `${id}${separator}`;\n }\n\n /** Manually refresh all items from the iframe */\n async refresh(): Promise<void> {\n const connection = this.#connection;\n if (!connection?.ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n\n await this.#queueRefresh(connection, true, 'Failed to refresh iframe items');\n await this.#waitForRefreshQueue(connection);\n if (!this.#isCurrentConnection(connection) || !connection.ready) {\n throw new Error('Iframe connection changed before refresh completed');\n }\n }\n\n // ==================== Private Methods ====================\n\n #createIframe(): void {\n if (this.#iframe) return;\n\n this.#iframe = document.createElement('iframe');\n\n // Mirror all iframe attributes\n for (const attr of IFRAME_ATTRIBUTES) {\n const value = this.getAttribute(attr);\n if (value !== null) {\n this.#iframe.setAttribute(attr, value);\n }\n }\n\n this.#iframe.style.border = 'none';\n\n // src/srcdoc changes only tear down; the load event rebuilds the connection.\n this.#iframe.addEventListener('load', () => void this.#reconnect());\n\n this.shadowRoot?.appendChild(this.#iframe);\n }\n\n async #connect(requestGeneration: number): Promise<void> {\n const activeAttempt = this.#connectionAttempt;\n if (activeAttempt) {\n await activeAttempt.catch(() => undefined);\n }\n const iframe = this.#iframe;\n if (requestGeneration !== this.#connectionRequestGeneration || !iframe || !this.isConnected) {\n return;\n }\n\n const attempt = this.#connectCurrent(requestGeneration, iframe);\n this.#connectionAttempt = attempt;\n try {\n await attempt;\n } finally {\n if (this.#connectionAttempt === attempt) {\n this.#connectionAttempt = null;\n }\n }\n }\n\n async #connectCurrent(requestGeneration: number, iframe: HTMLIFrameElement): Promise<void> {\n let connection: Connection | undefined;\n\n try {\n const transport = new IframeParentTransport({\n iframe,\n targetOrigin: this.#getTargetOrigin(),\n channelId: this.getAttribute('channel') ?? DEFAULT_IFRAME_CHANNEL_ID,\n });\n const onListChanged = () => {\n if (connection) this.#requestRefresh(connection, 'Failed to refresh iframe items');\n };\n const listChanged = { autoRefresh: false, debounceMs: 0, onChanged: onListChanged };\n const client = new Client(\n { name: `MCPIframe:${this.id || 'anonymous'}`, version: '1.0.0' },\n {\n versionNegotiation: { mode: 'auto' },\n listChanged: { tools: listChanged, resources: listChanged, prompts: listChanged },\n }\n );\n const currentConnection: Connection = {\n client,\n ready: false,\n refreshRevision: 0,\n refreshQueue: Promise.resolve(),\n items: emptyItems(),\n toolRegistrations: new Map(),\n resourceRegistrations: new Map(),\n promptRegistrations: new Map(),\n };\n connection = currentConnection;\n this.#connection = currentConnection;\n client.onclose = () => {\n if (currentConnection.ready) void this.#disconnect(currentConnection);\n };\n client.onerror = (error) => {\n this.#failConnection(currentConnection, error, 'Iframe MCP client error');\n };\n\n await client.connect(transport);\n if (\n requestGeneration !== this.#connectionRequestGeneration ||\n !this.#isCurrentConnection(currentConnection)\n ) {\n return;\n }\n\n await this.#queueRefresh(currentConnection, false, 'Failed to connect');\n await this.#waitForRefreshQueue(currentConnection);\n if (!this.#isCurrentConnection(currentConnection)) return;\n currentConnection.ready = true;\n this.#dispatchItems('mcp-iframe-ready');\n } catch (error) {\n if (\n requestGeneration !== this.#connectionRequestGeneration ||\n !this.isConnected ||\n (connection && !this.#isCurrentConnection(connection))\n ) {\n return;\n }\n if (connection) {\n this.#failConnection(connection, error, 'Failed to connect');\n } else {\n this.#dispatchError(error, 'Failed to connect');\n }\n }\n }\n\n async #fetchAllFromIframe(client: Client): Promise<McpItems> {\n const capabilities = client.getServerCapabilities();\n const [tools, resources, resourceTemplates, prompts] = await Promise.all([\n capabilities?.tools\n ? client.listTools(undefined, { cacheMode: 'refresh' }).then((result) => result.tools)\n : [],\n capabilities?.resources\n ? client\n .listResources(undefined, { cacheMode: 'refresh' })\n .then((result) => result.resources)\n : [],\n capabilities?.resources\n ? client\n .listResourceTemplates(undefined, { cacheMode: 'refresh' })\n .then((result) => result.resourceTemplates)\n : [],\n capabilities?.prompts\n ? client.listPrompts(undefined, { cacheMode: 'refresh' }).then((result) => result.prompts)\n : [],\n ]);\n return { tools, resources, resourceTemplates, prompts };\n }\n\n #isCurrentConnection(connection: Connection): boolean {\n return this.#connection === connection;\n }\n\n #requestRefresh(connection: Connection, context: string): void {\n void this.#queueRefresh(connection, true, context).catch(() => undefined);\n }\n\n #queueRefresh(connection: Connection, notify: boolean, context: string): Promise<void> {\n const refreshRevision = ++connection.refreshRevision;\n let registrationsReplaced = false;\n const refresh = connection.refreshQueue\n .catch(() => undefined)\n .then(async () => {\n const isActive = () =>\n this.#isCurrentConnection(connection) && refreshRevision === connection.refreshRevision;\n if (!isActive()) return;\n\n try {\n const items = await this.#fetchAllFromIframe(connection.client);\n if (!isActive()) return;\n\n this.#unregisterAll(connection);\n registrationsReplaced = true;\n connection.items = items;\n await this.#registerAllOnModelContext(connection, this.itemPrefix, isActive);\n } catch (error) {\n if (!isActive()) {\n if (registrationsReplaced) this.#unregisterAll(connection);\n return;\n }\n this.#failConnection(connection, error, context);\n throw error;\n }\n\n if (!isActive()) {\n this.#unregisterAll(connection);\n return;\n }\n if (notify && connection.ready) this.#dispatchItems('mcp-iframe-items-changed');\n });\n connection.refreshQueue = refresh;\n return refresh;\n }\n\n async #waitForRefreshQueue(connection: Connection): Promise<void> {\n while (this.#isCurrentConnection(connection)) {\n const refresh = connection.refreshQueue;\n await refresh;\n if (refresh === connection.refreshQueue) return;\n }\n }\n\n #dispatchItems(type: 'mcp-iframe-ready' | 'mcp-iframe-items-changed'): void {\n this.dispatchEvent(\n new CustomEvent<MCPIframeItemsEventDetail>(type, {\n detail: {\n tools: this.exposedTools,\n resources: this.exposedResources,\n prompts: this.exposedPrompts,\n },\n })\n );\n }\n\n #dispatchError(error: unknown, context: string): void {\n console.error(`[MCPIframe] ${context}:`, error);\n this.dispatchEvent(\n new CustomEvent<MCPIframeErrorEventDetail>('mcp-iframe-error', { detail: { error } })\n );\n }\n\n #failConnection(connection: Connection, error: unknown, context: string): void {\n if (!this.#invalidateConnection(connection)) return;\n this.#dispatchError(error, context);\n void this.#closeConnection(connection);\n }\n\n #getTargetOrigin(): string {\n const explicit = this.getAttribute('target-origin');\n if (explicit !== null) {\n if (!explicit) throw new Error('target-origin cannot be empty');\n return explicit;\n }\n\n if (\n this.#iframe?.hasAttribute('sandbox') &&\n !this.#iframe.sandbox.contains('allow-same-origin')\n ) {\n throw new Error('Opaque sandboxed iframes require target-origin=\"*\"');\n }\n if (this.hasAttribute('srcdoc')) return window.location.origin;\n\n const src = this.getAttribute('src');\n if (!src) return window.location.origin;\n const url = new URL(src, window.location.href);\n if (url.protocol === 'about:') return window.location.origin;\n if (url.origin === 'null') {\n throw new Error('Opaque iframe sources require target-origin=\"*\"');\n }\n return url.origin;\n }\n\n async #registerAllOnModelContext(\n connection: Connection,\n prefix: string,\n isActive: () => boolean\n ): Promise<void> {\n const modelContext: ModelContext | undefined = document.modelContext ?? navigator.modelContext;\n if (!modelContext) {\n throw new Error('Model Context API not available on parent');\n }\n\n if (this.#nativeToolSyncPending && hasNativeToolSync(modelContext)) {\n // Drain native reconciliation queued by the aborted registrations before reusing names.\n await modelContext.syncNativeTools();\n if (!isActive()) return;\n this.#nativeToolSyncPending = false;\n }\n\n await this.#registerToolsOnModelContext(connection, modelContext, prefix, isActive);\n if (!isActive()) return;\n if (hasMcpBRegistrationExtensions(modelContext)) {\n this.#registerResourcesOnModelContext(connection, modelContext, prefix);\n this.#registerPromptsOnModelContext(connection, modelContext, prefix);\n } else if (\n connection.items.resources.length > 0 ||\n connection.items.resourceTemplates.length > 0 ||\n connection.items.prompts.length > 0\n ) {\n console.warn(\n '[MCPIframe] Parent modelContext does not provide the MCP-B resource and prompt extensions'\n );\n }\n }\n\n async #registerToolsOnModelContext(\n connection: Connection,\n modelContext: ModelContext,\n prefix: string,\n isActive: () => boolean\n ): Promise<void> {\n for (const tool of connection.items.tools) {\n if (!isActive()) return;\n const prefixedName = `${prefix}${tool.name}`;\n if (isDuplicateRegistration(connection.toolRegistrations, prefixedName, 'tool')) continue;\n\n const descriptor: ModelContextTool<Record<string, unknown>, CallToolResult> & {\n inputSchema: InputSchema;\n } = {\n name: prefixedName,\n ...(tool.title !== undefined && { title: tool.title }),\n description: tool.description ?? `Tool from iframe: ${tool.name}`,\n inputSchema: tool.inputSchema,\n ...(tool.annotations?.readOnlyHint !== undefined && {\n annotations: { readOnlyHint: tool.annotations.readOnlyHint },\n }),\n execute: (args) => this.#callIframeTool(tool.name, args),\n };\n const controller = new AbortController();\n connection.toolRegistrations.set(prefixedName, controller);\n try {\n await modelContext.registerTool(descriptor, { signal: controller.signal });\n } catch (error) {\n const wasAborted = controller.signal.aborted;\n if (!wasAborted) {\n this.#nativeToolSyncPending = true;\n controller.abort();\n }\n if (connection.toolRegistrations.get(prefixedName) === controller) {\n connection.toolRegistrations.delete(prefixedName);\n }\n if (!wasAborted) throw error;\n }\n }\n }\n\n #registerResourcesOnModelContext(\n connection: Connection,\n modelContext: McpBRegistrationExtensions,\n prefix: string\n ): void {\n for (const resource of connection.items.resources) {\n const parentUri = createParentResourceUri(prefix, resource.uri);\n if (isDuplicateRegistration(connection.resourceRegistrations, parentUri, 'resource'))\n continue;\n\n const descriptor: ResourceDescriptor = {\n uri: parentUri,\n name: resource.name,\n ...(resource.description !== undefined && { description: resource.description }),\n ...(resource.mimeType !== undefined && { mimeType: resource.mimeType }),\n read: (_uri, _params) => this.#readIframeResource(resource.uri),\n };\n const registration = modelContext.registerResource(descriptor);\n connection.resourceRegistrations.set(parentUri, registration);\n }\n\n for (const resourceTemplate of connection.items.resourceTemplates) {\n const childTemplate = new UriTemplate(resourceTemplate.uriTemplate);\n const parentUri = createParentResourceUri(\n prefix,\n resourceTemplate.uriTemplate,\n childTemplate.variableNames\n );\n if (isDuplicateRegistration(connection.resourceRegistrations, parentUri, 'resource'))\n continue;\n\n const descriptor: ResourceDescriptor = {\n uri: parentUri,\n name: resourceTemplate.name,\n ...(resourceTemplate.description !== undefined && {\n description: resourceTemplate.description,\n }),\n ...(resourceTemplate.mimeType !== undefined && { mimeType: resourceTemplate.mimeType }),\n read: (_uri, params) =>\n this.#readIframeResource(childTemplate.expand(decodeTemplateVariables(params ?? {}))),\n };\n const registration = modelContext.registerResource(descriptor);\n connection.resourceRegistrations.set(parentUri, registration);\n }\n }\n\n #registerPromptsOnModelContext(\n connection: Connection,\n modelContext: McpBRegistrationExtensions,\n prefix: string\n ): void {\n for (const prompt of connection.items.prompts) {\n const prefixedName = `${prefix}${prompt.name}`;\n if (isDuplicateRegistration(connection.promptRegistrations, prefixedName, 'prompt')) continue;\n\n const descriptor: PromptDescriptor = {\n name: prefixedName,\n ...(prompt.description !== undefined && { description: prompt.description }),\n ...(prompt.arguments &&\n prompt.arguments.length > 0 && {\n argsSchema: {\n type: 'object',\n properties: Object.fromEntries(\n prompt.arguments.map((arg) => [\n arg.name,\n {\n type: 'string',\n ...(arg.description !== undefined && { description: arg.description }),\n },\n ])\n ),\n required: prompt.arguments.filter((a) => a.required).map((a) => a.name),\n } satisfies InputSchema,\n }),\n get: (args) => this.#getIframePrompt(prompt.name, args),\n };\n const registration = modelContext.registerPrompt(descriptor);\n connection.promptRegistrations.set(prefixedName, registration);\n }\n }\n\n #unregisterAll(connection: Connection): void {\n const tools = [...connection.toolRegistrations.values()];\n const registrations = [\n ...connection.resourceRegistrations.values(),\n ...connection.promptRegistrations.values(),\n ];\n connection.toolRegistrations.clear();\n connection.resourceRegistrations.clear();\n connection.promptRegistrations.clear();\n connection.items = emptyItems();\n if (tools.length > 0) this.#nativeToolSyncPending = true;\n\n for (const controller of tools) {\n try {\n controller.abort();\n } catch (error) {\n console.warn('[MCPIframe] Failed to unregister a parent tool:', error);\n }\n }\n for (const registration of registrations) {\n try {\n registration.unregister();\n } catch (error) {\n console.warn('[MCPIframe] Failed to unregister a parent item:', error);\n }\n }\n }\n\n #requireClient(): Client {\n const connection = this.#connection;\n if (!connection?.ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n return connection.client;\n }\n\n #getCallTimeout(): number {\n const timeout = Number(this.getAttribute('call-timeout'));\n return Number.isSafeInteger(timeout) && timeout > 0 ? timeout : DEFAULT_CALL_TIMEOUT;\n }\n\n async #callIframeTool(toolName: string, args: Record<string, unknown>): Promise<CallToolResult> {\n return this.#requireClient().callTool(\n { name: toolName, arguments: args },\n { timeout: this.#getCallTimeout() }\n );\n }\n\n async #readIframeResource(uri: string): Promise<ReadResourceResult> {\n return this.#requireClient().readResource({ uri }, { timeout: this.#getCallTimeout() });\n }\n\n async #getIframePrompt(name: string, args: Record<string, string>): Promise<GetPromptResult> {\n return this.#requireClient().getPrompt(\n { name, arguments: args },\n { timeout: this.#getCallTimeout() }\n );\n }\n\n async #reconnect(): Promise<void> {\n const requestGeneration = ++this.#connectionRequestGeneration;\n await this.#disconnect();\n if (requestGeneration !== this.#connectionRequestGeneration || !this.isConnected) return;\n await this.#connect(requestGeneration);\n }\n\n async #disconnect(connection = this.#connection): Promise<void> {\n if (!connection || !this.#invalidateConnection(connection)) return;\n await this.#closeConnection(connection);\n }\n\n #invalidateConnection(connection: Connection): boolean {\n if (!this.#isCurrentConnection(connection)) return false;\n this.#connection = null;\n ++connection.refreshRevision;\n connection.ready = false;\n delete connection.client.onclose;\n delete connection.client.onerror;\n this.#unregisterAll(connection);\n return true;\n }\n\n async #closeConnection(connection: Connection): Promise<void> {\n try {\n await connection.client.close();\n } catch (error) {\n console.warn('[MCPIframeElement] Error closing client during disconnect:', error);\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'mcp-iframe': MCPIframeElement;\n }\n}\n\n/** Register the custom element with a custom tag name */\nexport function registerMCPIframeElement(tagName = 'mcp-iframe'): void {\n if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {\n customElements.define(tagName, MCPIframeElement);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ //#region src/MCPIframeElement.d.ts
2
+ /**
3
+ * MCPIframe Custom Element
4
+ *
5
+ * A custom element that wraps an iframe and automatically exposes tools,
6
+ * resources, and prompts registered in the iframe's MCP server to the
7
+ * parent page's Model Context API.
8
+ *
9
+ * The iframe should expose its MCP server through `document.modelContext`.
10
+ * Older runtimes that only expose `navigator.modelContext` remain supported.
11
+ *
12
+ * @example
13
+ * ```html
14
+ * <mcp-iframe src="./child-app.html" id="my-app"></mcp-iframe>
15
+ * ```
16
+ *
17
+ * Tool and prompt names use the element's ID as a prefix. Resources use a wrapper URI:
18
+ * - Child registers tool "calculate" -> Parent sees "my-app_calculate"
19
+ * - Child registers resource "config://settings" -> Parent sees an "mcp-iframe:" wrapper URI
20
+ * - Child registers prompt "help" -> Parent sees "my-app_help"
21
+ *
22
+ * Note: The prefix separator defaults to underscore (_) to ensure WebMCP compatibility.
23
+ * The parent model context validates the final prefixed tool name.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const mcpIframe = document.querySelector('mcp-iframe');
28
+ * mcpIframe.addEventListener('mcp-iframe-ready', (e) => {
29
+ * console.log('Tools:', e.detail.tools);
30
+ * console.log('Resources:', e.detail.resources);
31
+ * console.log('Prompts:', e.detail.prompts);
32
+ * });
33
+ * ```
34
+ */
35
+ /** Names of items currently exposed on the parent model context. */
36
+ interface MCPIframeItemsEventDetail {
37
+ tools: string[];
38
+ resources: string[];
39
+ prompts: string[];
40
+ }
41
+ /** Custom event detail for mcp-iframe-error */
42
+ interface MCPIframeErrorEventDetail {
43
+ error: unknown;
44
+ }
45
+ interface MCPIframeEventMap {
46
+ 'mcp-iframe-ready': CustomEvent<MCPIframeItemsEventDetail>;
47
+ 'mcp-iframe-error': CustomEvent<MCPIframeErrorEventDetail>;
48
+ 'mcp-iframe-items-changed': CustomEvent<MCPIframeItemsEventDetail>;
49
+ }
50
+ /**
51
+ * MCPIframe Custom Element
52
+ *
53
+ * Wraps an iframe and exposes its MCP tools, resources, and prompts
54
+ * to the parent's Model Context API.
55
+ *
56
+ * @fires mcp-iframe-ready - When connected to iframe's MCP server
57
+ * @fires mcp-iframe-error - When the connection or an item refresh fails
58
+ * @fires mcp-iframe-items-changed - When items are refreshed
59
+ */
60
+ declare class MCPIframeElement extends HTMLElement {
61
+ #private;
62
+ addEventListener: (<K extends keyof MCPIframeEventMap>(type: K, listener: (this: MCPIframeElement, event: MCPIframeEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions) => void) & HTMLElement['addEventListener'];
63
+ removeEventListener: (<K extends keyof MCPIframeEventMap>(type: K, listener: (this: MCPIframeElement, event: MCPIframeEventMap[K]) => unknown, options?: boolean | EventListenerOptions) => void) & HTMLElement['removeEventListener'];
64
+ static get observedAttributes(): string[];
65
+ constructor();
66
+ connectedCallback(): void;
67
+ disconnectedCallback(): void;
68
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
69
+ /** The wrapped iframe element */
70
+ get iframe(): HTMLIFrameElement | null;
71
+ /** Whether the element is connected to the iframe's MCP server */
72
+ get ready(): boolean;
73
+ /** List of exposed tool names (with prefix) */
74
+ get exposedTools(): string[];
75
+ /** List of exposed parent-side resource wrapper URIs */
76
+ get exposedResources(): string[];
77
+ /** List of exposed prompt names (with prefix) */
78
+ get exposedPrompts(): string[];
79
+ /** The item name prefix (id + separator), sanitized for MCP compatibility */
80
+ get itemPrefix(): string;
81
+ /** Manually refresh all items from the iframe */
82
+ refresh(): Promise<void>;
83
+ }
84
+ declare global {
85
+ interface HTMLElementTagNameMap {
86
+ 'mcp-iframe': MCPIframeElement;
87
+ }
88
+ }
89
+ /** Register the custom element with a custom tag name */
90
+ declare function registerMCPIframeElement(tagName?: string): void;
91
+ //#endregion
92
+ export { registerMCPIframeElement as a, MCPIframeItemsEventDetail as i, MCPIframeErrorEventDetail as n, MCPIframeEventMap as r, MCPIframeElement as t };
93
+ //# sourceMappingURL=MCPIframeElement-d12Xcjs_.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { a as registerMCPIframeElement, i as MCPIframeItemsEventDetail, n as MCPIframeErrorEventDetail, r as MCPIframeEventMap, t as MCPIframeElement } from "./MCPIframeElement-d12Xcjs_.js";
2
+ export { MCPIframeElement, type MCPIframeErrorEventDetail, type MCPIframeEventMap, type MCPIframeItemsEventDetail, registerMCPIframeElement };
@@ -0,0 +1 @@
1
+ import{n as e,t}from"./MCPIframeElement-BbBz_xpV.js";export{t as MCPIframeElement,e as registerMCPIframeElement};
package/dist/index.d.ts CHANGED
@@ -1,68 +1,2 @@
1
- import { Client, Prompt, Resource, Tool } from "@mcp-b/webmcp-ts-sdk";
2
-
3
- //#region src/MCPIframeElement.d.ts
4
- /** Custom event detail for mcp-iframe-ready */
5
- interface MCPIframeReadyEventDetail {
6
- tools: string[];
7
- resources: string[];
8
- prompts: string[];
9
- }
10
- /** Custom event detail for mcp-iframe-error */
11
- interface MCPIframeErrorEventDetail {
12
- error: unknown;
13
- }
14
- /** Custom event detail for mcp-iframe-tools-changed */
15
- interface MCPIframeToolsChangedEventDetail {
16
- tools: string[];
17
- resources: string[];
18
- prompts: string[];
19
- }
20
- /**
21
- * MCPIframe Custom Element
22
- *
23
- * Wraps an iframe and exposes its MCP tools, resources, and prompts
24
- * to the parent's Model Context API.
25
- *
26
- * @fires mcp-iframe-ready - When connected to iframe's MCP server
27
- * @fires mcp-iframe-error - When connection fails
28
- * @fires mcp-iframe-tools-changed - When items are refreshed
29
- */
30
- declare class MCPIframeElement extends HTMLElement {
31
- #private;
32
- static get observedAttributes(): string[];
33
- constructor();
34
- connectedCallback(): void;
35
- disconnectedCallback(): void;
36
- attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
37
- /** The wrapped iframe element */
38
- get iframe(): HTMLIFrameElement | null;
39
- /** The MCP client (if connected) */
40
- get client(): Client | null;
41
- /** Whether the element is connected to the iframe's MCP server */
42
- get ready(): boolean;
43
- /** List of exposed tool names (with prefix) */
44
- get exposedTools(): string[];
45
- /** List of exposed resource URIs (with prefix) */
46
- get exposedResources(): string[];
47
- /** List of exposed prompt names (with prefix) */
48
- get exposedPrompts(): string[];
49
- /** Raw tools from the iframe's MCP server (without prefix) */
50
- get mcpTools(): Tool[];
51
- /** Raw resources from the iframe's MCP server (without prefix) */
52
- get mcpResources(): Resource[];
53
- /** Raw prompts from the iframe's MCP server (without prefix) */
54
- get mcpPrompts(): Prompt[];
55
- /** The item name prefix (id + separator), sanitized for MCP compatibility */
56
- get itemPrefix(): string;
57
- /** @deprecated Use itemPrefix instead */
58
- get toolPrefix(): string;
59
- /** Manually refresh all items from the iframe */
60
- refresh(): Promise<void>;
61
- /** @deprecated Use refresh() instead */
62
- refreshTools(): Promise<void>;
63
- }
64
- /** Register the custom element with a custom tag name */
65
- declare function registerMCPIframeElement(tagName?: string): void;
66
- //#endregion
67
- export { MCPIframeElement, type MCPIframeErrorEventDetail, type MCPIframeReadyEventDetail, type MCPIframeToolsChangedEventDetail, registerMCPIframeElement };
68
- //# sourceMappingURL=index.d.ts.map
1
+ import { i as MCPIframeItemsEventDetail, n as MCPIframeErrorEventDetail, r as MCPIframeEventMap, t as MCPIframeElement } from "./MCPIframeElement-d12Xcjs_.js";
2
+ export { MCPIframeElement, type MCPIframeErrorEventDetail, type MCPIframeEventMap, type MCPIframeItemsEventDetail };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{IframeParentTransport as e}from"@mcp-b/transports";import{Client as t}from"@mcp-b/webmcp-ts-sdk";const n=3e4,r=`mcp-iframe`,i=/^[a-zA-Z0-9_-]{1,128}$/;function a(e){return/^[a-zA-Z0-9_-]*$/.test(e)}function o(e){return i.test(e)}function s(e){return e.replace(/[^a-zA-Z0-9_-]/g,`_`)}const c=[`src`,`srcdoc`,`name`,`sandbox`,`allow`,`allowfullscreen`,`width`,`height`,`loading`,`referrerpolicy`,`credentialless`];var l=class extends HTMLElement{#e=null;#t=null;#n=null;#r=!1;#i=!1;#a=[];#o=[];#s=[];#c=new Set;#l=new Map;#u=new Map;#d=n;#f=`_`;#p=r;#m=null;static get observedAttributes(){return[...c,`target-origin`,`channel`,`call-timeout`,`prefix-separator`]}constructor(){super(),this.attachShadow({mode:`open`})}connectedCallback(){this.#h()}disconnectedCallback(){this.#k()}attributeChangedCallback(e,t,i){if(t!==i)switch(e){case`target-origin`:this.#m=i,this.#r&&this.#D();break;case`channel`:this.#p=i??r,this.#r&&this.#D();break;case`call-timeout`:this.#d=i?Number.parseInt(i,10):n;break;case`prefix-separator`:{let e=i??`_`;a(e)?this.#f=e:(console.warn(`[MCPIframe] Invalid prefix-separator "${e}". Only alphanumeric characters, underscores, and hyphens are allowed. Using sanitized value: "${s(e)}"`),this.#f=s(e)),this.#r&&(this.#C(),this.#y());break}default:this.#e&&c.includes(e)&&(i===null?this.#e.removeAttribute(e):this.#e.setAttribute(e,i),(e===`src`||e===`srcdoc`)&&this.#D())}}get iframe(){return this.#e}get client(){return this.#t}get ready(){return this.#r}get exposedTools(){return Array.from(this.#c.values())}get exposedResources(){return Array.from(this.#l.keys())}get exposedPrompts(){return Array.from(this.#u.keys())}get mcpTools(){return[...this.#a]}get mcpResources(){return[...this.#o]}get mcpPrompts(){return[...this.#s]}get itemPrefix(){let e=this.id||this.getAttribute(`name`)||`iframe`,t=s(e);return t!==e&&console.warn(`[MCPIframe] ID/name "${e}" contains invalid characters for MCP names. Using sanitized value: "${t}"`),`${t}${this.#f}`}get toolPrefix(){return this.itemPrefix}async refresh(){if(!this.#t||!this.#r)throw Error(`Not connected to iframe MCP server`);await this.#_(),this.#C(),this.#y(),this.dispatchEvent(new CustomEvent(`mcp-iframe-tools-changed`,{detail:{tools:this.exposedTools,resources:this.exposedResources,prompts:this.exposedPrompts}}))}async refreshTools(){return this.refresh()}#h(){this.#e=document.createElement(`iframe`);for(let e of c){let t=this.getAttribute(e);t!==null&&this.#e.setAttribute(e,t)}this.#e.style.border=`none`,this.#e.style.width=this.getAttribute(`width`)??`100%`,this.#e.style.height=this.getAttribute(`height`)??`100%`,this.#e.addEventListener(`load`,()=>void this.#g()),this.shadowRoot?.appendChild(this.#e)}async#g(){if(!(this.#i||!this.#e)){this.#i=!0;try{let n=this.#v();if(!n){console.warn(`[MCPIframe] Cannot determine target origin. Set target-origin attribute.`);return}this.#n=new e({iframe:this.#e,targetOrigin:n,channelId:this.#p}),this.#t=new t({name:`MCPIframe:${this.id||`anonymous`}`,version:`1.0.0`}),await this.#t.connect(this.#n),this.#r=!0,await this.#_(),this.#y(),this.dispatchEvent(new CustomEvent(`mcp-iframe-ready`,{detail:{tools:this.exposedTools,resources:this.exposedResources,prompts:this.exposedPrompts}}))}catch(e){console.error(`[MCPIframe] Failed to connect:`,e),this.dispatchEvent(new CustomEvent(`mcp-iframe-error`,{detail:{error:e}}))}finally{this.#i=!1}}}async#_(){if(!this.#t)return;let[e,t,n]=await Promise.all([this.#t.listTools(),this.#t.listResources().catch(e=>(console.warn(`[MCPIframeElement] listResources failed, defaulting to empty:`,e),{resources:[]})),this.#t.listPrompts().catch(e=>(console.warn(`[MCPIframeElement] listPrompts failed, defaulting to empty:`,e),{prompts:[]}))]);this.#a=e.tools,this.#o=t.resources,this.#s=n.prompts}#v(){if(this.#m)return this.#m;let e=this.getAttribute(`src`);if(e)try{return new URL(e,window.location.href).origin}catch(t){console.warn(`[MCPIframeElement] Invalid src URL for origin detection:`,e,t)}return window.location.origin}#y(){let e=navigator.modelContext;if(!e){console.warn(`[MCPIframe] Model Context API not available on parent`);return}let t=e;this.#b(t),this.#x(t),this.#S(t)}#b(e){for(let t of this.#a){let n=`${this.itemPrefix}${t.name}`;if(!o(n)){n.length>128?console.error(`[MCPIframe] Cannot register tool "${t.name}": prefixed name "${n}" exceeds 128 characters (${n.length} chars). Skipping registration.`):console.error(`[MCPIframe] Cannot register tool "${t.name}": prefixed name "${n}" contains invalid characters. Tool names must match pattern: ^[a-zA-Z0-9_-]{1,128}\$. Skipping registration.`);continue}e.registerTool({name:n,description:t.description??`Tool from iframe: ${t.name}`,...t.inputSchema&&{inputSchema:t.inputSchema},execute:e=>this.#w(t.name,e)}),this.#c.add(n)}}#x(e){for(let t of this.#o){let n=`${this.itemPrefix}${t.uri}`,r={uri:n,name:t.name,...t.description!==void 0&&{description:t.description},...t.mimeType!==void 0&&{mimeType:t.mimeType},read:(e,n)=>this.#T(t.uri)},i=e.registerResource(r);this.#l.set(n,i)}}#S(e){for(let t of this.#s){let n=`${this.itemPrefix}${t.name}`;if(!o(n)){n.length>128?console.error(`[MCPIframe] Cannot register prompt "${t.name}": prefixed name "${n}" exceeds 128 characters (${n.length} chars). Skipping registration.`):console.error(`[MCPIframe] Cannot register prompt "${t.name}": prefixed name "${n}" contains invalid characters. Prompt names must match pattern: ^[a-zA-Z0-9_-]{1,128}\$. Skipping registration.`);continue}let r={name:n,...t.description!==void 0&&{description:t.description},...t.arguments&&t.arguments.length>0&&{argsSchema:{type:`object`,properties:Object.fromEntries(t.arguments.map(e=>[e.name,{type:`string`,...e.description!==void 0&&{description:e.description}}])),required:t.arguments.filter(e=>e.required).map(e=>e.name)}},get:e=>this.#E(t.name,e)},i=e.registerPrompt(r);this.#u.set(n,i)}}#C(){let e=navigator.modelContext;if(e){let t=e;for(let e of this.#c.values())try{t.unregisterTool(e)}catch(t){console.warn(`[MCPIframeElement] Failed to unregister tool "${e}" during cleanup:`,t)}}this.#c.clear();for(let e of this.#l.values())e.unregister();this.#l.clear();for(let e of this.#u.values())e.unregister();this.#u.clear()}async#w(e,t){if(!this.#t||!this.#r)throw Error(`Not connected to iframe MCP server`);let n=new AbortController,r=setTimeout(()=>n.abort(),this.#d);try{return await this.#t.callTool({name:e,arguments:t},void 0,{signal:n.signal})}finally{clearTimeout(r)}}async#T(e){if(!this.#t||!this.#r)throw Error(`Not connected to iframe MCP server`);let t=new AbortController,n=setTimeout(()=>t.abort(),this.#d);try{return await this.#t.readResource({uri:e},{signal:t.signal})}finally{clearTimeout(n)}}async#E(e,t){if(!this.#t||!this.#r)throw Error(`Not connected to iframe MCP server`);let n=new AbortController,r=setTimeout(()=>n.abort(),this.#d);try{return await this.#t.getPrompt({name:e,arguments:t},{signal:n.signal})}finally{clearTimeout(r)}}async#D(){await this.#O(),await new Promise(e=>setTimeout(e,100)),await this.#g()}async#O(){if(this.#r=!1,this.#a=[],this.#o=[],this.#s=[],this.#C(),this.#t){try{await this.#t.close()}catch(e){console.warn(`[MCPIframeElement] Error closing client during disconnect:`,e)}this.#t=null}if(this.#n){try{await this.#n.close()}catch(e){console.warn(`[MCPIframeElement] Error closing transport during disconnect:`,e)}this.#n=null}}async#k(){await this.#O()}};function u(e=`mcp-iframe`){typeof customElements<`u`&&!customElements.get(e)&&customElements.define(e,l)}typeof window<`u`&&typeof customElements<`u`&&u();export{l as MCPIframeElement,u as registerMCPIframeElement};
1
+ import{n as e,t}from"./MCPIframeElement-BbBz_xpV.js";e();export{t as MCPIframeElement};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#createIframe","#cleanup","#targetOrigin","#ready","#reconnect","#channelId","#callTimeout","#prefixSeparator","#unregisterAll","#registerAllOnModelContext","#iframe","#client","#registeredTools","#registeredResources","#registeredPrompts","#mcpTools","#mcpResources","#mcpPrompts","#fetchAllFromIframe","#connect","#connecting","#getTargetOrigin","#transport","#registerToolsOnModelContext","#registerResourcesOnModelContext","#registerPromptsOnModelContext","#callIframeTool","#readIframeResource","#getIframePrompt","#disconnect"],"sources":["../src/MCPIframeElement.ts"],"sourcesContent":["/**\n * MCPIframe Custom Element\n *\n * A custom element that wraps an iframe and automatically exposes tools,\n * resources, and prompts registered in the iframe's MCP server to the\n * parent page's Model Context API.\n *\n * The iframe should have the MCP polyfill installed, which creates an MCP server\n * that exposes items registered via `navigator.modelContext`.\n *\n * @example\n * ```html\n * <mcp-iframe src=\"./child-app.html\" id=\"my-app\"></mcp-iframe>\n * ```\n *\n * Items from the iframe will be exposed with the element's ID as prefix:\n * - Child registers tool \"calculate\" -> Parent sees \"my-app_calculate\"\n * - Child registers resource \"config://settings\" -> Parent sees \"my-app_config://settings\"\n * - Child registers prompt \"help\" -> Parent sees \"my-app_help\"\n *\n * Note: The prefix separator defaults to underscore (_) to ensure MCP compatibility.\n * Tool and prompt names must match the pattern: ^[a-zA-Z0-9_-]{1,128}$\n *\n * @example\n * ```typescript\n * const mcpIframe = document.querySelector('mcp-iframe');\n * mcpIframe.addEventListener('mcp-iframe-ready', (e) => {\n * console.log('Tools:', e.detail.tools);\n * console.log('Resources:', e.detail.resources);\n * console.log('Prompts:', e.detail.prompts);\n * });\n * ```\n */\n\nimport { IframeParentTransport } from '@mcp-b/transports';\nimport type {\n GetPromptResult,\n Prompt,\n ReadResourceResult,\n Resource,\n Tool,\n} from '@mcp-b/webmcp-ts-sdk';\nimport {\n type BrowserMcpServer,\n Client,\n type PromptDescriptor,\n type ResourceDescriptor,\n} from '@mcp-b/webmcp-ts-sdk';\nimport type {\n CallToolResult,\n InputSchema,\n RegistrationHandle,\n ToolDescriptor,\n} from '@mcp-b/webmcp-types';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CALL_TIMEOUT = 30000;\nconst DEFAULT_PREFIX_SEPARATOR = '_';\nconst DEFAULT_CHANNEL_ID = 'mcp-iframe';\n\n/**\n * MCP name validation pattern.\n * Tool/prompt names must match: ^[a-zA-Z0-9_-]{1,128}$\n */\nconst MCP_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/;\nconst MCP_NAME_MAX_LENGTH = 128;\n\n/**\n * Validates that a string contains only valid MCP name characters.\n * Valid characters: a-z, A-Z, 0-9, underscore (_), hyphen (-)\n */\nfunction isValidMCPNameChars(str: string): boolean {\n return /^[a-zA-Z0-9_-]*$/.test(str);\n}\n\n/**\n * Validates a complete MCP name (tool name, prompt name).\n * Must be 1-128 characters and contain only valid characters.\n */\nfunction isValidMCPName(name: string): boolean {\n return MCP_NAME_PATTERN.test(name);\n}\n\n/**\n * Sanitizes a string to contain only valid MCP name characters.\n * Replaces invalid characters with underscores.\n */\nfunction sanitizeMCPNamePart(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, '_');\n}\n\n/** Standard iframe attributes that are mirrored to the internal iframe */\nconst IFRAME_ATTRIBUTES = [\n 'src',\n 'srcdoc',\n 'name',\n 'sandbox',\n 'allow',\n 'allowfullscreen',\n 'width',\n 'height',\n 'loading',\n 'referrerpolicy',\n 'credentialless',\n] as const;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Custom event detail for mcp-iframe-ready */\nexport interface MCPIframeReadyEventDetail {\n tools: string[];\n resources: string[];\n prompts: string[];\n}\n\n/** Custom event detail for mcp-iframe-error */\nexport interface MCPIframeErrorEventDetail {\n error: unknown;\n}\n\n/** Custom event detail for mcp-iframe-tools-changed */\nexport interface MCPIframeToolsChangedEventDetail {\n tools: string[];\n resources: string[];\n prompts: string[];\n}\n\n// ============================================================================\n// MCPIframeElement\n// ============================================================================\n\n/**\n * MCPIframe Custom Element\n *\n * Wraps an iframe and exposes its MCP tools, resources, and prompts\n * to the parent's Model Context API.\n *\n * @fires mcp-iframe-ready - When connected to iframe's MCP server\n * @fires mcp-iframe-error - When connection fails\n * @fires mcp-iframe-tools-changed - When items are refreshed\n */\nexport class MCPIframeElement extends HTMLElement {\n // Internal state\n #iframe: HTMLIFrameElement | null = null;\n #client: Client | null = null;\n #transport: IframeParentTransport | null = null;\n #ready = false;\n #connecting = false;\n\n // MCP items from iframe\n #mcpTools: Tool[] = [];\n #mcpResources: Resource[] = [];\n #mcpPrompts: Prompt[] = [];\n\n // Registered items on parent\n #registeredTools = new Set<string>();\n #registeredResources = new Map<string, RegistrationHandle>();\n #registeredPrompts = new Map<string, RegistrationHandle>();\n\n // Configuration\n #callTimeout = DEFAULT_CALL_TIMEOUT;\n #prefixSeparator = DEFAULT_PREFIX_SEPARATOR;\n #channelId = DEFAULT_CHANNEL_ID;\n #targetOrigin: string | null = null;\n\n static get observedAttributes(): string[] {\n return [...IFRAME_ATTRIBUTES, 'target-origin', 'channel', 'call-timeout', 'prefix-separator'];\n }\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n // ==================== Lifecycle ====================\n\n connectedCallback(): void {\n this.#createIframe();\n }\n\n disconnectedCallback(): void {\n void this.#cleanup();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'target-origin':\n this.#targetOrigin = newValue;\n if (this.#ready) void this.#reconnect();\n break;\n\n case 'channel':\n this.#channelId = newValue ?? DEFAULT_CHANNEL_ID;\n if (this.#ready) void this.#reconnect();\n break;\n\n case 'call-timeout':\n this.#callTimeout = newValue ? Number.parseInt(newValue, 10) : DEFAULT_CALL_TIMEOUT;\n break;\n\n case 'prefix-separator': {\n const separator = newValue ?? DEFAULT_PREFIX_SEPARATOR;\n if (!isValidMCPNameChars(separator)) {\n console.warn(\n `[MCPIframe] Invalid prefix-separator \"${separator}\". ` +\n 'Only alphanumeric characters, underscores, and hyphens are allowed. ' +\n `Using sanitized value: \"${sanitizeMCPNamePart(separator)}\"`\n );\n this.#prefixSeparator = sanitizeMCPNamePart(separator);\n } else {\n this.#prefixSeparator = separator;\n }\n if (this.#ready) {\n this.#unregisterAll();\n this.#registerAllOnModelContext();\n }\n break;\n }\n\n default:\n // Mirror standard iframe attributes\n if (this.#iframe && (IFRAME_ATTRIBUTES as readonly string[]).includes(name)) {\n if (newValue === null) {\n this.#iframe.removeAttribute(name);\n } else {\n this.#iframe.setAttribute(name, newValue);\n }\n // Reconnect when source changes\n if (name === 'src' || name === 'srcdoc') {\n void this.#reconnect();\n }\n }\n }\n }\n\n // ==================== Public API ====================\n\n /** The wrapped iframe element */\n get iframe(): HTMLIFrameElement | null {\n return this.#iframe;\n }\n\n /** The MCP client (if connected) */\n get client(): Client | null {\n return this.#client;\n }\n\n /** Whether the element is connected to the iframe's MCP server */\n get ready(): boolean {\n return this.#ready;\n }\n\n /** List of exposed tool names (with prefix) */\n get exposedTools(): string[] {\n return Array.from(this.#registeredTools.values());\n }\n\n /** List of exposed resource URIs (with prefix) */\n get exposedResources(): string[] {\n return Array.from(this.#registeredResources.keys());\n }\n\n /** List of exposed prompt names (with prefix) */\n get exposedPrompts(): string[] {\n return Array.from(this.#registeredPrompts.keys());\n }\n\n /** Raw tools from the iframe's MCP server (without prefix) */\n get mcpTools(): Tool[] {\n return [...this.#mcpTools];\n }\n\n /** Raw resources from the iframe's MCP server (without prefix) */\n get mcpResources(): Resource[] {\n return [...this.#mcpResources];\n }\n\n /** Raw prompts from the iframe's MCP server (without prefix) */\n get mcpPrompts(): Prompt[] {\n return [...this.#mcpPrompts];\n }\n\n /** The item name prefix (id + separator), sanitized for MCP compatibility */\n get itemPrefix(): string {\n const rawId = this.id || this.getAttribute('name') || 'iframe';\n const id = sanitizeMCPNamePart(rawId);\n if (id !== rawId) {\n console.warn(\n `[MCPIframe] ID/name \"${rawId}\" contains invalid characters for MCP names. ` +\n `Using sanitized value: \"${id}\"`\n );\n }\n return `${id}${this.#prefixSeparator}`;\n }\n\n /** @deprecated Use itemPrefix instead */\n get toolPrefix(): string {\n return this.itemPrefix;\n }\n\n /** Manually refresh all items from the iframe */\n async refresh(): Promise<void> {\n if (!this.#client || !this.#ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n\n await this.#fetchAllFromIframe();\n this.#unregisterAll();\n this.#registerAllOnModelContext();\n\n this.dispatchEvent(\n new CustomEvent<MCPIframeToolsChangedEventDetail>('mcp-iframe-tools-changed', {\n detail: {\n tools: this.exposedTools,\n resources: this.exposedResources,\n prompts: this.exposedPrompts,\n },\n })\n );\n }\n\n /** @deprecated Use refresh() instead */\n async refreshTools(): Promise<void> {\n return this.refresh();\n }\n\n // ==================== Private Methods ====================\n\n #createIframe(): void {\n this.#iframe = document.createElement('iframe');\n\n // Mirror all iframe attributes\n for (const attr of IFRAME_ATTRIBUTES) {\n const value = this.getAttribute(attr);\n if (value !== null) {\n this.#iframe.setAttribute(attr, value);\n }\n }\n\n // Default styling\n this.#iframe.style.border = 'none';\n this.#iframe.style.width = this.getAttribute('width') ?? '100%';\n this.#iframe.style.height = this.getAttribute('height') ?? '100%';\n\n // Connect when iframe loads\n this.#iframe.addEventListener('load', () => void this.#connect());\n\n this.shadowRoot?.appendChild(this.#iframe);\n }\n\n async #connect(): Promise<void> {\n if (this.#connecting || !this.#iframe) return;\n this.#connecting = true;\n\n try {\n const targetOrigin = this.#getTargetOrigin();\n if (!targetOrigin) {\n console.warn('[MCPIframe] Cannot determine target origin. Set target-origin attribute.');\n return;\n }\n\n // Create transport and client\n this.#transport = new IframeParentTransport({\n iframe: this.#iframe,\n targetOrigin,\n channelId: this.#channelId,\n });\n\n this.#client = new Client({\n name: `MCPIframe:${this.id || 'anonymous'}`,\n version: '1.0.0',\n });\n\n // Connect to iframe's MCP server\n await this.#client.connect(this.#transport);\n this.#ready = true;\n\n // Fetch all items from iframe\n await this.#fetchAllFromIframe();\n\n // Register on parent's Model Context\n this.#registerAllOnModelContext();\n\n this.dispatchEvent(\n new CustomEvent<MCPIframeReadyEventDetail>('mcp-iframe-ready', {\n detail: {\n tools: this.exposedTools,\n resources: this.exposedResources,\n prompts: this.exposedPrompts,\n },\n })\n );\n } catch (error) {\n console.error('[MCPIframe] Failed to connect:', error);\n this.dispatchEvent(\n new CustomEvent<MCPIframeErrorEventDetail>('mcp-iframe-error', {\n detail: { error },\n })\n );\n } finally {\n this.#connecting = false;\n }\n }\n\n async #fetchAllFromIframe(): Promise<void> {\n if (!this.#client) return;\n\n // Fetch tools, resources, and prompts in parallel\n const [toolsResult, resourcesResult, promptsResult] = await Promise.all([\n this.#client.listTools(),\n this.#client.listResources().catch((err) => {\n console.warn('[MCPIframeElement] listResources failed, defaulting to empty:', err);\n return { resources: [] };\n }),\n this.#client.listPrompts().catch((err) => {\n console.warn('[MCPIframeElement] listPrompts failed, defaulting to empty:', err);\n return { prompts: [] };\n }),\n ]);\n\n this.#mcpTools = toolsResult.tools;\n this.#mcpResources = resourcesResult.resources;\n this.#mcpPrompts = promptsResult.prompts;\n }\n\n #getTargetOrigin(): string | null {\n // Use explicit attribute if set\n if (this.#targetOrigin) {\n return this.#targetOrigin;\n }\n\n // Infer from src attribute\n const src = this.getAttribute('src');\n if (src) {\n try {\n return new URL(src, window.location.href).origin;\n } catch (error) {\n console.warn('[MCPIframeElement] Invalid src URL for origin detection:', src, error);\n }\n }\n\n // Default to same origin\n return window.location.origin;\n }\n\n #registerAllOnModelContext(): void {\n const mc = navigator.modelContext;\n if (!mc) {\n console.warn('[MCPIframe] Model Context API not available on parent');\n return;\n }\n const modelContext = mc as unknown as BrowserMcpServer;\n\n this.#registerToolsOnModelContext(modelContext);\n this.#registerResourcesOnModelContext(modelContext);\n this.#registerPromptsOnModelContext(modelContext);\n }\n\n #registerToolsOnModelContext(modelContext: BrowserMcpServer): void {\n for (const tool of this.#mcpTools) {\n const prefixedName = `${this.itemPrefix}${tool.name}`;\n\n // Validate the final tool name\n if (!isValidMCPName(prefixedName)) {\n if (prefixedName.length > MCP_NAME_MAX_LENGTH) {\n console.error(\n `[MCPIframe] Cannot register tool \"${tool.name}\": ` +\n `prefixed name \"${prefixedName}\" exceeds ${MCP_NAME_MAX_LENGTH} characters ` +\n `(${prefixedName.length} chars). Skipping registration.`\n );\n } else {\n console.error(\n `[MCPIframe] Cannot register tool \"${tool.name}\": ` +\n `prefixed name \"${prefixedName}\" contains invalid characters. ` +\n 'Tool names must match pattern: ^[a-zA-Z0-9_-]{1,128}$. Skipping registration.'\n );\n }\n continue;\n }\n\n (modelContext.registerTool as (tool: ToolDescriptor) => void)({\n name: prefixedName,\n description: tool.description ?? `Tool from iframe: ${tool.name}`,\n ...(tool.inputSchema && { inputSchema: tool.inputSchema as InputSchema }),\n execute: (args) => this.#callIframeTool(tool.name, args),\n });\n\n this.#registeredTools.add(prefixedName);\n }\n }\n\n #registerResourcesOnModelContext(modelContext: BrowserMcpServer): void {\n for (const resource of this.#mcpResources) {\n const prefixedUri = `${this.itemPrefix}${resource.uri}`;\n\n const descriptor: ResourceDescriptor = {\n uri: prefixedUri,\n name: resource.name,\n ...(resource.description !== undefined && { description: resource.description }),\n ...(resource.mimeType !== undefined && { mimeType: resource.mimeType }),\n read: (_uri, _params) =>\n this.#readIframeResource(resource.uri) as ReturnType<ResourceDescriptor['read']>,\n };\n const registration = modelContext.registerResource(descriptor);\n this.#registeredResources.set(prefixedUri, registration);\n }\n }\n\n #registerPromptsOnModelContext(modelContext: BrowserMcpServer): void {\n for (const prompt of this.#mcpPrompts) {\n const prefixedName = `${this.itemPrefix}${prompt.name}`;\n\n // Validate the final prompt name\n if (!isValidMCPName(prefixedName)) {\n if (prefixedName.length > MCP_NAME_MAX_LENGTH) {\n console.error(\n `[MCPIframe] Cannot register prompt \"${prompt.name}\": ` +\n `prefixed name \"${prefixedName}\" exceeds ${MCP_NAME_MAX_LENGTH} characters ` +\n `(${prefixedName.length} chars). Skipping registration.`\n );\n } else {\n console.error(\n `[MCPIframe] Cannot register prompt \"${prompt.name}\": ` +\n `prefixed name \"${prefixedName}\" contains invalid characters. ` +\n 'Prompt names must match pattern: ^[a-zA-Z0-9_-]{1,128}$. Skipping registration.'\n );\n }\n continue;\n }\n\n const descriptor: PromptDescriptor = {\n name: prefixedName,\n ...(prompt.description !== undefined && { description: prompt.description }),\n ...(prompt.arguments &&\n prompt.arguments.length > 0 && {\n argsSchema: {\n type: 'object',\n properties: Object.fromEntries(\n prompt.arguments.map((arg) => [\n arg.name,\n {\n type: 'string',\n ...(arg.description !== undefined && { description: arg.description }),\n },\n ])\n ),\n required: prompt.arguments.filter((a) => a.required).map((a) => a.name),\n } satisfies InputSchema,\n }),\n get: (args) => this.#getIframePrompt(prompt.name, args),\n };\n const registration = modelContext.registerPrompt(descriptor);\n this.#registeredPrompts.set(prefixedName, registration);\n }\n }\n\n #unregisterAll(): void {\n const mc = navigator.modelContext;\n if (mc) {\n const modelContext = mc as unknown as BrowserMcpServer;\n for (const toolName of this.#registeredTools.values()) {\n try {\n modelContext.unregisterTool(toolName);\n } catch (error) {\n console.warn(\n `[MCPIframeElement] Failed to unregister tool \"${toolName}\" during cleanup:`,\n error\n );\n }\n }\n }\n this.#registeredTools.clear();\n\n for (const registration of this.#registeredResources.values()) {\n registration.unregister();\n }\n this.#registeredResources.clear();\n\n for (const registration of this.#registeredPrompts.values()) {\n registration.unregister();\n }\n this.#registeredPrompts.clear();\n }\n\n async #callIframeTool(toolName: string, args: Record<string, unknown>): Promise<CallToolResult> {\n if (!this.#client || !this.#ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.#callTimeout);\n\n try {\n const result = await this.#client.callTool({ name: toolName, arguments: args }, undefined, {\n signal: controller.signal,\n });\n return result as CallToolResult;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async #readIframeResource(uri: string): Promise<ReadResourceResult> {\n if (!this.#client || !this.#ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.#callTimeout);\n\n try {\n const result = await this.#client.readResource({ uri }, { signal: controller.signal });\n return result as ReadResourceResult;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async #getIframePrompt(name: string, args: Record<string, unknown>): Promise<GetPromptResult> {\n if (!this.#client || !this.#ready) {\n throw new Error('Not connected to iframe MCP server');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.#callTimeout);\n\n try {\n const result = await this.#client.getPrompt(\n { name, arguments: args as Record<string, string> },\n { signal: controller.signal }\n );\n return result as GetPromptResult;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async #reconnect(): Promise<void> {\n await this.#disconnect();\n // Brief delay for iframe to be ready\n await new Promise((resolve) => setTimeout(resolve, 100));\n await this.#connect();\n }\n\n async #disconnect(): Promise<void> {\n this.#ready = false;\n this.#mcpTools = [];\n this.#mcpResources = [];\n this.#mcpPrompts = [];\n this.#unregisterAll();\n\n if (this.#client) {\n try {\n await this.#client.close();\n } catch (error) {\n console.warn('[MCPIframeElement] Error closing client during disconnect:', error);\n }\n this.#client = null;\n }\n\n if (this.#transport) {\n try {\n await this.#transport.close();\n } catch (error) {\n console.warn('[MCPIframeElement] Error closing transport during disconnect:', error);\n }\n this.#transport = null;\n }\n }\n\n async #cleanup(): Promise<void> {\n await this.#disconnect();\n }\n}\n\n// ============================================================================\n// Registration\n// ============================================================================\n\n/** Register the custom element with a custom tag name */\nexport function registerMCPIframeElement(tagName = 'mcp-iframe'): void {\n if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {\n customElements.define(tagName, MCPIframeElement);\n }\n}\n\n// Auto-register in browser environments\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n registerMCPIframeElement();\n}\n"],"mappings":"wGA2DA,MAAM,EAAuB,IAEvB,EAAqB,aAMrB,EAAmB,yBAOzB,SAAS,EAAoB,EAAsB,CACjD,MAAO,mBAAmB,KAAK,EAAI,CAOrC,SAAS,EAAe,EAAuB,CAC7C,OAAO,EAAiB,KAAK,EAAK,CAOpC,SAAS,EAAoB,EAAqB,CAChD,OAAO,EAAI,QAAQ,kBAAmB,IAAI,CAI5C,MAAM,EAAoB,CACxB,MACA,SACA,OACA,UACA,QACA,kBACA,QACA,SACA,UACA,iBACA,iBACD,CAuCD,IAAa,EAAb,cAAsC,WAAY,CAEhD,GAAoC,KACpC,GAAyB,KACzB,GAA2C,KAC3C,GAAS,GACT,GAAc,GAGd,GAAoB,EAAE,CACtB,GAA4B,EAAE,CAC9B,GAAwB,EAAE,CAG1B,GAAmB,IAAI,IACvB,GAAuB,IAAI,IAC3B,GAAqB,IAAI,IAGzB,GAAe,EACf,GAAmB,IACnB,GAAa,EACb,GAA+B,KAE/B,WAAW,oBAA+B,CACxC,MAAO,CAAC,GAAG,EAAmB,gBAAiB,UAAW,eAAgB,mBAAmB,CAG/F,aAAc,CACZ,OAAO,CACP,KAAK,aAAa,CAAE,KAAM,OAAQ,CAAC,CAKrC,mBAA0B,CACxB,KAAKA,IAAe,CAGtB,sBAA6B,CAC3B,KAAUC,IAAU,CAGtB,yBAAyB,EAAc,EAAyB,EAA+B,CACzF,OAAa,EAEjB,OAAQ,EAAR,CACE,IAAK,gBACH,KAAKC,GAAgB,EACjB,KAAKC,IAAQ,KAAUC,IAAY,CACvC,MAEF,IAAK,UACH,KAAKC,GAAa,GAAY,EAC1B,KAAKF,IAAQ,KAAUC,IAAY,CACvC,MAEF,IAAK,eACH,KAAKE,GAAe,EAAW,OAAO,SAAS,EAAU,GAAG,CAAG,EAC/D,MAEF,IAAK,mBAAoB,CACvB,IAAM,EAAY,GAAY,IACzB,EAAoB,EAAU,CAQjC,KAAKC,GAAmB,GAPxB,QAAQ,KACN,yCAAyC,EAAU,iGAEtB,EAAoB,EAAU,CAAC,GAC7D,CACD,KAAKA,GAAmB,EAAoB,EAAU,EAIpD,KAAKJ,KACP,KAAKK,IAAgB,CACrB,KAAKC,IAA4B,EAEnC,MAGF,QAEM,KAAKC,IAAY,EAAwC,SAAS,EAAK,GACrE,IAAa,KACf,KAAKA,GAAQ,gBAAgB,EAAK,CAElC,KAAKA,GAAQ,aAAa,EAAM,EAAS,EAGvC,IAAS,OAAS,IAAS,WAC7B,KAAUN,IAAY,GAShC,IAAI,QAAmC,CACrC,OAAO,KAAKM,GAId,IAAI,QAAwB,CAC1B,OAAO,KAAKC,GAId,IAAI,OAAiB,CACnB,OAAO,KAAKR,GAId,IAAI,cAAyB,CAC3B,OAAO,MAAM,KAAK,KAAKS,GAAiB,QAAQ,CAAC,CAInD,IAAI,kBAA6B,CAC/B,OAAO,MAAM,KAAK,KAAKC,GAAqB,MAAM,CAAC,CAIrD,IAAI,gBAA2B,CAC7B,OAAO,MAAM,KAAK,KAAKC,GAAmB,MAAM,CAAC,CAInD,IAAI,UAAmB,CACrB,MAAO,CAAC,GAAG,KAAKC,GAAU,CAI5B,IAAI,cAA2B,CAC7B,MAAO,CAAC,GAAG,KAAKC,GAAc,CAIhC,IAAI,YAAuB,CACzB,MAAO,CAAC,GAAG,KAAKC,GAAY,CAI9B,IAAI,YAAqB,CACvB,IAAM,EAAQ,KAAK,IAAM,KAAK,aAAa,OAAO,EAAI,SAChD,EAAK,EAAoB,EAAM,CAOrC,OANI,IAAO,GACT,QAAQ,KACN,wBAAwB,EAAM,uEACD,EAAG,GACjC,CAEI,GAAG,IAAK,KAAKV,KAItB,IAAI,YAAqB,CACvB,OAAO,KAAK,WAId,MAAM,SAAyB,CAC7B,GAAI,CAAC,KAAKI,IAAW,CAAC,KAAKR,GACzB,MAAU,MAAM,qCAAqC,CAGvD,MAAM,KAAKe,IAAqB,CAChC,KAAKV,IAAgB,CACrB,KAAKC,IAA4B,CAEjC,KAAK,cACH,IAAI,YAA8C,2BAA4B,CAC5E,OAAQ,CACN,MAAO,KAAK,aACZ,UAAW,KAAK,iBAChB,QAAS,KAAK,eACf,CACF,CAAC,CACH,CAIH,MAAM,cAA8B,CAClC,OAAO,KAAK,SAAS,CAKvB,IAAsB,CACpB,KAAKC,GAAU,SAAS,cAAc,SAAS,CAG/C,IAAK,IAAM,KAAQ,EAAmB,CACpC,IAAM,EAAQ,KAAK,aAAa,EAAK,CACjC,IAAU,MACZ,KAAKA,GAAQ,aAAa,EAAM,EAAM,CAK1C,KAAKA,GAAQ,MAAM,OAAS,OAC5B,KAAKA,GAAQ,MAAM,MAAQ,KAAK,aAAa,QAAQ,EAAI,OACzD,KAAKA,GAAQ,MAAM,OAAS,KAAK,aAAa,SAAS,EAAI,OAG3D,KAAKA,GAAQ,iBAAiB,WAAc,KAAK,KAAKS,IAAU,CAAC,CAEjE,KAAK,YAAY,YAAY,KAAKT,GAAQ,CAG5C,KAAMS,IAA0B,CAC1B,UAAKC,IAAe,CAAC,KAAKV,IAC9B,MAAKU,GAAc,GAEnB,GAAI,CACF,IAAM,EAAe,KAAKC,IAAkB,CAC5C,GAAI,CAAC,EAAc,CACjB,QAAQ,KAAK,2EAA2E,CACxF,OAIF,KAAKC,GAAa,IAAI,EAAsB,CAC1C,OAAQ,KAAKZ,GACb,eACA,UAAW,KAAKL,GACjB,CAAC,CAEF,KAAKM,GAAU,IAAI,EAAO,CACxB,KAAM,aAAa,KAAK,IAAM,cAC9B,QAAS,QACV,CAAC,CAGF,MAAM,KAAKA,GAAQ,QAAQ,KAAKW,GAAW,CAC3C,KAAKnB,GAAS,GAGd,MAAM,KAAKe,IAAqB,CAGhC,KAAKT,IAA4B,CAEjC,KAAK,cACH,IAAI,YAAuC,mBAAoB,CAC7D,OAAQ,CACN,MAAO,KAAK,aACZ,UAAW,KAAK,iBAChB,QAAS,KAAK,eACf,CACF,CAAC,CACH,OACM,EAAO,CACd,QAAQ,MAAM,iCAAkC,EAAM,CACtD,KAAK,cACH,IAAI,YAAuC,mBAAoB,CAC7D,OAAQ,CAAE,QAAO,CAClB,CAAC,CACH,QACO,CACR,KAAKW,GAAc,KAIvB,KAAMF,IAAqC,CACzC,GAAI,CAAC,KAAKP,GAAS,OAGnB,GAAM,CAAC,EAAa,EAAiB,GAAiB,MAAM,QAAQ,IAAI,CACtE,KAAKA,GAAQ,WAAW,CACxB,KAAKA,GAAQ,eAAe,CAAC,MAAO,IAClC,QAAQ,KAAK,gEAAiE,EAAI,CAC3E,CAAE,UAAW,EAAE,CAAE,EACxB,CACF,KAAKA,GAAQ,aAAa,CAAC,MAAO,IAChC,QAAQ,KAAK,8DAA+D,EAAI,CACzE,CAAE,QAAS,EAAE,CAAE,EACtB,CACH,CAAC,CAEF,KAAKI,GAAY,EAAY,MAC7B,KAAKC,GAAgB,EAAgB,UACrC,KAAKC,GAAc,EAAc,QAGnC,IAAkC,CAEhC,GAAI,KAAKf,GACP,OAAO,KAAKA,GAId,IAAM,EAAM,KAAK,aAAa,MAAM,CACpC,GAAI,EACF,GAAI,CACF,OAAO,IAAI,IAAI,EAAK,OAAO,SAAS,KAAK,CAAC,aACnC,EAAO,CACd,QAAQ,KAAK,2DAA4D,EAAK,EAAM,CAKxF,OAAO,OAAO,SAAS,OAGzB,IAAmC,CACjC,IAAM,EAAK,UAAU,aACrB,GAAI,CAAC,EAAI,CACP,QAAQ,KAAK,wDAAwD,CACrE,OAEF,IAAM,EAAe,EAErB,KAAKqB,GAA6B,EAAa,CAC/C,KAAKC,GAAiC,EAAa,CACnD,KAAKC,GAA+B,EAAa,CAGnD,GAA6B,EAAsC,CACjE,IAAK,IAAM,KAAQ,KAAKV,GAAW,CACjC,IAAM,EAAe,GAAG,KAAK,aAAa,EAAK,OAG/C,GAAI,CAAC,EAAe,EAAa,CAAE,CAC7B,EAAa,OAAS,IACxB,QAAQ,MACN,qCAAqC,EAAK,KAAK,oBAC3B,EAAa,4BAC3B,EAAa,OAAO,iCAC3B,CAED,QAAQ,MACN,qCAAqC,EAAK,KAAK,oBAC3B,EAAa,+GAElC,CAEH,SAGF,EAAc,aAAgD,CAC5D,KAAM,EACN,YAAa,EAAK,aAAe,qBAAqB,EAAK,OAC3D,GAAI,EAAK,aAAe,CAAE,YAAa,EAAK,YAA4B,CACxE,QAAU,GAAS,KAAKW,GAAgB,EAAK,KAAM,EAAK,CACzD,CAAC,CAEF,KAAKd,GAAiB,IAAI,EAAa,EAI3C,GAAiC,EAAsC,CACrE,IAAK,IAAM,KAAY,KAAKI,GAAe,CACzC,IAAM,EAAc,GAAG,KAAK,aAAa,EAAS,MAE5C,EAAiC,CACrC,IAAK,EACL,KAAM,EAAS,KACf,GAAI,EAAS,cAAgB,IAAA,IAAa,CAAE,YAAa,EAAS,YAAa,CAC/E,GAAI,EAAS,WAAa,IAAA,IAAa,CAAE,SAAU,EAAS,SAAU,CACtE,MAAO,EAAM,IACX,KAAKW,GAAoB,EAAS,IAAI,CACzC,CACK,EAAe,EAAa,iBAAiB,EAAW,CAC9D,KAAKd,GAAqB,IAAI,EAAa,EAAa,EAI5D,GAA+B,EAAsC,CACnE,IAAK,IAAM,KAAU,KAAKI,GAAa,CACrC,IAAM,EAAe,GAAG,KAAK,aAAa,EAAO,OAGjD,GAAI,CAAC,EAAe,EAAa,CAAE,CAC7B,EAAa,OAAS,IACxB,QAAQ,MACN,uCAAuC,EAAO,KAAK,oBAC/B,EAAa,4BAC3B,EAAa,OAAO,iCAC3B,CAED,QAAQ,MACN,uCAAuC,EAAO,KAAK,oBAC/B,EAAa,iHAElC,CAEH,SAGF,IAAM,EAA+B,CACnC,KAAM,EACN,GAAI,EAAO,cAAgB,IAAA,IAAa,CAAE,YAAa,EAAO,YAAa,CAC3E,GAAI,EAAO,WACT,EAAO,UAAU,OAAS,GAAK,CAC7B,WAAY,CACV,KAAM,SACN,WAAY,OAAO,YACjB,EAAO,UAAU,IAAK,GAAQ,CAC5B,EAAI,KACJ,CACE,KAAM,SACN,GAAI,EAAI,cAAgB,IAAA,IAAa,CAAE,YAAa,EAAI,YAAa,CACtE,CACF,CAAC,CACH,CACD,SAAU,EAAO,UAAU,OAAQ,GAAM,EAAE,SAAS,CAAC,IAAK,GAAM,EAAE,KAAK,CACxE,CACF,CACH,IAAM,GAAS,KAAKW,GAAiB,EAAO,KAAM,EAAK,CACxD,CACK,EAAe,EAAa,eAAe,EAAW,CAC5D,KAAKd,GAAmB,IAAI,EAAc,EAAa,EAI3D,IAAuB,CACrB,IAAM,EAAK,UAAU,aACrB,GAAI,EAAI,CACN,IAAM,EAAe,EACrB,IAAK,IAAM,KAAY,KAAKF,GAAiB,QAAQ,CACnD,GAAI,CACF,EAAa,eAAe,EAAS,OAC9B,EAAO,CACd,QAAQ,KACN,iDAAiD,EAAS,mBAC1D,EACD,EAIP,KAAKA,GAAiB,OAAO,CAE7B,IAAK,IAAM,KAAgB,KAAKC,GAAqB,QAAQ,CAC3D,EAAa,YAAY,CAE3B,KAAKA,GAAqB,OAAO,CAEjC,IAAK,IAAM,KAAgB,KAAKC,GAAmB,QAAQ,CACzD,EAAa,YAAY,CAE3B,KAAKA,GAAmB,OAAO,CAGjC,KAAMY,GAAgB,EAAkB,EAAwD,CAC9F,GAAI,CAAC,KAAKf,IAAW,CAAC,KAAKR,GACzB,MAAU,MAAM,qCAAqC,CAGvD,IAAM,EAAa,IAAI,gBACjB,EAAY,eAAiB,EAAW,OAAO,CAAE,KAAKG,GAAa,CAEzE,GAAI,CAIF,OAAO,MAHc,KAAKK,GAAQ,SAAS,CAAE,KAAM,EAAU,UAAW,EAAM,CAAE,IAAA,GAAW,CACzF,OAAQ,EAAW,OACpB,CAAC,QAEM,CACR,aAAa,EAAU,EAI3B,KAAMgB,GAAoB,EAA0C,CAClE,GAAI,CAAC,KAAKhB,IAAW,CAAC,KAAKR,GACzB,MAAU,MAAM,qCAAqC,CAGvD,IAAM,EAAa,IAAI,gBACjB,EAAY,eAAiB,EAAW,OAAO,CAAE,KAAKG,GAAa,CAEzE,GAAI,CAEF,OAAO,MADc,KAAKK,GAAQ,aAAa,CAAE,MAAK,CAAE,CAAE,OAAQ,EAAW,OAAQ,CAAC,QAE9E,CACR,aAAa,EAAU,EAI3B,KAAMiB,GAAiB,EAAc,EAAyD,CAC5F,GAAI,CAAC,KAAKjB,IAAW,CAAC,KAAKR,GACzB,MAAU,MAAM,qCAAqC,CAGvD,IAAM,EAAa,IAAI,gBACjB,EAAY,eAAiB,EAAW,OAAO,CAAE,KAAKG,GAAa,CAEzE,GAAI,CAKF,OAAO,MAJc,KAAKK,GAAQ,UAChC,CAAE,OAAM,UAAW,EAAgC,CACnD,CAAE,OAAQ,EAAW,OAAQ,CAC9B,QAEO,CACR,aAAa,EAAU,EAI3B,KAAMP,IAA4B,CAChC,MAAM,KAAKyB,IAAa,CAExB,MAAM,IAAI,QAAS,GAAY,WAAW,EAAS,IAAI,CAAC,CACxD,MAAM,KAAKV,IAAU,CAGvB,KAAMU,IAA6B,CAOjC,GANA,KAAK1B,GAAS,GACd,KAAKY,GAAY,EAAE,CACnB,KAAKC,GAAgB,EAAE,CACvB,KAAKC,GAAc,EAAE,CACrB,KAAKT,IAAgB,CAEjB,KAAKG,GAAS,CAChB,GAAI,CACF,MAAM,KAAKA,GAAQ,OAAO,OACnB,EAAO,CACd,QAAQ,KAAK,6DAA8D,EAAM,CAEnF,KAAKA,GAAU,KAGjB,GAAI,KAAKW,GAAY,CACnB,GAAI,CACF,MAAM,KAAKA,GAAW,OAAO,OACtB,EAAO,CACd,QAAQ,KAAK,gEAAiE,EAAM,CAEtF,KAAKA,GAAa,MAItB,KAAMrB,IAA0B,CAC9B,MAAM,KAAK4B,IAAa,GAS5B,SAAgB,EAAyB,EAAU,aAAoB,CACjE,OAAO,eAAmB,KAAe,CAAC,eAAe,IAAI,EAAQ,EACvE,eAAe,OAAO,EAAS,EAAiB,CAKhD,OAAO,OAAW,KAAe,OAAO,eAAmB,KAC7D,GAA0B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @mcp-b/mcp-iframe\n *\n * Custom element for exposing iframe MCP tools, resources, and prompts\n * to the parent page's Model Context API.\n *\n * @example\n * ```typescript\n * // Import to auto-register the <mcp-iframe> element\n * import '@mcp-b/mcp-iframe';\n *\n * // Or use the side-effect-free entry for a custom tag\n * import { registerMCPIframeElement } from '@mcp-b/mcp-iframe/element';\n * registerMCPIframeElement('custom-mcp-iframe');\n * ```\n *\n * @example\n * ```html\n * <mcp-iframe src=\"./child-app.html\" id=\"my-app\"></mcp-iframe>\n * ```\n *\n * @packageDocumentation\n */\n\nimport { registerMCPIframeElement } from './MCPIframeElement.js';\n\nexport {\n MCPIframeElement,\n type MCPIframeErrorEventDetail,\n type MCPIframeEventMap,\n type MCPIframeItemsEventDetail,\n} from './MCPIframeElement.js';\n\nregisterMCPIframeElement();\n"],"mappings":"qDAiCA,GAA0B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/mcp-iframe",
3
- "version": "4.1.0-beta.20260702224704",
3
+ "version": "5.0.1",
4
4
  "description": "Custom element for exposing iframe MCP tools, resources, and prompts to parent page",
5
5
  "keywords": [
6
6
  "browser",
@@ -26,7 +26,9 @@
26
26
  "dist"
27
27
  ],
28
28
  "type": "module",
29
- "sideEffects": true,
29
+ "sideEffects": [
30
+ "./dist/index.js"
31
+ ],
30
32
  "main": "./dist/index.js",
31
33
  "module": "./dist/index.js",
32
34
  "types": "./dist/index.d.ts",
@@ -35,6 +37,11 @@
35
37
  "types": "./dist/index.d.ts",
36
38
  "import": "./dist/index.js",
37
39
  "default": "./dist/index.js"
40
+ },
41
+ "./element": {
42
+ "types": "./dist/element.d.ts",
43
+ "import": "./dist/element.js",
44
+ "default": "./dist/element.js"
38
45
  }
39
46
  },
40
47
  "publishConfig": {
@@ -42,17 +49,18 @@
42
49
  "registry": "https://registry.npmjs.org/"
43
50
  },
44
51
  "dependencies": {
45
- "@mcp-b/webmcp-types": "4.1.0-beta.20260702224704",
46
- "@mcp-b/webmcp-ts-sdk": "4.1.0-beta.20260702224704",
47
- "@mcp-b/transports": "4.1.0-beta.20260702224704"
52
+ "@modelcontextprotocol/client": "2.0.0",
53
+ "@mcp-b/transports": "5.0.1"
48
54
  },
49
55
  "devDependencies": {
50
56
  "@types/node": "22.17.2",
51
57
  "typescript": "^5.8.3",
52
- "vite-plus": "^0.1.22"
58
+ "vite-plus": "^0.1.22",
59
+ "@mcp-b/webmcp-types": "5.0.1",
60
+ "@mcp-b/webmcp-ts-sdk": "5.0.1"
53
61
  },
54
62
  "engines": {
55
- "node": ">=18"
63
+ "node": ">=20"
56
64
  },
57
65
  "scripts": {
58
66
  "build": "vp pack",
@@ -62,9 +70,6 @@
62
70
  "lint": "vp lint --fix",
63
71
  "publish:dry": "pnpm publish --access public --dry-run",
64
72
  "publish:npm": "pnpm publish --access public",
65
- "typecheck": "tsc --noEmit",
66
- "version:major": "pnpm version major --no-git-tag-version",
67
- "version:minor": "pnpm version minor --no-git-tag-version",
68
- "version:patch": "pnpm version patch --no-git-tag-version"
73
+ "typecheck": "tsc --noEmit"
69
74
  }
70
75
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/MCPIframeElement.ts"],"mappings":";;;;UAkHiB,yBAAA;EACf,KAAA;EACA,SAAA;EACA,OAAA;AAAA;;UAIe,yBAAA;EACf,KAAA;AAAA;;UAIe,gCAAA;EACf,KAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;;;;;;;;cAiBW,gBAAA,SAAyB,WAAA;EAAA;aAwBzB,kBAAA,CAAA;;EAWX,iBAAA,CAAA;EAIA,oBAAA,CAAA;EAIA,wBAAA,CAAyB,IAAA,UAAc,QAAA,iBAAyB,QAAA;EA4I1C;EAAA,IApFlB,MAAA,CAAA,GAAU,iBAAA;EAoFe;EAAA,IA/EzB,MAAA,CAAA,GAAU,MAAA;EAqbwB;EAAA,IAhblC,KAAA,CAAA;EAgbmC;EAAA,IA3anC,YAAA,CAAA;;MAKA,gBAAA,CAAA;;MAKA,cAAA,CAAA;;MAKA,QAAA,CAAA,GAAY,IAAA;;MAKZ,YAAA,CAAA,GAAgB,QAAA;;MAKhB,UAAA,CAAA,GAAc,MAAA;;MAKd,UAAA,CAAA;;MAaA,UAAA,CAAA;;EAKE,OAAA,CAAA,GAAW,OAAA;;EAqBX,YAAA,CAAA,GAAgB,OAAA;AAAA;;iBAsWR,wBAAA,CAAyB,OAAA"}