@mcp-b/mcp-iframe 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ declare class MCPIframeElement extends HTMLElement {
|
|
|
54
54
|
get mcpResources(): Resource[];
|
|
55
55
|
/** Raw prompts from the iframe's MCP server (without prefix) */
|
|
56
56
|
get mcpPrompts(): Prompt[];
|
|
57
|
-
/** The item name prefix (id + separator) */
|
|
57
|
+
/** The item name prefix (id + separator), sanitized for MCP compatibility */
|
|
58
58
|
get itemPrefix(): string;
|
|
59
59
|
/** @deprecated Use itemPrefix instead */
|
|
60
60
|
get toolPrefix(): string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/MCPIframeElement.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/MCPIframeElement.ts"],"sourcesContent":[],"mappings":";;;;;;UA2IiB,yBAAA;;;;;;UAOA,yBAAA;;;;UAKA,gCAAA;;;;;;;;;;;;;;;cAoBJ,gBAAA,SAAyB,WAAA;;;;;;;;gBAmGtB;;gBAKA;;;;;;;;;;kBAyBE;;sBAKI;;oBAKF;;;;;;aAuBD;;kBAqBK;;;iBAuVR,wBAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{IframeParentTransport as e}from"@mcp-b/transports";import{Client as t}from"@modelcontextprotocol/sdk/client/index.js";const n=3e4,r=`mcp-iframe`,i=[`src`,`srcdoc`,`name`,`sandbox`,`allow`,`allowfullscreen`,`width`,`height`,`loading`,`referrerpolicy`,`credentialless`];var
|
|
1
|
+
import{IframeParentTransport as e}from"@mcp-b/transports";import{Client as t}from"@modelcontextprotocol/sdk/client/index.js";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{#iframe=null;#client=null;#transport=null;#ready=!1;#connecting=!1;#mcpTools=[];#mcpResources=[];#mcpPrompts=[];#registeredTools=new Map;#registeredResources=new Map;#registeredPrompts=new Map;#callTimeout=n;#prefixSeparator=`_`;#channelId=r;#targetOrigin=null;static get observedAttributes(){return[...c,`target-origin`,`channel`,`call-timeout`,`prefix-separator`]}constructor(){super(),this.attachShadow({mode:`open`})}connectedCallback(){this.#createIframe()}disconnectedCallback(){this.#cleanup()}attributeChangedCallback(e,t,i){if(t!==i)switch(e){case`target-origin`:this.#targetOrigin=i,this.#ready&&this.#reconnect();break;case`channel`:this.#channelId=i??r,this.#ready&&this.#reconnect();break;case`call-timeout`:this.#callTimeout=i?Number.parseInt(i,10):n;break;case`prefix-separator`:{let e=i??`_`;a(e)?this.#prefixSeparator=e:(console.warn(`[MCPIframe] Invalid prefix-separator "${e}". Only alphanumeric characters, underscores, and hyphens are allowed. Using sanitized value: "${s(e)}"`),this.#prefixSeparator=s(e)),this.#ready&&(this.#unregisterAll(),this.#registerAllOnModelContext());break}default:this.#iframe&&c.includes(e)&&(i===null?this.#iframe.removeAttribute(e):this.#iframe.setAttribute(e,i),(e===`src`||e===`srcdoc`)&&this.#reconnect())}}get iframe(){return this.#iframe}get client(){return this.#client}get ready(){return this.#ready}get exposedTools(){return Array.from(this.#registeredTools.keys())}get exposedResources(){return Array.from(this.#registeredResources.keys())}get exposedPrompts(){return Array.from(this.#registeredPrompts.keys())}get mcpTools(){return[...this.#mcpTools]}get mcpResources(){return[...this.#mcpResources]}get mcpPrompts(){return[...this.#mcpPrompts]}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.#prefixSeparator}`}get toolPrefix(){return this.itemPrefix}async refresh(){if(!this.#client||!this.#ready)throw Error(`Not connected to iframe MCP server`);await this.#fetchAllFromIframe(),this.#unregisterAll(),this.#registerAllOnModelContext(),this.dispatchEvent(new CustomEvent(`mcp-iframe-tools-changed`,{detail:{tools:this.exposedTools,resources:this.exposedResources,prompts:this.exposedPrompts}}))}async refreshTools(){return this.refresh()}#createIframe(){this.#iframe=document.createElement(`iframe`);for(let e of c){let t=this.getAttribute(e);t!==null&&this.#iframe.setAttribute(e,t)}this.#iframe.style.border=`none`,this.#iframe.style.width=this.getAttribute(`width`)??`100%`,this.#iframe.style.height=this.getAttribute(`height`)??`100%`,this.#iframe.addEventListener(`load`,()=>void this.#connect()),this.shadowRoot?.appendChild(this.#iframe)}async#connect(){if(!(this.#connecting||!this.#iframe)){this.#connecting=!0;try{let n=this.#getTargetOrigin();if(!n){console.warn(`[MCPIframe] Cannot determine target origin. Set target-origin attribute.`);return}this.#transport=new e({iframe:this.#iframe,targetOrigin:n,channelId:this.#channelId}),this.#client=new t({name:`MCPIframe:${this.id||`anonymous`}`,version:`1.0.0`}),await this.#client.connect(this.#transport),this.#ready=!0,await this.#fetchAllFromIframe(),this.#registerAllOnModelContext(),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.#connecting=!1}}}async#fetchAllFromIframe(){if(!this.#client)return;let[e,t,n]=await Promise.all([this.#client.listTools(),this.#client.listResources().catch(()=>({resources:[]})),this.#client.listPrompts().catch(()=>({prompts:[]}))]);this.#mcpTools=e.tools,this.#mcpResources=t.resources,this.#mcpPrompts=n.prompts}#getTargetOrigin(){if(this.#targetOrigin)return this.#targetOrigin;let e=this.getAttribute(`src`);if(e)try{return new URL(e,window.location.href).origin}catch{}return window.location.origin}#registerAllOnModelContext(){let e=navigator.modelContext;if(!e){console.warn(`[MCPIframe] Model Context API not available on parent`);return}this.#registerToolsOnModelContext(e),this.#registerResourcesOnModelContext(e),this.#registerPromptsOnModelContext(e)}#registerToolsOnModelContext(e){for(let t of this.#mcpTools){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}let r=e.registerTool({name:n,description:t.description??`Tool from iframe: ${t.name}`,inputSchema:t.inputSchema,execute:e=>this.#callIframeTool(t.name,e)});this.#registeredTools.set(n,r)}}#registerResourcesOnModelContext(e){for(let t of this.#mcpResources){let n=`${this.itemPrefix}${t.uri}`,r={uri:n,name:t.name,read:(e,n)=>this.#readIframeResource(t.uri)};t.description!==void 0&&(r.description=t.description),t.mimeType!==void 0&&(r.mimeType=t.mimeType);let i=e.registerResource(r);this.#registeredResources.set(n,i)}}#registerPromptsOnModelContext(e){for(let t of this.#mcpPrompts){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,get:e=>this.#getIframePrompt(t.name,e)};t.description!==void 0&&(r.description=t.description),t.arguments&&t.arguments.length>0&&(r.argsSchema={type:`object`,properties:Object.fromEntries(t.arguments.map(e=>[e.name,{type:`string`,description:e.description}])),required:t.arguments.filter(e=>e.required).map(e=>e.name)});let i=e.registerPrompt(r);this.#registeredPrompts.set(n,i)}}#unregisterAll(){for(let e of this.#registeredTools.values())e.unregister();this.#registeredTools.clear();for(let e of this.#registeredResources.values())e.unregister();this.#registeredResources.clear();for(let e of this.#registeredPrompts.values())e.unregister();this.#registeredPrompts.clear()}async#callIframeTool(e,t){if(!this.#client||!this.#ready)throw Error(`Not connected to iframe MCP server`);let n=new AbortController,r=setTimeout(()=>n.abort(),this.#callTimeout);try{return await this.#client.callTool({name:e,arguments:t},void 0,{signal:n.signal})}finally{clearTimeout(r)}}async#readIframeResource(e){if(!this.#client||!this.#ready)throw Error(`Not connected to iframe MCP server`);let t=new AbortController,n=setTimeout(()=>t.abort(),this.#callTimeout);try{return await this.#client.readResource({uri:e},{signal:t.signal})}finally{clearTimeout(n)}}async#getIframePrompt(e,t){if(!this.#client||!this.#ready)throw Error(`Not connected to iframe MCP server`);let n=new AbortController,r=setTimeout(()=>n.abort(),this.#callTimeout);try{return await this.#client.getPrompt({name:e,arguments:t},{signal:n.signal})}finally{clearTimeout(r)}}async#reconnect(){await this.#disconnect(),await new Promise(e=>setTimeout(e,100)),await this.#connect()}async#disconnect(){if(this.#ready=!1,this.#mcpTools=[],this.#mcpResources=[],this.#mcpPrompts=[],this.#unregisterAll(),this.#client){try{await this.#client.close()}catch{}this.#client=null}if(this.#transport){try{await this.#transport.close()}catch{}this.#transport=null}}async#cleanup(){await this.#disconnect()}};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};
|
|
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","resourceDescriptor: Parameters<ModelContext['registerResource']>[0]","#readIframeResource","promptDescriptor: Parameters<ModelContext['registerPrompt']>[0]","#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 * @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 { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type {\n CallToolResult,\n GetPromptResult,\n Prompt,\n ReadResourceResult,\n Resource,\n Tool,\n} from '@modelcontextprotocol/sdk/types.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CALL_TIMEOUT = 30000;\nconst DEFAULT_PREFIX_SEPARATOR = ':';\nconst DEFAULT_CHANNEL_ID = 'mcp-iframe';\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/** Registration handle returned by navigator.modelContext register methods */\ninterface RegistrationHandle {\n unregister: () => void;\n}\n\n/** Minimal ModelContext interface for registering tools, resources, and prompts */\ninterface ModelContext {\n registerTool(tool: {\n name: string;\n description: string;\n inputSchema: Tool['inputSchema'];\n execute: (args: Record<string, unknown>) => Promise<CallToolResult>;\n }): RegistrationHandle;\n\n registerResource(resource: {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n read: (uri: URL, params?: Record<string, string>) => Promise<ReadResourceResult>;\n }): RegistrationHandle;\n\n registerPrompt(prompt: {\n name: string;\n description?: string;\n argsSchema?: Record<string, unknown>;\n get: (args: Record<string, unknown>) => Promise<GetPromptResult>;\n }): RegistrationHandle;\n}\n\n/** Navigator with optional modelContext */\ninterface NavigatorWithModelContext extends Navigator {\n modelContext?: ModelContext;\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 Map<string, RegistrationHandle>();\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 this.#prefixSeparator = newValue ?? DEFAULT_PREFIX_SEPARATOR;\n if (this.#ready) {\n this.#unregisterAll();\n this.#registerAllOnModelContext();\n }\n break;\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.keys());\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) */\n get itemPrefix(): string {\n const id = this.id || this.getAttribute('name') || 'iframe';\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(() => ({ resources: [] })),\n this.#client.listPrompts().catch(() => ({ prompts: [] })),\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 {\n // Invalid URL\n }\n }\n\n // Default to same origin\n return window.location.origin;\n }\n\n #registerAllOnModelContext(): void {\n const modelContext = (navigator as NavigatorWithModelContext).modelContext;\n if (!modelContext) {\n console.warn('[MCPIframe] Model Context API not available on parent');\n return;\n }\n\n this.#registerToolsOnModelContext(modelContext);\n this.#registerResourcesOnModelContext(modelContext);\n this.#registerPromptsOnModelContext(modelContext);\n }\n\n #registerToolsOnModelContext(modelContext: ModelContext): void {\n for (const tool of this.#mcpTools) {\n const prefixedName = `${this.itemPrefix}${tool.name}`;\n\n const registration = modelContext.registerTool({\n name: prefixedName,\n description: tool.description ?? `Tool from iframe: ${tool.name}`,\n inputSchema: tool.inputSchema,\n execute: (args) => this.#callIframeTool(tool.name, args),\n });\n\n this.#registeredTools.set(prefixedName, registration);\n }\n }\n\n #registerResourcesOnModelContext(modelContext: ModelContext): void {\n for (const resource of this.#mcpResources) {\n const prefixedUri = `${this.itemPrefix}${resource.uri}`;\n\n const resourceDescriptor: Parameters<ModelContext['registerResource']>[0] = {\n uri: prefixedUri,\n name: resource.name,\n read: (_uri, _params) => this.#readIframeResource(resource.uri),\n };\n if (resource.description !== undefined) {\n resourceDescriptor.description = resource.description;\n }\n if (resource.mimeType !== undefined) {\n resourceDescriptor.mimeType = resource.mimeType;\n }\n\n const registration = modelContext.registerResource(resourceDescriptor);\n this.#registeredResources.set(prefixedUri, registration);\n }\n }\n\n #registerPromptsOnModelContext(modelContext: ModelContext): void {\n for (const prompt of this.#mcpPrompts) {\n const prefixedName = `${this.itemPrefix}${prompt.name}`;\n\n const promptDescriptor: Parameters<ModelContext['registerPrompt']>[0] = {\n name: prefixedName,\n get: (args) => this.#getIframePrompt(prompt.name, args),\n };\n if (prompt.description !== undefined) {\n promptDescriptor.description = prompt.description;\n }\n if (prompt.arguments && prompt.arguments.length > 0) {\n promptDescriptor.argsSchema = {\n type: 'object',\n properties: Object.fromEntries(\n prompt.arguments.map((arg) => [\n arg.name,\n { type: 'string', description: arg.description },\n ])\n ),\n required: prompt.arguments.filter((a) => a.required).map((a) => a.name),\n };\n }\n\n const registration = modelContext.registerPrompt(promptDescriptor);\n this.#registeredPrompts.set(prefixedName, registration);\n }\n }\n\n #unregisterAll(): void {\n for (const registration of this.#registeredTools.values()) {\n registration.unregister();\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 {\n // Ignore\n }\n this.#client = null;\n }\n\n if (this.#transport) {\n try {\n await this.#transport.close();\n } catch {\n // Ignore\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":"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"}
|
|
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","resourceDescriptor: Parameters<ModelContext['registerResource']>[0]","#readIframeResource","promptDescriptor: Parameters<ModelContext['registerPrompt']>[0]","#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 { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type {\n CallToolResult,\n GetPromptResult,\n Prompt,\n ReadResourceResult,\n Resource,\n Tool,\n} from '@modelcontextprotocol/sdk/types.js';\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/** Registration handle returned by navigator.modelContext register methods */\ninterface RegistrationHandle {\n unregister: () => void;\n}\n\n/** Minimal ModelContext interface for registering tools, resources, and prompts */\ninterface ModelContext {\n registerTool(tool: {\n name: string;\n description: string;\n inputSchema: Tool['inputSchema'];\n execute: (args: Record<string, unknown>) => Promise<CallToolResult>;\n }): RegistrationHandle;\n\n registerResource(resource: {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n read: (uri: URL, params?: Record<string, string>) => Promise<ReadResourceResult>;\n }): RegistrationHandle;\n\n registerPrompt(prompt: {\n name: string;\n description?: string;\n argsSchema?: Record<string, unknown>;\n get: (args: Record<string, unknown>) => Promise<GetPromptResult>;\n }): RegistrationHandle;\n}\n\n/** Navigator with optional modelContext */\ninterface NavigatorWithModelContext extends Navigator {\n modelContext?: ModelContext;\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 Map<string, RegistrationHandle>();\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.keys());\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(() => ({ resources: [] })),\n this.#client.listPrompts().catch(() => ({ prompts: [] })),\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 {\n // Invalid URL\n }\n }\n\n // Default to same origin\n return window.location.origin;\n }\n\n #registerAllOnModelContext(): void {\n const modelContext = (navigator as NavigatorWithModelContext).modelContext;\n if (!modelContext) {\n console.warn('[MCPIframe] Model Context API not available on parent');\n return;\n }\n\n this.#registerToolsOnModelContext(modelContext);\n this.#registerResourcesOnModelContext(modelContext);\n this.#registerPromptsOnModelContext(modelContext);\n }\n\n #registerToolsOnModelContext(modelContext: ModelContext): 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 const registration = modelContext.registerTool({\n name: prefixedName,\n description: tool.description ?? `Tool from iframe: ${tool.name}`,\n inputSchema: tool.inputSchema,\n execute: (args) => this.#callIframeTool(tool.name, args),\n });\n\n this.#registeredTools.set(prefixedName, registration);\n }\n }\n\n #registerResourcesOnModelContext(modelContext: ModelContext): void {\n for (const resource of this.#mcpResources) {\n const prefixedUri = `${this.itemPrefix}${resource.uri}`;\n\n const resourceDescriptor: Parameters<ModelContext['registerResource']>[0] = {\n uri: prefixedUri,\n name: resource.name,\n read: (_uri, _params) => this.#readIframeResource(resource.uri),\n };\n if (resource.description !== undefined) {\n resourceDescriptor.description = resource.description;\n }\n if (resource.mimeType !== undefined) {\n resourceDescriptor.mimeType = resource.mimeType;\n }\n\n const registration = modelContext.registerResource(resourceDescriptor);\n this.#registeredResources.set(prefixedUri, registration);\n }\n }\n\n #registerPromptsOnModelContext(modelContext: ModelContext): 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 promptDescriptor: Parameters<ModelContext['registerPrompt']>[0] = {\n name: prefixedName,\n get: (args) => this.#getIframePrompt(prompt.name, args),\n };\n if (prompt.description !== undefined) {\n promptDescriptor.description = prompt.description;\n }\n if (prompt.arguments && prompt.arguments.length > 0) {\n promptDescriptor.argsSchema = {\n type: 'object',\n properties: Object.fromEntries(\n prompt.arguments.map((arg) => [\n arg.name,\n { type: 'string', description: arg.description },\n ])\n ),\n required: prompt.arguments.filter((a) => a.required).map((a) => a.name),\n };\n }\n\n const registration = modelContext.registerPrompt(promptDescriptor);\n this.#registeredPrompts.set(prefixedName, registration);\n }\n }\n\n #unregisterAll(): void {\n for (const registration of this.#registeredTools.values()) {\n registration.unregister();\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 {\n // Ignore\n }\n this.#client = null;\n }\n\n if (this.#transport) {\n try {\n await this.#transport.close();\n } catch {\n // Ignore\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":"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"}
|