@wp-playground/mcp 3.1.15 → 3.1.17

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
@@ -51,6 +51,12 @@ AI Client (stdio) → MCP Server (Node.js) → WebSocket (port 7999) → Browser
51
51
 
52
52
  The MCP server communicates with AI clients via stdio and with the browser via WebSocket. A bridge client (`bridge-client.ts`) integrated into the Playground website via Redux middleware auto-connects to the WebSocket server and proxies commands to the PlaygroundClient API.
53
53
 
54
+ ## WebMCP
55
+
56
+ The [Playground Website](https://playground.wordpress.net/) also supports [WebMCP](https://github.com/webmachinelearning/webmcp) — a browser-native MCP proposal that exposes tools via `navigator.modelContext`. When a Playground site loads, its tools are registered automatically with no CLI or WebSocket bridge needed.
57
+
58
+ > **Note:** WebMCP is still a draft proposal and not widely supported.
59
+
54
60
  ## Security
55
61
 
56
62
  The MCP bridge runs locally and is only accessible from your machine — connections are origin-restricted and require a token generated at server startup, preventing other websites from hijacking it.
@@ -1,20 +1,20 @@
1
1
  import type { PlaygroundClient } from '@wp-playground/remote';
2
2
  /**
3
- * Shared configuration for the MCP bridge client and WebMCP.
4
- *
5
- * Both transports need the same callbacks to interact with
6
- * the Playground site list and active client.
3
+ * Configuration accepted by `startMcpBridge`. Only includes the
4
+ * methods the bridge actually calls — callers can pass any wider
5
+ * object (e.g. the full site-management API) and TypeScript will
6
+ * accept it structurally.
7
7
  */
8
- export interface PlaygroundConfig {
9
- getSites: () => Array<{
8
+ export interface PlaygroundBridgeConfig {
9
+ list(): Array<{
10
10
  slug: string;
11
11
  name: string;
12
12
  storage: string;
13
13
  isActive: boolean;
14
14
  }>;
15
- getPlaygroundClient: (siteSlug: string) => PlaygroundClient | undefined;
16
- renameSite?: (siteSlug: string, newName: string) => Promise<void>;
17
- saveSite?: (siteSlug: string) => Promise<{
15
+ getClient(): PlaygroundClient | undefined;
16
+ rename(newName: string): Promise<void>;
17
+ saveInBrowser(): Promise<{
18
18
  slug: string;
19
19
  storage: string;
20
20
  }>;
@@ -24,4 +24,4 @@ export interface McpBridgeHandle {
24
24
  notifySitesChanged: () => void;
25
25
  stop: () => void;
26
26
  }
27
- export declare function startMcpBridge(config: PlaygroundConfig, port: number): McpBridgeHandle;
27
+ export declare function startMcpBridge(config: PlaygroundBridgeConfig, port: number): McpBridgeHandle;
package/client.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("./tool-executors-Bg92LDCE.cjs"),E=5e3;function g(t,r){const d=crypto.randomUUID();let e=null,a="",i=null,p=!1;function c(o){const s=t.getSites(),l=JSON.stringify(s);l!==a&&(a=l,o.send(JSON.stringify({type:"register",tabId:d,sites:s})))}async function n(){try{const o=await fetch(`http://127.0.0.1:${r}/bridge-token`);if(!o.ok){f();return}const{token:s}=await o.json();e=new WebSocket(`ws://127.0.0.1:${r}?token=${s}`)}catch{f();return}e.addEventListener("open",()=>{var o;a="",c(e),(o=t.onConnect)==null||o.call(t)}),e.addEventListener("message",async o=>{let s;try{s=JSON.parse(o.data)}catch{return}if(s.type!=="command")return;const{id:l,method:w,args:y,siteSlug:S}=s;try{const u=await N(t,w,y||[],S,r);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:l,type:"response",value:u}))}catch(u){const m=u instanceof Error?u.message:String(u);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:l,type:"response",error:m}))}}),e.addEventListener("close",()=>{e=null,f()}),e.addEventListener("error",()=>{})}function f(){p||(i=setTimeout(n,E))}return n(),{notifySitesChanged:()=>{(e==null?void 0:e.readyState)===WebSocket.OPEN&&c(e)},stop:()=>{p=!0,i!==null&&(clearTimeout(i),i=null),e&&(e.close(),e=null)}}}async function N(t,r,d,e,a){if(r==="__open_site"){const n=new URL(window.location.href);if(n.searchParams.set("mcp","yes"),n.searchParams.set("mcp-port",String(a)),n.searchParams.set("site-slug",e),!window.open(n.toString(),"_blank"))throw new Error("Pop-up blocked by browser. The user must allow pop-ups for this site.");return!0}if(r==="__rename_site"){if(!t.renameSite)throw new Error("renameSite not configured");const[n]=d;return await t.renameSite(e,n),!0}if(r==="__save_site"){if(!t.saveSite)throw new Error("saveSite not configured");return await t.saveSite(e)}const i=t.getPlaygroundClient(e);if(!i)throw new Error(`No active client for site: ${e}`);const c=h.createToolClient(i)[r];if(typeof c!="function")throw new Error(`Unknown method: ${r}`);return await c(...d)}exports.startMcpBridge=g;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./tool-executors-CnK8qvl5.cjs"),v=5e3;function b(r,o){const c=crypto.randomUUID();let e=null,n="",t=null,l=!1;function s(u){const d=r.list(),p=JSON.stringify(d);p!==n&&(n=p,u.send(JSON.stringify({type:"register",tabId:c,sites:d})))}async function i(){try{const u=await fetch(`http://127.0.0.1:${o}/bridge-token`);if(!u.ok){f();return}const{token:d}=await u.json();e=new WebSocket(`ws://127.0.0.1:${o}?token=${d}`)}catch{f();return}e.addEventListener("open",()=>{var u;n="",s(e),(u=r.onConnect)==null||u.call(r)}),e.addEventListener("message",async u=>{let d;try{d=JSON.parse(u.data)}catch{return}if(d.type!=="command")return;const{id:p,method:w,args:_,siteSlug:S}=d;try{const y=await E(r,w,_||[],S,o);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:p,type:"response",value:y}))}catch(y){const h=y instanceof Error?y.message:String(y);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:p,type:"response",error:h}))}}),e.addEventListener("close",()=>{e=null,f()}),e.addEventListener("error",()=>{})}function f(){l||(t=setTimeout(i,v))}return i(),{notifySitesChanged:()=>{(e==null?void 0:e.readyState)===WebSocket.OPEN&&s(e)},stop:()=>{l=!0,t!==null&&(clearTimeout(t),t=null),e&&(e.close(),e=null)}}}async function E(r,o,c,e,n){if(o==="__open_site_in_new_tab"){const i=new URL(window.location.href);if(i.searchParams.set("mcp","yes"),i.searchParams.set("mcp-port",String(n)),i.searchParams.set("site-slug",e),!window.open(i.toString(),"_blank"))throw new Error("Pop-up blocked by browser. The user must allow pop-ups for this site.");return!0}if(o==="__rename_site"){const[i]=c;return await r.rename(i),!0}if(o==="__save_site")return await r.saveInBrowser();const t=r.getClient();if(!t)throw new Error(`No active client for site: ${e}`);const s=a.createToolClient(t)[o];if(typeof s!="function")throw new Error(`Unknown method: ${o}`);return await s(...c)}const m=a.getSiteToolDefinitions();let g=null;function N(r){const c=r.list().find(e=>e.isActive);if(!c)throw new Error("No active Playground site");return c}function T(r){if(typeof navigator>"u"||!navigator.modelContext)return;g==null||g.abort(),g=new AbortController;const o=g.signal;function c(){const n=r.getClient();if(!n)throw new Error("No client for active site");return n}const e=Object.entries(a.toolDefinitions).map(([n,t])=>({name:n,description:t.description,inputSchema:a.paramsToJsonSchema(t.params),annotations:t.annotations,execute:async l=>{try{const s=a.toolExecutors[n];if(!s)return{error:`No executor for "${n}"`};const i=a.createToolClient(c());return await s(i,l)}catch(s){return{error:`${t.errorPrefix}: ${a.stringifyError(s)}`}}}}));e.push(...x(r));for(const n of e)navigator.modelContext.registerTool(n,{signal:o})}function x(r){const o=m.playground_list_sites,c=m.playground_save_in_browser,e=m.playground_rename_site,n=m.playground_get_website_url;return[{name:"playground_list_sites",description:o.description,annotations:o.annotations,execute:async()=>{try{return{connectedTabs:1,sites:r.list().map(t=>({siteId:t.slug,name:t.name,storage:a.formatStorageLabel(t.storage),isActive:t.isActive}))}}catch(t){return{error:`${o.errorPrefix}: ${a.stringifyError(t)}`}}}},{name:"playground_save_in_browser",description:c.description,annotations:c.annotations,execute:async()=>{try{const t=N(r),l=a.formatStorageLabel(t.storage);if(l!=="temporary")return{success:!0,alreadySaved:!0,siteId:t.slug,name:t.name,storage:l};const s=await r.saveInBrowser();return{success:!0,alreadySaved:!1,siteId:s.slug,name:t.name??s.slug,storage:a.formatStorageLabel(s.storage)}}catch(t){return{error:`${c.errorPrefix}: ${a.stringifyError(t)}`}}}},{name:"playground_rename_site",description:e.description,inputSchema:a.paramsToJsonSchema(e.params),annotations:e.annotations,execute:async t=>{try{const l=t.newName,i=r.list().find(f=>f.isActive);return await r.rename(l),{success:!0,siteId:i==null?void 0:i.slug,newName:l}}catch(l){return{error:`${e.errorPrefix}: ${a.stringifyError(l)}`}}}},{name:"playground_get_website_url",description:n.description,annotations:n.annotations,execute:async()=>{try{return{url:window.location.href}}catch(t){return{error:`${n.errorPrefix}: ${a.stringifyError(t)}`}}}}]}exports.registerWebMCPTools=T;exports.startMcpBridge=b;
2
2
  //# sourceMappingURL=client.cjs.map
package/client.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","sources":["../../../../packages/playground/mcp/src/bridge-client.ts"],"sourcesContent":["import type { PlaygroundClient } from '@wp-playground/remote';\nimport { createToolClient } from './tools/tool-executors';\nimport type { ToolClient } from './tools/tool-executors';\n\n/**\n * Shared configuration for the MCP bridge client and WebMCP.\n *\n * Both transports need the same callbacks to interact with\n * the Playground site list and active client.\n */\nexport interface PlaygroundConfig {\n\tgetSites: () => Array<{\n\t\tslug: string;\n\t\tname: string;\n\t\tstorage: string;\n\t\tisActive: boolean;\n\t}>;\n\tgetPlaygroundClient: (siteSlug: string) => PlaygroundClient | undefined;\n\trenameSite?: (siteSlug: string, newName: string) => Promise<void>;\n\tsaveSite?: (siteSlug: string) => Promise<{ slug: string; storage: string }>;\n\tonConnect?: () => void;\n}\n\nexport interface McpBridgeHandle {\n\tnotifySitesChanged: () => void;\n\tstop: () => void;\n}\n\nconst RECONNECT_INTERVAL_MS = 5000;\n\nexport function startMcpBridge(\n\tconfig: PlaygroundConfig,\n\tport: number\n): McpBridgeHandle {\n\tconst tabId = crypto.randomUUID();\n\tlet ws: WebSocket | null = null;\n\tlet previousSitesSerialized = '';\n\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet stopped = false;\n\n\tfunction sendSitesRegistration(socket: WebSocket) {\n\t\tconst sites = config.getSites();\n\t\tconst serialized = JSON.stringify(sites);\n\t\tif (serialized === previousSitesSerialized) {\n\t\t\treturn;\n\t\t}\n\t\tpreviousSitesSerialized = serialized;\n\t\tsocket.send(JSON.stringify({ type: 'register', tabId, sites }));\n\t}\n\n\tasync function connect() {\n\t\ttry {\n\t\t\tconst response = await fetch(\n\t\t\t\t`http://127.0.0.1:${port}/bridge-token`\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tscheduleReconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst { token } = await response.json();\n\t\t\tws = new WebSocket(`ws://127.0.0.1:${port}?token=${token}`);\n\t\t} catch {\n\t\t\tscheduleReconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tws.addEventListener('open', () => {\n\t\t\tpreviousSitesSerialized = '';\n\t\t\tsendSitesRegistration(ws!);\n\t\t\tconfig.onConnect?.();\n\t\t});\n\n\t\tws.addEventListener('message', async (event) => {\n\t\t\tlet message;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.type !== 'command') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { id, method, args, siteSlug } = message;\n\t\t\ttry {\n\t\t\t\tconst value = await handleCommand(\n\t\t\t\t\tconfig,\n\t\t\t\t\tmethod,\n\t\t\t\t\targs || [],\n\t\t\t\t\tsiteSlug,\n\t\t\t\t\tport\n\t\t\t\t);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(JSON.stringify({ id, type: 'response', value }));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg =\n\t\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'response',\n\t\t\t\t\t\t\terror: errorMsg,\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener('close', () => {\n\t\t\tws = null;\n\t\t\tscheduleReconnect();\n\t\t});\n\n\t\tws.addEventListener('error', () => {\n\t\t\t// Error will be followed by close event,\n\t\t\t// which handles reconnect\n\t\t});\n\t}\n\n\tfunction scheduleReconnect() {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\treconnectTimer = setTimeout(connect, RECONNECT_INTERVAL_MS);\n\t}\n\n\tconnect();\n\n\treturn {\n\t\tnotifySitesChanged: () => {\n\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tsendSitesRegistration(ws);\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tstopped = true;\n\t\t\tif (reconnectTimer !== null) {\n\t\t\t\tclearTimeout(reconnectTimer);\n\t\t\t\treconnectTimer = null;\n\t\t\t}\n\t\t\tif (ws) {\n\t\t\t\tws.close();\n\t\t\t\tws = null;\n\t\t\t}\n\t\t},\n\t};\n}\n\nasync function handleCommand(\n\tconfig: PlaygroundConfig,\n\tmethod: string,\n\targs: unknown[],\n\tsiteSlug: string,\n\tport: number\n): Promise<unknown> {\n\tif (method === '__open_site') {\n\t\tconst url = new URL(window.location.href);\n\t\turl.searchParams.set('mcp', 'yes');\n\t\turl.searchParams.set('mcp-port', String(port));\n\t\turl.searchParams.set('site-slug', siteSlug);\n\t\tconst newWindow = window.open(url.toString(), '_blank');\n\t\tif (!newWindow) {\n\t\t\tthrow new Error(\n\t\t\t\t'Pop-up blocked by browser. The user ' +\n\t\t\t\t\t'must allow pop-ups for this site.'\n\t\t\t);\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method === '__rename_site') {\n\t\tif (!config.renameSite) {\n\t\t\tthrow new Error('renameSite not configured');\n\t\t}\n\t\tconst [newName] = args as [string];\n\t\tawait config.renameSite(siteSlug, newName);\n\t\treturn true;\n\t}\n\n\tif (method === '__save_site') {\n\t\tif (!config.saveSite) {\n\t\t\tthrow new Error('saveSite not configured');\n\t\t}\n\t\treturn await config.saveSite(siteSlug);\n\t}\n\n\tconst playgroundClient = config.getPlaygroundClient(siteSlug);\n\tif (!playgroundClient) {\n\t\tthrow new Error(`No active client for site: ${siteSlug}`);\n\t}\n\n\tconst client = createToolClient(playgroundClient);\n\tconst fn = client[method as keyof ToolClient];\n\tif (typeof fn !== 'function') {\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t}\n\treturn await (fn as (...a: unknown[]) => Promise<unknown>)(...args);\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient"],"mappings":"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"}
1
+ {"version":3,"file":"client.cjs","sources":["../../../../packages/playground/mcp/src/bridge-client.ts","../../../../packages/playground/mcp/src/webmcp.ts"],"sourcesContent":["import type { PlaygroundClient } from '@wp-playground/remote';\nimport { createToolClient } from './tools/tool-executors';\nimport type { ToolClient } from './tools/tool-executors';\n\n/**\n * Configuration accepted by `startMcpBridge`. Only includes the\n * methods the bridge actually calls — callers can pass any wider\n * object (e.g. the full site-management API) and TypeScript will\n * accept it structurally.\n */\nexport interface PlaygroundBridgeConfig {\n\tlist(): Array<{\n\t\tslug: string;\n\t\tname: string;\n\t\tstorage: string;\n\t\tisActive: boolean;\n\t}>;\n\tgetClient(): PlaygroundClient | undefined;\n\trename(newName: string): Promise<void>;\n\tsaveInBrowser(): Promise<{ slug: string; storage: string }>;\n\tonConnect?: () => void;\n}\n\nexport interface McpBridgeHandle {\n\tnotifySitesChanged: () => void;\n\tstop: () => void;\n}\n\nconst RECONNECT_INTERVAL_MS = 5000;\n\nexport function startMcpBridge(\n\tconfig: PlaygroundBridgeConfig,\n\tport: number\n): McpBridgeHandle {\n\tconst tabId = crypto.randomUUID();\n\tlet ws: WebSocket | null = null;\n\tlet previousSitesSerialized = '';\n\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet stopped = false;\n\n\tfunction sendSitesRegistration(socket: WebSocket) {\n\t\tconst sites = config.list();\n\t\tconst serialized = JSON.stringify(sites);\n\t\tif (serialized === previousSitesSerialized) {\n\t\t\treturn;\n\t\t}\n\t\tpreviousSitesSerialized = serialized;\n\t\tsocket.send(JSON.stringify({ type: 'register', tabId, sites }));\n\t}\n\n\tasync function connect() {\n\t\ttry {\n\t\t\tconst response = await fetch(\n\t\t\t\t`http://127.0.0.1:${port}/bridge-token`\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tscheduleReconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst { token } = await response.json();\n\t\t\tws = new WebSocket(`ws://127.0.0.1:${port}?token=${token}`);\n\t\t} catch {\n\t\t\tscheduleReconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tws.addEventListener('open', () => {\n\t\t\tpreviousSitesSerialized = '';\n\t\t\tsendSitesRegistration(ws!);\n\t\t\tconfig.onConnect?.();\n\t\t});\n\n\t\tws.addEventListener('message', async (event) => {\n\t\t\tlet message;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.type !== 'command') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { id, method, args, siteSlug } = message;\n\t\t\ttry {\n\t\t\t\tconst value = await handleCommand(\n\t\t\t\t\tconfig,\n\t\t\t\t\tmethod,\n\t\t\t\t\targs || [],\n\t\t\t\t\tsiteSlug,\n\t\t\t\t\tport\n\t\t\t\t);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(JSON.stringify({ id, type: 'response', value }));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg =\n\t\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'response',\n\t\t\t\t\t\t\terror: errorMsg,\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener('close', () => {\n\t\t\tws = null;\n\t\t\tscheduleReconnect();\n\t\t});\n\n\t\tws.addEventListener('error', () => {\n\t\t\t// Error will be followed by close event,\n\t\t\t// which handles reconnect\n\t\t});\n\t}\n\n\tfunction scheduleReconnect() {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\treconnectTimer = setTimeout(connect, RECONNECT_INTERVAL_MS);\n\t}\n\n\tconnect();\n\n\treturn {\n\t\tnotifySitesChanged: () => {\n\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tsendSitesRegistration(ws);\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tstopped = true;\n\t\t\tif (reconnectTimer !== null) {\n\t\t\t\tclearTimeout(reconnectTimer);\n\t\t\t\treconnectTimer = null;\n\t\t\t}\n\t\t\tif (ws) {\n\t\t\t\tws.close();\n\t\t\t\tws = null;\n\t\t\t}\n\t\t},\n\t};\n}\n\nasync function handleCommand(\n\tconfig: PlaygroundBridgeConfig,\n\tmethod: string,\n\targs: unknown[],\n\tsiteSlug: string,\n\tport: number\n): Promise<unknown> {\n\tif (method === '__open_site_in_new_tab') {\n\t\tconst url = new URL(window.location.href);\n\t\turl.searchParams.set('mcp', 'yes');\n\t\turl.searchParams.set('mcp-port', String(port));\n\t\turl.searchParams.set('site-slug', siteSlug);\n\t\tconst newWindow = window.open(url.toString(), '_blank');\n\t\tif (!newWindow) {\n\t\t\tthrow new Error(\n\t\t\t\t'Pop-up blocked by browser. The user ' +\n\t\t\t\t\t'must allow pop-ups for this site.'\n\t\t\t);\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method === '__rename_site') {\n\t\tconst [newName] = args as [string];\n\t\tawait config.rename(newName);\n\t\treturn true;\n\t}\n\n\tif (method === '__save_site') {\n\t\treturn await config.saveInBrowser();\n\t}\n\n\tconst playgroundClient = config.getClient();\n\tif (!playgroundClient) {\n\t\tthrow new Error(`No active client for site: ${siteSlug}`);\n\t}\n\n\tconst client = createToolClient(playgroundClient);\n\tconst fn = client[method as keyof ToolClient];\n\tif (typeof fn !== 'function') {\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t}\n\treturn await (fn as (...a: unknown[]) => Promise<unknown>)(...args);\n}\n","/**\n * WebMCP registration for WordPress Playground.\n *\n * Registers playground tools with `navigator.modelContext` (the\n * Chrome WebMCP API) so that browser-side AI agents can interact\n * with the running Playground site.\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tformatStorageLabel,\n\tparamsToJsonSchema,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport { toolExecutors, createToolClient } from './tools/tool-executors';\nimport type { PlaygroundBridgeConfig } from './bridge-client';\n\nconst siteToolDefinitions = getSiteToolDefinitions();\n\n// -- WebMCP type declarations --\n\ninterface ModelContextTool {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: Record<string, unknown>;\n\texecute: (\n\t\tinput: Record<string, unknown>,\n\t\tclient: ModelContextClient\n\t) => Promise<unknown>;\n\tannotations?: { readOnlyHint?: boolean; destructiveHint?: boolean };\n}\n\ninterface ModelContextClient {\n\trequestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContext {\n\tprovideContext(options: { tools: ModelContextTool[] }): void;\n\tregisterTool(\n\t\ttool: ModelContextTool,\n\t\toptions?: { signal?: AbortSignal }\n\t): void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Navigator {\n\t\tmodelContext?: ModelContext;\n\t}\n}\n\n// -- Registration --\n\nlet registrationController: AbortController | null = null;\n\nfunction getActiveSite(config: PlaygroundBridgeConfig) {\n\tconst sites = config.list();\n\tconst active = sites.find((s) => s.isActive);\n\tif (!active) {\n\t\tthrow new Error('No active Playground site');\n\t}\n\treturn active;\n}\n\nexport function registerWebMCPTools(config: PlaygroundBridgeConfig): void {\n\tif (typeof navigator === 'undefined' || !navigator.modelContext) {\n\t\treturn;\n\t}\n\n\t// Abort any previous registration before re-registering.\n\tregistrationController?.abort();\n\tregistrationController = new AbortController();\n\tconst signal = registrationController.signal;\n\n\tfunction getActiveClient(): PlaygroundClient {\n\t\tconst client = config.getClient();\n\t\tif (!client) {\n\t\t\tthrow new Error('No client for active site');\n\t\t}\n\t\treturn client;\n\t}\n\n\t// Per-site tools\n\tconst tools: ModelContextTool[] = Object.entries(toolDefinitions).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\tdescription: def.description,\n\t\t\tinputSchema: paramsToJsonSchema(def.params),\n\t\t\tannotations: def.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst executor = toolExecutors[name];\n\t\t\t\t\tif (!executor) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\terror: `No executor for \"${name}\"`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst adapter = createToolClient(getActiveClient());\n\t\t\t\t\treturn await executor(adapter, input);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${def.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t);\n\n\t// Site management tools\n\ttools.push(...createSiteManagementTools(config));\n\n\tfor (const tool of tools) {\n\t\tnavigator.modelContext.registerTool(tool, { signal });\n\t}\n}\n\nfunction createSiteManagementTools(\n\tconfig: PlaygroundBridgeConfig\n): ModelContextTool[] {\n\tconst listDef = siteToolDefinitions['playground_list_sites'];\n\tconst saveDef = siteToolDefinitions['playground_save_in_browser'];\n\tconst renameDef = siteToolDefinitions['playground_rename_site'];\n\tconst websiteUrlDef = siteToolDefinitions['playground_get_website_url'];\n\n\treturn [\n\t\t{\n\t\t\tname: 'playground_list_sites',\n\t\t\tdescription: listDef.description,\n\t\t\tannotations: listDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tconnectedTabs: 1,\n\t\t\t\t\t\tsites: config.list().map((s) => ({\n\t\t\t\t\t\t\tsiteId: s.slug,\n\t\t\t\t\t\t\tname: s.name,\n\t\t\t\t\t\t\tstorage: formatStorageLabel(s.storage),\n\t\t\t\t\t\t\tisActive: s.isActive,\n\t\t\t\t\t\t})),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${listDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_save_in_browser',\n\t\t\tdescription: saveDef.description,\n\t\t\tannotations: saveDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst site = getActiveSite(config);\n\t\t\t\t\tconst storage = formatStorageLabel(site.storage);\n\t\t\t\t\tif (storage !== 'temporary') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\talreadySaved: true,\n\t\t\t\t\t\t\tsiteId: site.slug,\n\t\t\t\t\t\t\tname: site.name,\n\t\t\t\t\t\t\tstorage,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst saved = await config.saveInBrowser();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\talreadySaved: false,\n\t\t\t\t\t\tsiteId: saved.slug,\n\t\t\t\t\t\tname: site.name ?? saved.slug,\n\t\t\t\t\t\tstorage: formatStorageLabel(saved.storage),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${saveDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_rename_site',\n\t\t\tdescription: renameDef.description,\n\t\t\tinputSchema: paramsToJsonSchema(renameDef.params),\n\t\t\tannotations: renameDef.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst newName = input['newName'] as string;\n\t\t\t\t\tconst sites = config.list();\n\t\t\t\t\tconst activeSite = sites.find((s) => s.isActive);\n\t\t\t\t\tawait config.rename(newName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tsiteId: activeSite?.slug,\n\t\t\t\t\t\tnewName,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${renameDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_get_website_url',\n\t\t\tdescription: websiteUrlDef.description,\n\t\t\tannotations: websiteUrlDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl: window.location.href,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${websiteUrlDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t];\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient","siteToolDefinitions","getSiteToolDefinitions","registrationController","getActiveSite","active","s","registerWebMCPTools","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","tool","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"mappings":"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"}
package/client.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { startMcpBridge } from './bridge-client';
2
- export type { PlaygroundConfig } from './bridge-client';
3
- export type { McpBridgeHandle } from './bridge-client';
2
+ export type { PlaygroundBridgeConfig, McpBridgeHandle } from './bridge-client';
3
+ export { registerWebMCPTools } from './webmcp';
package/client.js CHANGED
@@ -1,105 +1,240 @@
1
- import { c as h } from "./tool-executors-B9DCzwoD.js";
2
- const E = 5e3;
3
- function k(t, n) {
4
- const d = crypto.randomUUID();
5
- let e = null, a = "", i = null, p = !1;
6
- function c(o) {
7
- const s = t.getSites(), l = JSON.stringify(s);
8
- l !== a && (a = l, o.send(JSON.stringify({ type: "register", tabId: d, sites: s })));
1
+ import { c as _, t as N, a as x, s as y, b as S, f as w, g as $ } from "./tool-executors-SsQekZdW.js";
2
+ const P = 5e3;
3
+ function I(r, s) {
4
+ const a = crypto.randomUUID();
5
+ let e = null, n = "", t = null, c = !1;
6
+ function o(u) {
7
+ const l = r.list(), p = JSON.stringify(l);
8
+ p !== n && (n = p, u.send(JSON.stringify({ type: "register", tabId: a, sites: l })));
9
9
  }
10
- async function r() {
10
+ async function i() {
11
11
  try {
12
- const o = await fetch(
13
- `http://127.0.0.1:${n}/bridge-token`
12
+ const u = await fetch(
13
+ `http://127.0.0.1:${s}/bridge-token`
14
14
  );
15
- if (!o.ok) {
16
- f();
15
+ if (!u.ok) {
16
+ d();
17
17
  return;
18
18
  }
19
- const { token: s } = await o.json();
20
- e = new WebSocket(`ws://127.0.0.1:${n}?token=${s}`);
19
+ const { token: l } = await u.json();
20
+ e = new WebSocket(`ws://127.0.0.1:${s}?token=${l}`);
21
21
  } catch {
22
- f();
22
+ d();
23
23
  return;
24
24
  }
25
25
  e.addEventListener("open", () => {
26
- var o;
27
- a = "", c(e), (o = t.onConnect) == null || o.call(t);
28
- }), e.addEventListener("message", async (o) => {
29
- let s;
26
+ var u;
27
+ n = "", o(e), (u = r.onConnect) == null || u.call(r);
28
+ }), e.addEventListener("message", async (u) => {
29
+ let l;
30
30
  try {
31
- s = JSON.parse(o.data);
31
+ l = JSON.parse(u.data);
32
32
  } catch {
33
33
  return;
34
34
  }
35
- if (s.type !== "command")
35
+ if (l.type !== "command")
36
36
  return;
37
- const { id: l, method: w, args: y, siteSlug: S } = s;
37
+ const { id: p, method: h, args: v, siteSlug: b } = l;
38
38
  try {
39
- const u = await N(
40
- t,
41
- w,
42
- y || [],
43
- S,
44
- n
39
+ const f = await T(
40
+ r,
41
+ h,
42
+ v || [],
43
+ b,
44
+ s
45
45
  );
46
- (e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(JSON.stringify({ id: l, type: "response", value: u }));
47
- } catch (u) {
48
- const m = u instanceof Error ? u.message : String(u);
46
+ (e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(JSON.stringify({ id: p, type: "response", value: f }));
47
+ } catch (f) {
48
+ const E = f instanceof Error ? f.message : String(f);
49
49
  (e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(
50
50
  JSON.stringify({
51
- id: l,
51
+ id: p,
52
52
  type: "response",
53
- error: m
53
+ error: E
54
54
  })
55
55
  );
56
56
  }
57
57
  }), e.addEventListener("close", () => {
58
- e = null, f();
58
+ e = null, d();
59
59
  }), e.addEventListener("error", () => {
60
60
  });
61
61
  }
62
- function f() {
63
- p || (i = setTimeout(r, E));
62
+ function d() {
63
+ c || (t = setTimeout(i, P));
64
64
  }
65
- return r(), {
65
+ return i(), {
66
66
  notifySitesChanged: () => {
67
- (e == null ? void 0 : e.readyState) === WebSocket.OPEN && c(e);
67
+ (e == null ? void 0 : e.readyState) === WebSocket.OPEN && o(e);
68
68
  },
69
69
  stop: () => {
70
- p = !0, i !== null && (clearTimeout(i), i = null), e && (e.close(), e = null);
70
+ c = !0, t !== null && (clearTimeout(t), t = null), e && (e.close(), e = null);
71
71
  }
72
72
  };
73
73
  }
74
- async function N(t, n, d, e, a) {
75
- if (n === "__open_site") {
76
- const r = new URL(window.location.href);
77
- if (r.searchParams.set("mcp", "yes"), r.searchParams.set("mcp-port", String(a)), r.searchParams.set("site-slug", e), !window.open(r.toString(), "_blank"))
74
+ async function T(r, s, a, e, n) {
75
+ if (s === "__open_site_in_new_tab") {
76
+ const i = new URL(window.location.href);
77
+ if (i.searchParams.set("mcp", "yes"), i.searchParams.set("mcp-port", String(n)), i.searchParams.set("site-slug", e), !window.open(i.toString(), "_blank"))
78
78
  throw new Error(
79
79
  "Pop-up blocked by browser. The user must allow pop-ups for this site."
80
80
  );
81
81
  return !0;
82
82
  }
83
- if (n === "__rename_site") {
84
- if (!t.renameSite)
85
- throw new Error("renameSite not configured");
86
- const [r] = d;
87
- return await t.renameSite(e, r), !0;
83
+ if (s === "__rename_site") {
84
+ const [i] = a;
85
+ return await r.rename(i), !0;
88
86
  }
89
- if (n === "__save_site") {
90
- if (!t.saveSite)
91
- throw new Error("saveSite not configured");
92
- return await t.saveSite(e);
93
- }
94
- const i = t.getPlaygroundClient(e);
95
- if (!i)
87
+ if (s === "__save_site")
88
+ return await r.saveInBrowser();
89
+ const t = r.getClient();
90
+ if (!t)
96
91
  throw new Error(`No active client for site: ${e}`);
97
- const c = h(i)[n];
98
- if (typeof c != "function")
99
- throw new Error(`Unknown method: ${n}`);
100
- return await c(...d);
92
+ const o = _(t)[s];
93
+ if (typeof o != "function")
94
+ throw new Error(`Unknown method: ${s}`);
95
+ return await o(...a);
96
+ }
97
+ const g = $();
98
+ let m = null;
99
+ function C(r) {
100
+ const a = r.list().find((e) => e.isActive);
101
+ if (!a)
102
+ throw new Error("No active Playground site");
103
+ return a;
104
+ }
105
+ function A(r) {
106
+ if (typeof navigator > "u" || !navigator.modelContext)
107
+ return;
108
+ m == null || m.abort(), m = new AbortController();
109
+ const s = m.signal;
110
+ function a() {
111
+ const n = r.getClient();
112
+ if (!n)
113
+ throw new Error("No client for active site");
114
+ return n;
115
+ }
116
+ const e = Object.entries(N).map(
117
+ ([n, t]) => ({
118
+ name: n,
119
+ description: t.description,
120
+ inputSchema: S(t.params),
121
+ annotations: t.annotations,
122
+ execute: async (c) => {
123
+ try {
124
+ const o = x[n];
125
+ if (!o)
126
+ return {
127
+ error: `No executor for "${n}"`
128
+ };
129
+ const i = _(a());
130
+ return await o(i, c);
131
+ } catch (o) {
132
+ return {
133
+ error: `${t.errorPrefix}: ${y(o)}`
134
+ };
135
+ }
136
+ }
137
+ })
138
+ );
139
+ e.push(...k(r));
140
+ for (const n of e)
141
+ navigator.modelContext.registerTool(n, { signal: s });
142
+ }
143
+ function k(r) {
144
+ const s = g.playground_list_sites, a = g.playground_save_in_browser, e = g.playground_rename_site, n = g.playground_get_website_url;
145
+ return [
146
+ {
147
+ name: "playground_list_sites",
148
+ description: s.description,
149
+ annotations: s.annotations,
150
+ execute: async () => {
151
+ try {
152
+ return {
153
+ connectedTabs: 1,
154
+ sites: r.list().map((t) => ({
155
+ siteId: t.slug,
156
+ name: t.name,
157
+ storage: w(t.storage),
158
+ isActive: t.isActive
159
+ }))
160
+ };
161
+ } catch (t) {
162
+ return {
163
+ error: `${s.errorPrefix}: ${y(t)}`
164
+ };
165
+ }
166
+ }
167
+ },
168
+ {
169
+ name: "playground_save_in_browser",
170
+ description: a.description,
171
+ annotations: a.annotations,
172
+ execute: async () => {
173
+ try {
174
+ const t = C(r), c = w(t.storage);
175
+ if (c !== "temporary")
176
+ return {
177
+ success: !0,
178
+ alreadySaved: !0,
179
+ siteId: t.slug,
180
+ name: t.name,
181
+ storage: c
182
+ };
183
+ const o = await r.saveInBrowser();
184
+ return {
185
+ success: !0,
186
+ alreadySaved: !1,
187
+ siteId: o.slug,
188
+ name: t.name ?? o.slug,
189
+ storage: w(o.storage)
190
+ };
191
+ } catch (t) {
192
+ return {
193
+ error: `${a.errorPrefix}: ${y(t)}`
194
+ };
195
+ }
196
+ }
197
+ },
198
+ {
199
+ name: "playground_rename_site",
200
+ description: e.description,
201
+ inputSchema: S(e.params),
202
+ annotations: e.annotations,
203
+ execute: async (t) => {
204
+ try {
205
+ const c = t.newName, i = r.list().find((d) => d.isActive);
206
+ return await r.rename(c), {
207
+ success: !0,
208
+ siteId: i == null ? void 0 : i.slug,
209
+ newName: c
210
+ };
211
+ } catch (c) {
212
+ return {
213
+ error: `${e.errorPrefix}: ${y(c)}`
214
+ };
215
+ }
216
+ }
217
+ },
218
+ {
219
+ name: "playground_get_website_url",
220
+ description: n.description,
221
+ annotations: n.annotations,
222
+ execute: async () => {
223
+ try {
224
+ return {
225
+ url: window.location.href
226
+ };
227
+ } catch (t) {
228
+ return {
229
+ error: `${n.errorPrefix}: ${y(t)}`
230
+ };
231
+ }
232
+ }
233
+ }
234
+ ];
101
235
  }
102
236
  export {
103
- k as startMcpBridge
237
+ A as registerWebMCPTools,
238
+ I as startMcpBridge
104
239
  };
105
240
  //# sourceMappingURL=client.js.map
package/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../../packages/playground/mcp/src/bridge-client.ts"],"sourcesContent":["import type { PlaygroundClient } from '@wp-playground/remote';\nimport { createToolClient } from './tools/tool-executors';\nimport type { ToolClient } from './tools/tool-executors';\n\n/**\n * Shared configuration for the MCP bridge client and WebMCP.\n *\n * Both transports need the same callbacks to interact with\n * the Playground site list and active client.\n */\nexport interface PlaygroundConfig {\n\tgetSites: () => Array<{\n\t\tslug: string;\n\t\tname: string;\n\t\tstorage: string;\n\t\tisActive: boolean;\n\t}>;\n\tgetPlaygroundClient: (siteSlug: string) => PlaygroundClient | undefined;\n\trenameSite?: (siteSlug: string, newName: string) => Promise<void>;\n\tsaveSite?: (siteSlug: string) => Promise<{ slug: string; storage: string }>;\n\tonConnect?: () => void;\n}\n\nexport interface McpBridgeHandle {\n\tnotifySitesChanged: () => void;\n\tstop: () => void;\n}\n\nconst RECONNECT_INTERVAL_MS = 5000;\n\nexport function startMcpBridge(\n\tconfig: PlaygroundConfig,\n\tport: number\n): McpBridgeHandle {\n\tconst tabId = crypto.randomUUID();\n\tlet ws: WebSocket | null = null;\n\tlet previousSitesSerialized = '';\n\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet stopped = false;\n\n\tfunction sendSitesRegistration(socket: WebSocket) {\n\t\tconst sites = config.getSites();\n\t\tconst serialized = JSON.stringify(sites);\n\t\tif (serialized === previousSitesSerialized) {\n\t\t\treturn;\n\t\t}\n\t\tpreviousSitesSerialized = serialized;\n\t\tsocket.send(JSON.stringify({ type: 'register', tabId, sites }));\n\t}\n\n\tasync function connect() {\n\t\ttry {\n\t\t\tconst response = await fetch(\n\t\t\t\t`http://127.0.0.1:${port}/bridge-token`\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tscheduleReconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst { token } = await response.json();\n\t\t\tws = new WebSocket(`ws://127.0.0.1:${port}?token=${token}`);\n\t\t} catch {\n\t\t\tscheduleReconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tws.addEventListener('open', () => {\n\t\t\tpreviousSitesSerialized = '';\n\t\t\tsendSitesRegistration(ws!);\n\t\t\tconfig.onConnect?.();\n\t\t});\n\n\t\tws.addEventListener('message', async (event) => {\n\t\t\tlet message;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.type !== 'command') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { id, method, args, siteSlug } = message;\n\t\t\ttry {\n\t\t\t\tconst value = await handleCommand(\n\t\t\t\t\tconfig,\n\t\t\t\t\tmethod,\n\t\t\t\t\targs || [],\n\t\t\t\t\tsiteSlug,\n\t\t\t\t\tport\n\t\t\t\t);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(JSON.stringify({ id, type: 'response', value }));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg =\n\t\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'response',\n\t\t\t\t\t\t\terror: errorMsg,\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener('close', () => {\n\t\t\tws = null;\n\t\t\tscheduleReconnect();\n\t\t});\n\n\t\tws.addEventListener('error', () => {\n\t\t\t// Error will be followed by close event,\n\t\t\t// which handles reconnect\n\t\t});\n\t}\n\n\tfunction scheduleReconnect() {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\treconnectTimer = setTimeout(connect, RECONNECT_INTERVAL_MS);\n\t}\n\n\tconnect();\n\n\treturn {\n\t\tnotifySitesChanged: () => {\n\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tsendSitesRegistration(ws);\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tstopped = true;\n\t\t\tif (reconnectTimer !== null) {\n\t\t\t\tclearTimeout(reconnectTimer);\n\t\t\t\treconnectTimer = null;\n\t\t\t}\n\t\t\tif (ws) {\n\t\t\t\tws.close();\n\t\t\t\tws = null;\n\t\t\t}\n\t\t},\n\t};\n}\n\nasync function handleCommand(\n\tconfig: PlaygroundConfig,\n\tmethod: string,\n\targs: unknown[],\n\tsiteSlug: string,\n\tport: number\n): Promise<unknown> {\n\tif (method === '__open_site') {\n\t\tconst url = new URL(window.location.href);\n\t\turl.searchParams.set('mcp', 'yes');\n\t\turl.searchParams.set('mcp-port', String(port));\n\t\turl.searchParams.set('site-slug', siteSlug);\n\t\tconst newWindow = window.open(url.toString(), '_blank');\n\t\tif (!newWindow) {\n\t\t\tthrow new Error(\n\t\t\t\t'Pop-up blocked by browser. The user ' +\n\t\t\t\t\t'must allow pop-ups for this site.'\n\t\t\t);\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method === '__rename_site') {\n\t\tif (!config.renameSite) {\n\t\t\tthrow new Error('renameSite not configured');\n\t\t}\n\t\tconst [newName] = args as [string];\n\t\tawait config.renameSite(siteSlug, newName);\n\t\treturn true;\n\t}\n\n\tif (method === '__save_site') {\n\t\tif (!config.saveSite) {\n\t\t\tthrow new Error('saveSite not configured');\n\t\t}\n\t\treturn await config.saveSite(siteSlug);\n\t}\n\n\tconst playgroundClient = config.getPlaygroundClient(siteSlug);\n\tif (!playgroundClient) {\n\t\tthrow new Error(`No active client for site: ${siteSlug}`);\n\t}\n\n\tconst client = createToolClient(playgroundClient);\n\tconst fn = client[method as keyof ToolClient];\n\tif (typeof fn !== 'function') {\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t}\n\treturn await (fn as (...a: unknown[]) => Promise<unknown>)(...args);\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sources":["../../../../packages/playground/mcp/src/bridge-client.ts","../../../../packages/playground/mcp/src/webmcp.ts"],"sourcesContent":["import type { PlaygroundClient } from '@wp-playground/remote';\nimport { createToolClient } from './tools/tool-executors';\nimport type { ToolClient } from './tools/tool-executors';\n\n/**\n * Configuration accepted by `startMcpBridge`. Only includes the\n * methods the bridge actually calls — callers can pass any wider\n * object (e.g. the full site-management API) and TypeScript will\n * accept it structurally.\n */\nexport interface PlaygroundBridgeConfig {\n\tlist(): Array<{\n\t\tslug: string;\n\t\tname: string;\n\t\tstorage: string;\n\t\tisActive: boolean;\n\t}>;\n\tgetClient(): PlaygroundClient | undefined;\n\trename(newName: string): Promise<void>;\n\tsaveInBrowser(): Promise<{ slug: string; storage: string }>;\n\tonConnect?: () => void;\n}\n\nexport interface McpBridgeHandle {\n\tnotifySitesChanged: () => void;\n\tstop: () => void;\n}\n\nconst RECONNECT_INTERVAL_MS = 5000;\n\nexport function startMcpBridge(\n\tconfig: PlaygroundBridgeConfig,\n\tport: number\n): McpBridgeHandle {\n\tconst tabId = crypto.randomUUID();\n\tlet ws: WebSocket | null = null;\n\tlet previousSitesSerialized = '';\n\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet stopped = false;\n\n\tfunction sendSitesRegistration(socket: WebSocket) {\n\t\tconst sites = config.list();\n\t\tconst serialized = JSON.stringify(sites);\n\t\tif (serialized === previousSitesSerialized) {\n\t\t\treturn;\n\t\t}\n\t\tpreviousSitesSerialized = serialized;\n\t\tsocket.send(JSON.stringify({ type: 'register', tabId, sites }));\n\t}\n\n\tasync function connect() {\n\t\ttry {\n\t\t\tconst response = await fetch(\n\t\t\t\t`http://127.0.0.1:${port}/bridge-token`\n\t\t\t);\n\t\t\tif (!response.ok) {\n\t\t\t\tscheduleReconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst { token } = await response.json();\n\t\t\tws = new WebSocket(`ws://127.0.0.1:${port}?token=${token}`);\n\t\t} catch {\n\t\t\tscheduleReconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tws.addEventListener('open', () => {\n\t\t\tpreviousSitesSerialized = '';\n\t\t\tsendSitesRegistration(ws!);\n\t\t\tconfig.onConnect?.();\n\t\t});\n\n\t\tws.addEventListener('message', async (event) => {\n\t\t\tlet message;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.type !== 'command') {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { id, method, args, siteSlug } = message;\n\t\t\ttry {\n\t\t\t\tconst value = await handleCommand(\n\t\t\t\t\tconfig,\n\t\t\t\t\tmethod,\n\t\t\t\t\targs || [],\n\t\t\t\t\tsiteSlug,\n\t\t\t\t\tport\n\t\t\t\t);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(JSON.stringify({ id, type: 'response', value }));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg =\n\t\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\t\tws.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'response',\n\t\t\t\t\t\t\terror: errorMsg,\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener('close', () => {\n\t\t\tws = null;\n\t\t\tscheduleReconnect();\n\t\t});\n\n\t\tws.addEventListener('error', () => {\n\t\t\t// Error will be followed by close event,\n\t\t\t// which handles reconnect\n\t\t});\n\t}\n\n\tfunction scheduleReconnect() {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\treconnectTimer = setTimeout(connect, RECONNECT_INTERVAL_MS);\n\t}\n\n\tconnect();\n\n\treturn {\n\t\tnotifySitesChanged: () => {\n\t\t\tif (ws?.readyState === WebSocket.OPEN) {\n\t\t\t\tsendSitesRegistration(ws);\n\t\t\t}\n\t\t},\n\t\tstop: () => {\n\t\t\tstopped = true;\n\t\t\tif (reconnectTimer !== null) {\n\t\t\t\tclearTimeout(reconnectTimer);\n\t\t\t\treconnectTimer = null;\n\t\t\t}\n\t\t\tif (ws) {\n\t\t\t\tws.close();\n\t\t\t\tws = null;\n\t\t\t}\n\t\t},\n\t};\n}\n\nasync function handleCommand(\n\tconfig: PlaygroundBridgeConfig,\n\tmethod: string,\n\targs: unknown[],\n\tsiteSlug: string,\n\tport: number\n): Promise<unknown> {\n\tif (method === '__open_site_in_new_tab') {\n\t\tconst url = new URL(window.location.href);\n\t\turl.searchParams.set('mcp', 'yes');\n\t\turl.searchParams.set('mcp-port', String(port));\n\t\turl.searchParams.set('site-slug', siteSlug);\n\t\tconst newWindow = window.open(url.toString(), '_blank');\n\t\tif (!newWindow) {\n\t\t\tthrow new Error(\n\t\t\t\t'Pop-up blocked by browser. The user ' +\n\t\t\t\t\t'must allow pop-ups for this site.'\n\t\t\t);\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method === '__rename_site') {\n\t\tconst [newName] = args as [string];\n\t\tawait config.rename(newName);\n\t\treturn true;\n\t}\n\n\tif (method === '__save_site') {\n\t\treturn await config.saveInBrowser();\n\t}\n\n\tconst playgroundClient = config.getClient();\n\tif (!playgroundClient) {\n\t\tthrow new Error(`No active client for site: ${siteSlug}`);\n\t}\n\n\tconst client = createToolClient(playgroundClient);\n\tconst fn = client[method as keyof ToolClient];\n\tif (typeof fn !== 'function') {\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t}\n\treturn await (fn as (...a: unknown[]) => Promise<unknown>)(...args);\n}\n","/**\n * WebMCP registration for WordPress Playground.\n *\n * Registers playground tools with `navigator.modelContext` (the\n * Chrome WebMCP API) so that browser-side AI agents can interact\n * with the running Playground site.\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tformatStorageLabel,\n\tparamsToJsonSchema,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport { toolExecutors, createToolClient } from './tools/tool-executors';\nimport type { PlaygroundBridgeConfig } from './bridge-client';\n\nconst siteToolDefinitions = getSiteToolDefinitions();\n\n// -- WebMCP type declarations --\n\ninterface ModelContextTool {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: Record<string, unknown>;\n\texecute: (\n\t\tinput: Record<string, unknown>,\n\t\tclient: ModelContextClient\n\t) => Promise<unknown>;\n\tannotations?: { readOnlyHint?: boolean; destructiveHint?: boolean };\n}\n\ninterface ModelContextClient {\n\trequestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContext {\n\tprovideContext(options: { tools: ModelContextTool[] }): void;\n\tregisterTool(\n\t\ttool: ModelContextTool,\n\t\toptions?: { signal?: AbortSignal }\n\t): void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Navigator {\n\t\tmodelContext?: ModelContext;\n\t}\n}\n\n// -- Registration --\n\nlet registrationController: AbortController | null = null;\n\nfunction getActiveSite(config: PlaygroundBridgeConfig) {\n\tconst sites = config.list();\n\tconst active = sites.find((s) => s.isActive);\n\tif (!active) {\n\t\tthrow new Error('No active Playground site');\n\t}\n\treturn active;\n}\n\nexport function registerWebMCPTools(config: PlaygroundBridgeConfig): void {\n\tif (typeof navigator === 'undefined' || !navigator.modelContext) {\n\t\treturn;\n\t}\n\n\t// Abort any previous registration before re-registering.\n\tregistrationController?.abort();\n\tregistrationController = new AbortController();\n\tconst signal = registrationController.signal;\n\n\tfunction getActiveClient(): PlaygroundClient {\n\t\tconst client = config.getClient();\n\t\tif (!client) {\n\t\t\tthrow new Error('No client for active site');\n\t\t}\n\t\treturn client;\n\t}\n\n\t// Per-site tools\n\tconst tools: ModelContextTool[] = Object.entries(toolDefinitions).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\tdescription: def.description,\n\t\t\tinputSchema: paramsToJsonSchema(def.params),\n\t\t\tannotations: def.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst executor = toolExecutors[name];\n\t\t\t\t\tif (!executor) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\terror: `No executor for \"${name}\"`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst adapter = createToolClient(getActiveClient());\n\t\t\t\t\treturn await executor(adapter, input);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${def.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t);\n\n\t// Site management tools\n\ttools.push(...createSiteManagementTools(config));\n\n\tfor (const tool of tools) {\n\t\tnavigator.modelContext.registerTool(tool, { signal });\n\t}\n}\n\nfunction createSiteManagementTools(\n\tconfig: PlaygroundBridgeConfig\n): ModelContextTool[] {\n\tconst listDef = siteToolDefinitions['playground_list_sites'];\n\tconst saveDef = siteToolDefinitions['playground_save_in_browser'];\n\tconst renameDef = siteToolDefinitions['playground_rename_site'];\n\tconst websiteUrlDef = siteToolDefinitions['playground_get_website_url'];\n\n\treturn [\n\t\t{\n\t\t\tname: 'playground_list_sites',\n\t\t\tdescription: listDef.description,\n\t\t\tannotations: listDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tconnectedTabs: 1,\n\t\t\t\t\t\tsites: config.list().map((s) => ({\n\t\t\t\t\t\t\tsiteId: s.slug,\n\t\t\t\t\t\t\tname: s.name,\n\t\t\t\t\t\t\tstorage: formatStorageLabel(s.storage),\n\t\t\t\t\t\t\tisActive: s.isActive,\n\t\t\t\t\t\t})),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${listDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_save_in_browser',\n\t\t\tdescription: saveDef.description,\n\t\t\tannotations: saveDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst site = getActiveSite(config);\n\t\t\t\t\tconst storage = formatStorageLabel(site.storage);\n\t\t\t\t\tif (storage !== 'temporary') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\talreadySaved: true,\n\t\t\t\t\t\t\tsiteId: site.slug,\n\t\t\t\t\t\t\tname: site.name,\n\t\t\t\t\t\t\tstorage,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst saved = await config.saveInBrowser();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\talreadySaved: false,\n\t\t\t\t\t\tsiteId: saved.slug,\n\t\t\t\t\t\tname: site.name ?? saved.slug,\n\t\t\t\t\t\tstorage: formatStorageLabel(saved.storage),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${saveDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_rename_site',\n\t\t\tdescription: renameDef.description,\n\t\t\tinputSchema: paramsToJsonSchema(renameDef.params),\n\t\t\tannotations: renameDef.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst newName = input['newName'] as string;\n\t\t\t\t\tconst sites = config.list();\n\t\t\t\t\tconst activeSite = sites.find((s) => s.isActive);\n\t\t\t\t\tawait config.rename(newName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tsiteId: activeSite?.slug,\n\t\t\t\t\t\tnewName,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${renameDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_get_website_url',\n\t\t\tdescription: websiteUrlDef.description,\n\t\t\tannotations: websiteUrlDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl: window.location.href,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${websiteUrlDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t];\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient","siteToolDefinitions","getSiteToolDefinitions","registrationController","getActiveSite","active","s","registerWebMCPTools","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","tool","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"mappings":";AA4BA,MAAMA,IAAwB;AAEvB,SAASC,EACfC,GACAC,GACkB;AAClB,QAAMC,IAAQ,OAAO,WAAA;AACrB,MAAIC,IAAuB,MACvBC,IAA0B,IAC1BC,IAAuD,MACvDC,IAAU;AAEd,WAASC,EAAsBC,GAAmB;AACjD,UAAMC,IAAQT,EAAO,KAAA,GACfU,IAAa,KAAK,UAAUD,CAAK;AACvC,IAAIC,MAAeN,MAGnBA,IAA0BM,GAC1BF,EAAO,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,OAAAN,GAAO,OAAAO,EAAA,CAAO,CAAC;AAAA,EAC/D;AAEA,iBAAeE,IAAU;AACxB,QAAI;AACH,YAAMC,IAAW,MAAM;AAAA,QACtB,oBAAoBX,CAAI;AAAA,MAAA;AAEzB,UAAI,CAACW,EAAS,IAAI;AACjB,QAAAC,EAAA;AACA;AAAA,MACD;AACA,YAAM,EAAE,OAAAC,EAAA,IAAU,MAAMF,EAAS,KAAA;AACjC,MAAAT,IAAK,IAAI,UAAU,kBAAkBF,CAAI,UAAUa,CAAK,EAAE;AAAA,IAC3D,QAAQ;AACP,MAAAD,EAAA;AACA;AAAA,IACD;AAEA,IAAAV,EAAG,iBAAiB,QAAQ,MAAM;;AACjC,MAAAC,IAA0B,IAC1BG,EAAsBJ,CAAG,IACzBY,IAAAf,EAAO,cAAP,QAAAe,EAAA,KAAAf;AAAA,IACD,CAAC,GAEDG,EAAG,iBAAiB,WAAW,OAAOa,MAAU;AAC/C,UAAIC;AACJ,UAAI;AACH,QAAAA,IAAU,KAAK,MAAMD,EAAM,IAAc;AAAA,MAC1C,QAAQ;AACP;AAAA,MACD;AACA,UAAIC,EAAQ,SAAS;AACpB;AAGD,YAAM,EAAE,IAAAC,GAAI,QAAAC,GAAQ,MAAAC,GAAM,UAAAC,MAAaJ;AACvC,UAAI;AACH,cAAMK,IAAQ,MAAMC;AAAA,UACnBvB;AAAA,UACAmB;AAAA,UACAC,KAAQ,CAAA;AAAA,UACRC;AAAA,UACApB;AAAA,QAAA;AAED,SAAIE,KAAA,gBAAAA,EAAI,gBAAe,UAAU,QAChCA,EAAG,KAAK,KAAK,UAAU,EAAE,IAAAe,GAAI,MAAM,YAAY,OAAAI,EAAA,CAAO,CAAC;AAAA,MAEzD,SAASE,GAAO;AACf,cAAMC,IACLD,aAAiB,QAAQA,EAAM,UAAU,OAAOA,CAAK;AACtD,SAAIrB,KAAA,gBAAAA,EAAI,gBAAe,UAAU,QAChCA,EAAG;AAAA,UACF,KAAK,UAAU;AAAA,YACd,IAAAe;AAAA,YACA,MAAM;AAAA,YACN,OAAOO;AAAA,UAAA,CACP;AAAA,QAAA;AAAA,MAGJ;AAAA,IACD,CAAC,GAEDtB,EAAG,iBAAiB,SAAS,MAAM;AAClC,MAAAA,IAAK,MACLU,EAAA;AAAA,IACD,CAAC,GAEDV,EAAG,iBAAiB,SAAS,MAAM;AAAA,IAGnC,CAAC;AAAA,EACF;AAEA,WAASU,IAAoB;AAC5B,IAAIP,MAGJD,IAAiB,WAAWM,GAASb,CAAqB;AAAA,EAC3D;AAEA,SAAAa,EAAA,GAEO;AAAA,IACN,oBAAoB,MAAM;AACzB,OAAIR,KAAA,gBAAAA,EAAI,gBAAe,UAAU,QAChCI,EAAsBJ,CAAE;AAAA,IAE1B;AAAA,IACA,MAAM,MAAM;AACX,MAAAG,IAAU,IACND,MAAmB,SACtB,aAAaA,CAAc,GAC3BA,IAAiB,OAEdF,MACHA,EAAG,MAAA,GACHA,IAAK;AAAA,IAEP;AAAA,EAAA;AAEF;AAEA,eAAeoB,EACdvB,GACAmB,GACAC,GACAC,GACApB,GACmB;AACnB,MAAIkB,MAAW,0BAA0B;AACxC,UAAMO,IAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AAKxC,QAJAA,EAAI,aAAa,IAAI,OAAO,KAAK,GACjCA,EAAI,aAAa,IAAI,YAAY,OAAOzB,CAAI,CAAC,GAC7CyB,EAAI,aAAa,IAAI,aAAaL,CAAQ,GAEtC,CADc,OAAO,KAAKK,EAAI,SAAA,GAAY,QAAQ;AAErD,YAAM,IAAI;AAAA,QACT;AAAA,MAAA;AAIF,WAAO;AAAA,EACR;AAEA,MAAIP,MAAW,iBAAiB;AAC/B,UAAM,CAACQ,CAAO,IAAIP;AAClB,iBAAMpB,EAAO,OAAO2B,CAAO,GACpB;AAAA,EACR;AAEA,MAAIR,MAAW;AACd,WAAO,MAAMnB,EAAO,cAAA;AAGrB,QAAM4B,IAAmB5B,EAAO,UAAA;AAChC,MAAI,CAAC4B;AACJ,UAAM,IAAI,MAAM,8BAA8BP,CAAQ,EAAE;AAIzD,QAAMQ,IADSC,EAAiBF,CAAgB,EAC9BT,CAA0B;AAC5C,MAAI,OAAOU,KAAO;AACjB,UAAM,IAAI,MAAM,mBAAmBV,CAAM,EAAE;AAE5C,SAAO,MAAOU,EAA6C,GAAGT,CAAI;AACnE;AC9KA,MAAMW,IAAsBC,EAAA;AAoC5B,IAAIC,IAAiD;AAErD,SAASC,EAAclC,GAAgC;AAEtD,QAAMmC,IADQnC,EAAO,KAAA,EACA,KAAK,CAACoC,MAAMA,EAAE,QAAQ;AAC3C,MAAI,CAACD;AACJ,UAAM,IAAI,MAAM,2BAA2B;AAE5C,SAAOA;AACR;AAEO,SAASE,EAAoBrC,GAAsC;AACzE,MAAI,OAAO,YAAc,OAAe,CAAC,UAAU;AAClD;AAID,EAAAiC,KAAA,QAAAA,EAAwB,SACxBA,IAAyB,IAAI,gBAAA;AAC7B,QAAMK,IAASL,EAAuB;AAEtC,WAASM,IAAoC;AAC5C,UAAMC,IAASxC,EAAO,UAAA;AACtB,QAAI,CAACwC;AACJ,YAAM,IAAI,MAAM,2BAA2B;AAE5C,WAAOA;AAAA,EACR;AAGA,QAAMC,IAA4B,OAAO,QAAQC,CAAe,EAAE;AAAA,IACjE,CAAC,CAACC,GAAMC,CAAG,OAAO;AAAA,MACjB,MAAAD;AAAA,MACA,aAAaC,EAAI;AAAA,MACjB,aAAaC,EAAmBD,EAAI,MAAM;AAAA,MAC1C,aAAaA,EAAI;AAAA,MACjB,SAAS,OAAOE,MAAU;AACzB,YAAI;AACH,gBAAMC,IAAWC,EAAcL,CAAI;AACnC,cAAI,CAACI;AACJ,mBAAO;AAAA,cACN,OAAO,oBAAoBJ,CAAI;AAAA,YAAA;AAGjC,gBAAMM,IAAUnB,EAAiBS,GAAiB;AAClD,iBAAO,MAAMQ,EAASE,GAASH,CAAK;AAAA,QACrC,SAAStB,GAAO;AACf,iBAAO;AAAA,YACN,OAAO,GAAGoB,EAAI,WAAW,KAAKM,EAAe1B,CAAK,CAAC;AAAA,UAAA;AAAA,QAErD;AAAA,MACD;AAAA,IAAA;AAAA,EACD;AAID,EAAAiB,EAAM,KAAK,GAAGU,EAA0BnD,CAAM,CAAC;AAE/C,aAAWoD,KAAQX;AAClB,cAAU,aAAa,aAAaW,GAAM,EAAE,QAAAd,GAAQ;AAEtD;AAEA,SAASa,EACRnD,GACqB;AACrB,QAAMqD,IAAUtB,EAAoB,uBAC9BuB,IAAUvB,EAAoB,4BAC9BwB,IAAYxB,EAAoB,wBAChCyB,IAAgBzB,EAAoB;AAE1C,SAAO;AAAA,IACN;AAAA,MACC,MAAM;AAAA,MACN,aAAasB,EAAQ;AAAA,MACrB,aAAaA,EAAQ;AAAA,MACrB,SAAS,YAAY;AACpB,YAAI;AACH,iBAAO;AAAA,YACN,eAAe;AAAA,YACf,OAAOrD,EAAO,KAAA,EAAO,IAAI,CAACoC,OAAO;AAAA,cAChC,QAAQA,EAAE;AAAA,cACV,MAAMA,EAAE;AAAA,cACR,SAASqB,EAAmBrB,EAAE,OAAO;AAAA,cACrC,UAAUA,EAAE;AAAA,YAAA,EACX;AAAA,UAAA;AAAA,QAEJ,SAASZ,GAAO;AACf,iBAAO;AAAA,YACN,OAAO,GAAG6B,EAAQ,WAAW,KAAKH,EAAe1B,CAAK,CAAC;AAAA,UAAA;AAAA,QAEzD;AAAA,MACD;AAAA,IAAA;AAAA,IAED;AAAA,MACC,MAAM;AAAA,MACN,aAAa8B,EAAQ;AAAA,MACrB,aAAaA,EAAQ;AAAA,MACrB,SAAS,YAAY;AACpB,YAAI;AACH,gBAAMI,IAAOxB,EAAclC,CAAM,GAC3B2D,IAAUF,EAAmBC,EAAK,OAAO;AAC/C,cAAIC,MAAY;AACf,mBAAO;AAAA,cACN,SAAS;AAAA,cACT,cAAc;AAAA,cACd,QAAQD,EAAK;AAAA,cACb,MAAMA,EAAK;AAAA,cACX,SAAAC;AAAA,YAAA;AAGF,gBAAMC,IAAQ,MAAM5D,EAAO,cAAA;AAC3B,iBAAO;AAAA,YACN,SAAS;AAAA,YACT,cAAc;AAAA,YACd,QAAQ4D,EAAM;AAAA,YACd,MAAMF,EAAK,QAAQE,EAAM;AAAA,YACzB,SAASH,EAAmBG,EAAM,OAAO;AAAA,UAAA;AAAA,QAE3C,SAASpC,GAAO;AACf,iBAAO;AAAA,YACN,OAAO,GAAG8B,EAAQ,WAAW,KAAKJ,EAAe1B,CAAK,CAAC;AAAA,UAAA;AAAA,QAEzD;AAAA,MACD;AAAA,IAAA;AAAA,IAED;AAAA,MACC,MAAM;AAAA,MACN,aAAa+B,EAAU;AAAA,MACvB,aAAaV,EAAmBU,EAAU,MAAM;AAAA,MAChD,aAAaA,EAAU;AAAA,MACvB,SAAS,OAAOT,MAAU;AACzB,YAAI;AACH,gBAAMnB,IAAUmB,EAAM,SAEhBe,IADQ7D,EAAO,KAAA,EACI,KAAK,CAACoC,MAAMA,EAAE,QAAQ;AAC/C,uBAAMpC,EAAO,OAAO2B,CAAO,GACpB;AAAA,YACN,SAAS;AAAA,YACT,QAAQkC,KAAA,gBAAAA,EAAY;AAAA,YACpB,SAAAlC;AAAA,UAAA;AAAA,QAEF,SAASH,GAAO;AACf,iBAAO;AAAA,YACN,OAAO,GAAG+B,EAAU,WAAW,KAAKL,EAAe1B,CAAK,CAAC;AAAA,UAAA;AAAA,QAE3D;AAAA,MACD;AAAA,IAAA;AAAA,IAED;AAAA,MACC,MAAM;AAAA,MACN,aAAagC,EAAc;AAAA,MAC3B,aAAaA,EAAc;AAAA,MAC3B,SAAS,YAAY;AACpB,YAAI;AACH,iBAAO;AAAA,YACN,KAAK,OAAO,SAAS;AAAA,UAAA;AAAA,QAEvB,SAAShC,GAAO;AACf,iBAAO;AAAA,YACN,OAAO,GAAGgC,EAAc,WAAW,KAAKN,EAAe1B,CAAK,CAAC;AAAA,UAAA;AAAA,QAE/D;AAAA,MACD;AAAA,IAAA;AAAA,EACD;AAEF;"}