@wp-playground/mcp 3.1.52 → 3.1.54
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 +77 -0
- package/client.cjs +1 -1
- package/client.cjs.map +1 -1
- package/client.d.ts +2 -0
- package/client.js +176 -106
- package/client.js.map +1 -1
- package/package.json +5 -4
- package/webmcp-site-tools.d.ts +27 -0
- package/webmcp.d.ts +2 -1
package/README.md
CHANGED
|
@@ -86,6 +86,83 @@ The [Playground Website](https://playground.wordpress.net/) also supports [WebMC
|
|
|
86
86
|
|
|
87
87
|
> **Note:** WebMCP is still a draft proposal and not widely supported.
|
|
88
88
|
|
|
89
|
+
### Tools registered by the WordPress site
|
|
90
|
+
|
|
91
|
+
A plugin running inside Playground can register its own WebMCP tools, and both the Playground website and Personal Playground re-advertise them as tools of the page:
|
|
92
|
+
|
|
93
|
+
```php
|
|
94
|
+
add_action( 'wp_head', function () {
|
|
95
|
+
?>
|
|
96
|
+
<script>
|
|
97
|
+
document.modelContext.registerTool( {
|
|
98
|
+
name: 'create_order',
|
|
99
|
+
description: 'Creates a draft order.',
|
|
100
|
+
inputSchema: { type: 'object', properties: { sku: { type: 'string' } } },
|
|
101
|
+
execute: async ( input ) => ( { orderId: await createOrder( input.sku ) } ),
|
|
102
|
+
} );
|
|
103
|
+
</script>
|
|
104
|
+
<?php
|
|
105
|
+
} );
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
WordPress runs in a nested iframe an agent never sees, so Playground proxies these tools:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
agent → document.modelContext (Playground page)
|
|
112
|
+
→ PlaygroundClient.callWebMCPTool() (Comlink)
|
|
113
|
+
→ remote frame (postMessage)
|
|
114
|
+
→ WordPress document (the plugin's execute())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Tool names, descriptions and input schemas are carried over unchanged, and results must be JSON-serializable. A tool whose name collides with a built-in `playground_*` tool is skipped and a warning is logged.
|
|
118
|
+
|
|
119
|
+
### Tool lifetime
|
|
120
|
+
|
|
121
|
+
Tools belong to the document that registered them, exactly as in WebMCP, and the page shows what the iframe currently holds. Registering at any time works — a tool added a second after load, or from a click handler, is picked up as soon as it appears — but **every navigation starts from an empty list**.
|
|
122
|
+
|
|
123
|
+
That catches people out: hooking only `wp_head` means the tools are gone the moment the user opens wp-admin. Register on every context the tools should cover:
|
|
124
|
+
|
|
125
|
+
```php
|
|
126
|
+
add_action( 'wp_head', 'my_register_tools' );
|
|
127
|
+
add_action( 'admin_head', 'my_register_tools' );
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Documents WordPress renders no head for — `admin-ajax.php`, REST routes, static files, a PDF — carry no tools, and the previous page's tools are withdrawn rather than left behind to fail when called.
|
|
131
|
+
|
|
132
|
+
A browser tab shows one site at a time, so the tools always belong to the active site. Switching sites restarts the proxy against the new one, and tool names need no per-site qualifier.
|
|
133
|
+
|
|
134
|
+
`document.modelContext` is provided by Playground's mu-plugin on `wp_head` and `admin_head`, which covers the front end and wp-admin.
|
|
135
|
+
|
|
136
|
+
Chrome 150 deprecated `navigator.modelContext` in favour of `document.modelContext` but still serves it, so the mu-plugin does the same: inside the WordPress document the deprecated global returns the very same registry and warns once on first access. A plugin that has not migrated keeps working here exactly as it does in Chrome. Write new code against `document.modelContext` — the alias goes when Chrome removes it.
|
|
137
|
+
|
|
138
|
+
**The login screen is deliberately not covered.** `wp-login.php` fires neither hook, so that document has no registry and proxies no tools; a site's tools come back when the user leaves it. `login_head` still fires there, so a plugin hooking it must feature-detect rather than assume the registry exists — worth doing anywhere, since most browsers do not implement WebMCP:
|
|
139
|
+
|
|
140
|
+
```php
|
|
141
|
+
add_action( 'login_head', function () {
|
|
142
|
+
?>
|
|
143
|
+
<script>
|
|
144
|
+
if ( document.modelContext ) {
|
|
145
|
+
document.modelContext.registerTool( { /* … */ } );
|
|
146
|
+
}
|
|
147
|
+
</script>
|
|
148
|
+
<?php
|
|
149
|
+
} );
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Reading the tools
|
|
153
|
+
|
|
154
|
+
Playground's own tools and the site's tools both live on `document.modelContext`:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
document.modelContext.tools.map((tool) => tool.name);
|
|
158
|
+
|
|
159
|
+
await document.modelContext.tools.find((tool) => tool.name === 'create_order').execute({ sku: 'X' });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
WebMCP is a draft, so this needs a browser that implements it: Chrome behind `chrome://flags/#enable-webmcp-testing`, or an extension that provides `document.modelContext` itself. Playground registers into whichever it finds and registers nothing when there is none — the site's tools still cross the frame boundary either way, so an embedder can reach them through `PlaygroundClient.onWebMCPToolsChanged()` and `callWebMCPTool()` regardless of browser support.
|
|
163
|
+
|
|
164
|
+
If a tool is missing, switch the devtools console to the `wp` frame and run `document.modelContext.tools.map( t => t.name )`. An empty list there means the plugin never registered; a list there but not on the page means the announcement did not cross the frame boundary.
|
|
165
|
+
|
|
89
166
|
## Security
|
|
90
167
|
|
|
91
168
|
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.
|
package/client.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./tool-executors-DX_lR1Jc.cjs"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./tool-executors-DX_lR1Jc.cjs"),w=require("@php-wasm/logger"),E=5e3;function P(n,o){const a=crypto.randomUUID();let e=null,s="",t=null,u=!1;function d(f){const l=n.list(),p=JSON.stringify(l);p!==s&&(s=p,f.send(JSON.stringify({type:"register",tabId:a,sites:l})))}async function r(){try{const f=await fetch(`http://127.0.0.1:${o}/bridge-token`);if(!f.ok){c();return}const{token:l}=await f.json();e=new WebSocket(`ws://127.0.0.1:${o}?token=${l}`)}catch{c();return}e.addEventListener("open",()=>{var f;s="",d(e),(f=n.onConnect)==null||f.call(n)}),e.addEventListener("message",async f=>{let l;try{l=JSON.parse(f.data)}catch{return}if(l.type!=="command")return;const{id:p,method:h,args:S,siteSlug:_}=l;try{const y=await C(n,h,S||[],_,o);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:p,type:"response",value:y}))}catch(y){const v=y instanceof Error?y.message:String(y);(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify({id:p,type:"response",error:v}))}}),e.addEventListener("close",()=>{e=null,c()}),e.addEventListener("error",()=>{})}function c(){u||(t=setTimeout(r,E))}return r(),{notifySitesChanged:()=>{(e==null?void 0:e.readyState)===WebSocket.OPEN&&d(e)},stop:()=>{u=!0,t!==null&&(clearTimeout(t),t=null),e&&(e.close(),e=null)}}}async function C(n,o,a,e,s){if(o==="__open_site_in_new_tab"){const r=new URL(window.location.href);if(r.searchParams.set("mcp-port",String(s)),r.searchParams.set("site-slug",e),!window.open(r.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[r]=a;return await n.rename(r),!0}if(o==="__save_site")return await n.saveInBrowser();const t=n.getClient();if(!t)throw new Error(`No active client for site: ${e}`);const d=i.createToolClient(t)[o];if(typeof d!="function")throw new Error(`Unknown method: ${o}`);return await d(...a)}const m=i.getSiteToolDefinitions();let g=null;function T(n){const a=n.list().find(e=>e.isActive);if(!a)throw new Error("No active Playground site");return a}async function x(n){const o=b();if(!o)return;g==null||g.abort(),g=new AbortController;const a=g,e=a.signal;function s(){const r=n.getClient();if(!r)throw new Error("No client for active site");return r}const t=Object.entries(i.toolDefinitions).map(([r,c])=>({name:r,description:c.description,inputSchema:i.paramsToJsonSchema(c.params),annotations:c.annotations,execute:async f=>{try{const l=i.toolExecutors[r];if(!l)return{error:`No executor for "${r}"`};const p=i.createToolClient(s());return await l(p,f)}catch(l){return{error:`${c.errorPrefix}: ${i.stringifyError(l)}`}}}}));t.push(...$(n));const u=await Promise.allSettled(t.map(async r=>{await o.registerTool(r,{signal:e})})),d=[];for(const[r,c]of u.entries())c.status==="rejected"&&d.push({name:t[r].name,reason:c.reason});if(d.length>0)throw a.abort(),g===a&&(g=null),new Error("Failed to register WebMCP tools: "+d.map(({name:r,reason:c})=>`${r} (${i.stringifyError(c)})`).join(", "))}function b(){if(typeof document>"u")return;const n=document.modelContext;return typeof(n==null?void 0:n.registerTool)=="function"?n:void 0}function $(n){const o=m.playground_list_sites,a=m.playground_save_in_browser,e=m.playground_rename_site,s=m.playground_get_website_url;return[{name:"playground_list_sites",description:o.description,annotations:o.annotations,execute:async()=>{try{return{connectedTabs:1,sites:n.list().map(t=>({siteId:t.slug,name:t.name,storage:i.formatStorageLabel(t.storage),isActive:t.isActive}))}}catch(t){return{error:`${o.errorPrefix}: ${i.stringifyError(t)}`}}}},{name:"playground_save_in_browser",description:a.description,annotations:a.annotations,execute:async()=>{try{const t=T(n),u=i.formatStorageLabel(t.storage);if(u!=="temporary")return{success:!0,alreadySaved:!0,siteId:t.slug,name:t.name,storage:u};const d=await n.saveInBrowser();return{success:!0,alreadySaved:!1,siteId:d.slug,name:t.name??d.slug,storage:i.formatStorageLabel(d.storage)}}catch(t){return{error:`${a.errorPrefix}: ${i.stringifyError(t)}`}}}},{name:"playground_rename_site",description:e.description,inputSchema:i.paramsToJsonSchema(e.params),annotations:e.annotations,execute:async t=>{try{const u=t.newName,r=n.list().find(c=>c.isActive);return await n.rename(u),{success:!0,siteId:r==null?void 0:r.slug,newName:u}}catch(u){return{error:`${e.errorPrefix}: ${i.stringifyError(u)}`}}}},{name:"playground_get_website_url",description:s.description,annotations:s.annotations,execute:async()=>{try{return{url:window.location.href}}catch(t){return{error:`${s.errorPrefix}: ${i.stringifyError(t)}`}}}}]}function N(n){const o=b();if(!o)return{stop:()=>{}};const a=o,e=M();let s=null,t=!1,u="";async function d(r){if(t)return;const c=JSON.stringify(r);if(c===u)return;u=c,s==null||s.abort(),s=new AbortController;const{signal:f}=s;for(const l of r){if(e.has(l.name)){w.logger.warn(`Skipping the WebMCP tool "${l.name}" registered by the site: Playground already provides a tool under that name.`);continue}try{await a.registerTool(W(n,l),{signal:f})}catch(p){w.logger.warn(`Failed to register the site's WebMCP tool "${l.name}": ${i.stringifyError(p)}`)}if(f.aborted)return}}return n.onWebMCPToolsChanged(r=>{d(r)}).catch(r=>{w.logger.warn("Failed to observe the site's WebMCP tools:",r)}),{stop(){t=!0,s==null||s.abort(),s=null,u=""}}}function M(){return new Set([...Object.keys(i.toolDefinitions),...Object.keys(i.getSiteToolDefinitions())])}function W(n,o){return{name:o.name,description:o.description,inputSchema:o.inputSchema,annotations:o.annotations,execute:async a=>{try{return await n.callWebMCPTool(o.name,a??{})}catch(e){return{error:`Error running the WordPress site's "${o.name}" tool: ${i.stringifyError(e)}`}}}}}exports.registerWebMCPTools=x;exports.startMcpBridge=P;exports.startWebMCPSiteToolProxy=N;
|
|
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","../../../../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-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 `document.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): Promise<void> | void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Document {\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 async function registerWebMCPTools(\n\tconfig: PlaygroundBridgeConfig\n): Promise<void> {\n\tconst modelContext = getModelContext();\n\tif (!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 controller = registrationController;\n\tconst signal = controller.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\tconst results = await Promise.allSettled(\n\t\ttools.map(async (tool) => {\n\t\t\tawait modelContext.registerTool(tool, { signal });\n\t\t})\n\t);\n\tconst failedRegistrations: Array<{ name: string; reason: unknown }> = [];\n\tfor (const [index, result] of results.entries()) {\n\t\tif (result.status === 'rejected') {\n\t\t\tfailedRegistrations.push({\n\t\t\t\tname: tools[index].name,\n\t\t\t\treason: result.reason,\n\t\t\t});\n\t\t}\n\t}\n\tif (failedRegistrations.length > 0) {\n\t\tcontroller.abort();\n\t\tif (registrationController === controller) {\n\t\t\tregistrationController = null;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'Failed to register WebMCP tools: ' +\n\t\t\t\tfailedRegistrations\n\t\t\t\t\t.map(\n\t\t\t\t\t\t({ name, reason }) =>\n\t\t\t\t\t\t\t`${name} (${stringifyError(reason)})`\n\t\t\t\t\t)\n\t\t\t\t\t.join(', ')\n\t\t);\n\t}\n}\n\nfunction getModelContext(): ModelContext | undefined {\n\tif (typeof document === 'undefined') {\n\t\treturn undefined;\n\t}\n\tconst modelContext = document.modelContext;\n\treturn typeof modelContext?.registerTool === 'function'\n\t\t? modelContext\n\t\t: undefined;\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","modelContext","getModelContext","controller","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","results","tool","failedRegistrations","index","result","reason","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"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","../../../../packages/playground/mcp/src/webmcp-site-tools.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-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 `document.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\nexport interface 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): Promise<void> | void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Document {\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 async function registerWebMCPTools(\n\tconfig: PlaygroundBridgeConfig\n): Promise<void> {\n\tconst modelContext = getModelContext();\n\tif (!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 controller = registrationController;\n\tconst signal = controller.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\tconst results = await Promise.allSettled(\n\t\ttools.map(async (tool) => {\n\t\t\tawait modelContext.registerTool(tool, { signal });\n\t\t})\n\t);\n\tconst failedRegistrations: Array<{ name: string; reason: unknown }> = [];\n\tfor (const [index, result] of results.entries()) {\n\t\tif (result.status === 'rejected') {\n\t\t\tfailedRegistrations.push({\n\t\t\t\tname: tools[index].name,\n\t\t\t\treason: result.reason,\n\t\t\t});\n\t\t}\n\t}\n\tif (failedRegistrations.length > 0) {\n\t\tcontroller.abort();\n\t\tif (registrationController === controller) {\n\t\t\tregistrationController = null;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'Failed to register WebMCP tools: ' +\n\t\t\t\tfailedRegistrations\n\t\t\t\t\t.map(\n\t\t\t\t\t\t({ name, reason }) =>\n\t\t\t\t\t\t\t`${name} (${stringifyError(reason)})`\n\t\t\t\t\t)\n\t\t\t\t\t.join(', ')\n\t\t);\n\t}\n}\n\nexport function getModelContext(): ModelContext | undefined {\n\tif (typeof document === 'undefined') {\n\t\treturn undefined;\n\t}\n\tconst modelContext = document.modelContext;\n\treturn typeof modelContext?.registerTool === 'function'\n\t\t? modelContext\n\t\t: undefined;\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","/**\n * Re-advertises the WebMCP tools registered inside the WordPress document as\n * tools of the Playground page itself.\n *\n * A plugin running in Playground can register tools with\n * `document.modelContext`, but that registry lives in a nested,\n * service-worker-served iframe an agent never sees. This proxy mirrors those\n * tools onto the top-level `document.modelContext` and forwards each call back\n * into the WordPress document:\n *\n * agent → document.modelContext (this page)\n * → PlaygroundClient.callWebMCPTool() (Comlink)\n * → remote frame (postMessage)\n * → WordPress document (the plugin's execute())\n */\n\nimport type {\n\tPlaygroundClient,\n\tWebMCPToolDescriptor,\n} from '@wp-playground/remote';\nimport { logger } from '@php-wasm/logger';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport type { ModelContextTool } from './webmcp';\nimport { getModelContext } from './webmcp';\n\nexport interface WebMCPSiteToolProxy {\n\tstop(): void;\n}\n\n/**\n * Starts mirroring `client`'s in-site WebMCP tools onto this page.\n *\n * Every announcement carries the full tool list, so the previous registration\n * is aborted and the new list registered in its place. Returns a handle that\n * unregisters everything it registered.\n */\nexport function startWebMCPSiteToolProxy(\n\tclient: PlaygroundClient\n): WebMCPSiteToolProxy {\n\tconst maybeModelContext = getModelContext();\n\tif (!maybeModelContext) {\n\t\t// Nothing can be advertised, so there is nothing to observe for.\n\t\treturn { stop: () => {} };\n\t}\n\t// `register` is hoisted, so it does not inherit the narrowing above.\n\tconst modelContext = maybeModelContext;\n\n\tconst reserved = reservedToolNames();\n\tlet registration: AbortController | null = null;\n\tlet stopped = false;\n\tlet registeredDescriptors = '';\n\n\tasync function register(tools: WebMCPToolDescriptor[]) {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\t// Registration is asynchronous, so a burst of announcements could\n\t\t// otherwise re-register an unchanged list several times over.\n\t\tconst descriptors = JSON.stringify(tools);\n\t\tif (descriptors === registeredDescriptors) {\n\t\t\treturn;\n\t\t}\n\t\tregisteredDescriptors = descriptors;\n\n\t\tregistration?.abort();\n\t\tregistration = new AbortController();\n\t\tconst { signal } = registration;\n\n\t\tfor (const tool of tools) {\n\t\t\tif (reserved.has(tool.name)) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Skipping the WebMCP tool \"${tool.name}\" registered by ` +\n\t\t\t\t\t\t`the site: Playground already provides a tool under ` +\n\t\t\t\t\t\t`that name.`\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait modelContext.registerTool(proxyTool(client, tool), {\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to register the site's WebMCP tool ` +\n\t\t\t\t\t\t`\"${tool.name}\": ${stringifyError(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tclient\n\t\t.onWebMCPToolsChanged((tools: WebMCPToolDescriptor[]) => {\n\t\t\tvoid register(tools);\n\t\t})\n\t\t.catch((error) => {\n\t\t\tlogger.warn(\"Failed to observe the site's WebMCP tools:\", error);\n\t\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tregistration?.abort();\n\t\t\tregistration = null;\n\t\t\tregisteredDescriptors = '';\n\t\t},\n\t};\n}\n\n/**\n * Names Playground registers itself. A site tool may not take one of them\n * over, or an agent asking to execute PHP would reach the site's plugin\n * instead.\n */\nfunction reservedToolNames(): Set<string> {\n\treturn new Set([\n\t\t...Object.keys(toolDefinitions),\n\t\t...Object.keys(getSiteToolDefinitions()),\n\t]);\n}\n\nfunction proxyTool(\n\tclient: PlaygroundClient,\n\ttool: WebMCPToolDescriptor\n): ModelContextTool {\n\treturn {\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tinputSchema: tool.inputSchema,\n\t\tannotations: tool.annotations,\n\t\texecute: async (input) => {\n\t\t\ttry {\n\t\t\t\treturn await client.callWebMCPTool(tool.name, input ?? {});\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\terror:\n\t\t\t\t\t\t`Error running the WordPress site's \"${tool.name}\" ` +\n\t\t\t\t\t\t`tool: ${stringifyError(error)}`,\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","modelContext","getModelContext","controller","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","results","tool","failedRegistrations","index","result","reason","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite","startWebMCPSiteToolProxy","maybeModelContext","reserved","reservedToolNames","registration","registeredDescriptors","register","descriptors","logger","proxyTool"],"mappings":"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"}
|
package/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { startMcpBridge } from './bridge-client';
|
|
2
2
|
export type { PlaygroundBridgeConfig, McpBridgeHandle } from './bridge-client';
|
|
3
3
|
export { registerWebMCPTools } from './webmcp';
|
|
4
|
+
export { startWebMCPSiteToolProxy } from './webmcp-site-tools';
|
|
5
|
+
export type { WebMCPSiteToolProxy } from './webmcp-site-tools';
|
package/client.js
CHANGED
|
@@ -1,168 +1,169 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { c as b, t as S, a as E, s as p, b as _, f as w, g as v } from "./tool-executors-DeiRNg9-.js";
|
|
2
|
+
import { logger as h } from "@php-wasm/logger";
|
|
3
|
+
const N = 5e3;
|
|
4
|
+
function J(n, o) {
|
|
4
5
|
const i = crypto.randomUUID();
|
|
5
|
-
let e = null,
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
f !==
|
|
6
|
+
let e = null, s = "", t = null, l = !1;
|
|
7
|
+
function u(d) {
|
|
8
|
+
const c = n.list(), f = JSON.stringify(c);
|
|
9
|
+
f !== s && (s = f, d.send(JSON.stringify({ type: "register", tabId: i, sites: c })));
|
|
9
10
|
}
|
|
10
|
-
async function
|
|
11
|
+
async function r() {
|
|
11
12
|
try {
|
|
12
13
|
const d = await fetch(
|
|
13
14
|
`http://127.0.0.1:${o}/bridge-token`
|
|
14
15
|
);
|
|
15
16
|
if (!d.ok) {
|
|
16
|
-
|
|
17
|
+
a();
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
const { token:
|
|
20
|
-
e = new WebSocket(`ws://127.0.0.1:${o}?token=${
|
|
20
|
+
const { token: c } = await d.json();
|
|
21
|
+
e = new WebSocket(`ws://127.0.0.1:${o}?token=${c}`);
|
|
21
22
|
} catch {
|
|
22
|
-
|
|
23
|
+
a();
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
e.addEventListener("open", () => {
|
|
26
27
|
var d;
|
|
27
|
-
|
|
28
|
+
s = "", u(e), (d = n.onConnect) == null || d.call(n);
|
|
28
29
|
}), e.addEventListener("message", async (d) => {
|
|
29
|
-
let
|
|
30
|
+
let c;
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
+
c = JSON.parse(d.data);
|
|
32
33
|
} catch {
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
|
-
if (
|
|
36
|
+
if (c.type !== "command")
|
|
36
37
|
return;
|
|
37
|
-
const { id: f, method:
|
|
38
|
+
const { id: f, method: P, args: $, siteSlug: x } = c;
|
|
38
39
|
try {
|
|
39
|
-
const y = await
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const y = await W(
|
|
41
|
+
n,
|
|
42
|
+
P,
|
|
43
|
+
$ || [],
|
|
44
|
+
x,
|
|
44
45
|
o
|
|
45
46
|
);
|
|
46
47
|
(e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(JSON.stringify({ id: f, type: "response", value: y }));
|
|
47
48
|
} catch (y) {
|
|
48
|
-
const
|
|
49
|
+
const T = y instanceof Error ? y.message : String(y);
|
|
49
50
|
(e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(
|
|
50
51
|
JSON.stringify({
|
|
51
52
|
id: f,
|
|
52
53
|
type: "response",
|
|
53
|
-
error:
|
|
54
|
+
error: T
|
|
54
55
|
})
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
58
|
}), e.addEventListener("close", () => {
|
|
58
|
-
e = null,
|
|
59
|
+
e = null, a();
|
|
59
60
|
}), e.addEventListener("error", () => {
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
63
|
+
function a() {
|
|
64
|
+
l || (t = setTimeout(r, N));
|
|
64
65
|
}
|
|
65
|
-
return
|
|
66
|
+
return r(), {
|
|
66
67
|
notifySitesChanged: () => {
|
|
67
|
-
(e == null ? void 0 : e.readyState) === WebSocket.OPEN &&
|
|
68
|
+
(e == null ? void 0 : e.readyState) === WebSocket.OPEN && u(e);
|
|
68
69
|
},
|
|
69
70
|
stop: () => {
|
|
70
|
-
|
|
71
|
+
l = !0, t !== null && (clearTimeout(t), t = null), e && (e.close(), e = null);
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
|
-
async function
|
|
75
|
+
async function W(n, o, i, e, s) {
|
|
75
76
|
if (o === "__open_site_in_new_tab") {
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
77
|
+
const r = new URL(window.location.href);
|
|
78
|
+
if (r.searchParams.set("mcp-port", String(s)), r.searchParams.set("site-slug", e), !window.open(r.toString(), "_blank"))
|
|
78
79
|
throw new Error(
|
|
79
80
|
"Pop-up blocked by browser. The user must allow pop-ups for this site."
|
|
80
81
|
);
|
|
81
82
|
return !0;
|
|
82
83
|
}
|
|
83
84
|
if (o === "__rename_site") {
|
|
84
|
-
const [
|
|
85
|
-
return await
|
|
85
|
+
const [r] = i;
|
|
86
|
+
return await n.rename(r), !0;
|
|
86
87
|
}
|
|
87
88
|
if (o === "__save_site")
|
|
88
|
-
return await
|
|
89
|
-
const t =
|
|
89
|
+
return await n.saveInBrowser();
|
|
90
|
+
const t = n.getClient();
|
|
90
91
|
if (!t)
|
|
91
92
|
throw new Error(`No active client for site: ${e}`);
|
|
92
|
-
const
|
|
93
|
-
if (typeof
|
|
93
|
+
const u = b(t)[o];
|
|
94
|
+
if (typeof u != "function")
|
|
94
95
|
throw new Error(`Unknown method: ${o}`);
|
|
95
|
-
return await
|
|
96
|
+
return await u(...i);
|
|
96
97
|
}
|
|
97
|
-
const
|
|
98
|
-
let
|
|
99
|
-
function
|
|
100
|
-
const i =
|
|
98
|
+
const g = v();
|
|
99
|
+
let m = null;
|
|
100
|
+
function k(n) {
|
|
101
|
+
const i = n.list().find((e) => e.isActive);
|
|
101
102
|
if (!i)
|
|
102
103
|
throw new Error("No active Playground site");
|
|
103
104
|
return i;
|
|
104
105
|
}
|
|
105
|
-
async function
|
|
106
|
-
const o =
|
|
106
|
+
async function L(n) {
|
|
107
|
+
const o = C();
|
|
107
108
|
if (!o)
|
|
108
109
|
return;
|
|
109
|
-
|
|
110
|
-
const i =
|
|
111
|
-
function
|
|
112
|
-
const
|
|
113
|
-
if (!
|
|
110
|
+
m == null || m.abort(), m = new AbortController();
|
|
111
|
+
const i = m, e = i.signal;
|
|
112
|
+
function s() {
|
|
113
|
+
const r = n.getClient();
|
|
114
|
+
if (!r)
|
|
114
115
|
throw new Error("No client for active site");
|
|
115
|
-
return
|
|
116
|
+
return r;
|
|
116
117
|
}
|
|
117
|
-
const t = Object.entries(
|
|
118
|
-
([
|
|
119
|
-
name:
|
|
120
|
-
description:
|
|
121
|
-
inputSchema:
|
|
122
|
-
annotations:
|
|
118
|
+
const t = Object.entries(S).map(
|
|
119
|
+
([r, a]) => ({
|
|
120
|
+
name: r,
|
|
121
|
+
description: a.description,
|
|
122
|
+
inputSchema: _(a.params),
|
|
123
|
+
annotations: a.annotations,
|
|
123
124
|
execute: async (d) => {
|
|
124
125
|
try {
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
126
|
+
const c = E[r];
|
|
127
|
+
if (!c)
|
|
127
128
|
return {
|
|
128
|
-
error: `No executor for "${
|
|
129
|
+
error: `No executor for "${r}"`
|
|
129
130
|
};
|
|
130
|
-
const f =
|
|
131
|
-
return await
|
|
132
|
-
} catch (
|
|
131
|
+
const f = b(s());
|
|
132
|
+
return await c(f, d);
|
|
133
|
+
} catch (c) {
|
|
133
134
|
return {
|
|
134
|
-
error: `${
|
|
135
|
+
error: `${a.errorPrefix}: ${p(c)}`
|
|
135
136
|
};
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
})
|
|
139
140
|
);
|
|
140
|
-
t.push(...
|
|
141
|
-
const
|
|
142
|
-
t.map(async (
|
|
143
|
-
await o.registerTool(
|
|
141
|
+
t.push(...M(n));
|
|
142
|
+
const l = await Promise.allSettled(
|
|
143
|
+
t.map(async (r) => {
|
|
144
|
+
await o.registerTool(r, { signal: e });
|
|
144
145
|
})
|
|
145
|
-
),
|
|
146
|
-
for (const [
|
|
147
|
-
|
|
148
|
-
name: t[
|
|
149
|
-
reason:
|
|
146
|
+
), u = [];
|
|
147
|
+
for (const [r, a] of l.entries())
|
|
148
|
+
a.status === "rejected" && u.push({
|
|
149
|
+
name: t[r].name,
|
|
150
|
+
reason: a.reason
|
|
150
151
|
});
|
|
151
|
-
if (
|
|
152
|
-
throw i.abort(),
|
|
153
|
-
"Failed to register WebMCP tools: " +
|
|
154
|
-
({ name:
|
|
152
|
+
if (u.length > 0)
|
|
153
|
+
throw i.abort(), m === i && (m = null), new Error(
|
|
154
|
+
"Failed to register WebMCP tools: " + u.map(
|
|
155
|
+
({ name: r, reason: a }) => `${r} (${p(a)})`
|
|
155
156
|
).join(", ")
|
|
156
157
|
);
|
|
157
158
|
}
|
|
158
|
-
function
|
|
159
|
+
function C() {
|
|
159
160
|
if (typeof document > "u")
|
|
160
161
|
return;
|
|
161
|
-
const
|
|
162
|
-
return typeof (
|
|
162
|
+
const n = document.modelContext;
|
|
163
|
+
return typeof (n == null ? void 0 : n.registerTool) == "function" ? n : void 0;
|
|
163
164
|
}
|
|
164
|
-
function
|
|
165
|
-
const o =
|
|
165
|
+
function M(n) {
|
|
166
|
+
const o = g.playground_list_sites, i = g.playground_save_in_browser, e = g.playground_rename_site, s = g.playground_get_website_url;
|
|
166
167
|
return [
|
|
167
168
|
{
|
|
168
169
|
name: "playground_list_sites",
|
|
@@ -172,16 +173,16 @@ function O(r) {
|
|
|
172
173
|
try {
|
|
173
174
|
return {
|
|
174
175
|
connectedTabs: 1,
|
|
175
|
-
sites:
|
|
176
|
+
sites: n.list().map((t) => ({
|
|
176
177
|
siteId: t.slug,
|
|
177
178
|
name: t.name,
|
|
178
|
-
storage:
|
|
179
|
+
storage: w(t.storage),
|
|
179
180
|
isActive: t.isActive
|
|
180
181
|
}))
|
|
181
182
|
};
|
|
182
183
|
} catch (t) {
|
|
183
184
|
return {
|
|
184
|
-
error: `${o.errorPrefix}: ${
|
|
185
|
+
error: `${o.errorPrefix}: ${p(t)}`
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
}
|
|
@@ -192,26 +193,26 @@ function O(r) {
|
|
|
192
193
|
annotations: i.annotations,
|
|
193
194
|
execute: async () => {
|
|
194
195
|
try {
|
|
195
|
-
const t =
|
|
196
|
-
if (
|
|
196
|
+
const t = k(n), l = w(t.storage);
|
|
197
|
+
if (l !== "temporary")
|
|
197
198
|
return {
|
|
198
199
|
success: !0,
|
|
199
200
|
alreadySaved: !0,
|
|
200
201
|
siteId: t.slug,
|
|
201
202
|
name: t.name,
|
|
202
|
-
storage:
|
|
203
|
+
storage: l
|
|
203
204
|
};
|
|
204
|
-
const
|
|
205
|
+
const u = await n.saveInBrowser();
|
|
205
206
|
return {
|
|
206
207
|
success: !0,
|
|
207
208
|
alreadySaved: !1,
|
|
208
|
-
siteId:
|
|
209
|
-
name: t.name ??
|
|
210
|
-
storage:
|
|
209
|
+
siteId: u.slug,
|
|
210
|
+
name: t.name ?? u.slug,
|
|
211
|
+
storage: w(u.storage)
|
|
211
212
|
};
|
|
212
213
|
} catch (t) {
|
|
213
214
|
return {
|
|
214
|
-
error: `${i.errorPrefix}: ${
|
|
215
|
+
error: `${i.errorPrefix}: ${p(t)}`
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
218
|
}
|
|
@@ -219,27 +220,27 @@ function O(r) {
|
|
|
219
220
|
{
|
|
220
221
|
name: "playground_rename_site",
|
|
221
222
|
description: e.description,
|
|
222
|
-
inputSchema:
|
|
223
|
+
inputSchema: _(e.params),
|
|
223
224
|
annotations: e.annotations,
|
|
224
225
|
execute: async (t) => {
|
|
225
226
|
try {
|
|
226
|
-
const
|
|
227
|
-
return await
|
|
227
|
+
const l = t.newName, r = n.list().find((a) => a.isActive);
|
|
228
|
+
return await n.rename(l), {
|
|
228
229
|
success: !0,
|
|
229
|
-
siteId:
|
|
230
|
-
newName:
|
|
230
|
+
siteId: r == null ? void 0 : r.slug,
|
|
231
|
+
newName: l
|
|
231
232
|
};
|
|
232
|
-
} catch (
|
|
233
|
+
} catch (l) {
|
|
233
234
|
return {
|
|
234
|
-
error: `${e.errorPrefix}: ${
|
|
235
|
+
error: `${e.errorPrefix}: ${p(l)}`
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
},
|
|
239
240
|
{
|
|
240
241
|
name: "playground_get_website_url",
|
|
241
|
-
description:
|
|
242
|
-
annotations:
|
|
242
|
+
description: s.description,
|
|
243
|
+
annotations: s.annotations,
|
|
243
244
|
execute: async () => {
|
|
244
245
|
try {
|
|
245
246
|
return {
|
|
@@ -247,15 +248,84 @@ function O(r) {
|
|
|
247
248
|
};
|
|
248
249
|
} catch (t) {
|
|
249
250
|
return {
|
|
250
|
-
error: `${
|
|
251
|
+
error: `${s.errorPrefix}: ${p(t)}`
|
|
251
252
|
};
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
];
|
|
256
257
|
}
|
|
258
|
+
function j(n) {
|
|
259
|
+
const o = C();
|
|
260
|
+
if (!o)
|
|
261
|
+
return { stop: () => {
|
|
262
|
+
} };
|
|
263
|
+
const i = o, e = O();
|
|
264
|
+
let s = null, t = !1, l = "";
|
|
265
|
+
async function u(r) {
|
|
266
|
+
if (t)
|
|
267
|
+
return;
|
|
268
|
+
const a = JSON.stringify(r);
|
|
269
|
+
if (a === l)
|
|
270
|
+
return;
|
|
271
|
+
l = a, s == null || s.abort(), s = new AbortController();
|
|
272
|
+
const { signal: d } = s;
|
|
273
|
+
for (const c of r) {
|
|
274
|
+
if (e.has(c.name)) {
|
|
275
|
+
h.warn(
|
|
276
|
+
`Skipping the WebMCP tool "${c.name}" registered by the site: Playground already provides a tool under that name.`
|
|
277
|
+
);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await i.registerTool(A(n, c), {
|
|
282
|
+
signal: d
|
|
283
|
+
});
|
|
284
|
+
} catch (f) {
|
|
285
|
+
h.warn(
|
|
286
|
+
`Failed to register the site's WebMCP tool "${c.name}": ${p(f)}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (d.aborted)
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return n.onWebMCPToolsChanged((r) => {
|
|
294
|
+
u(r);
|
|
295
|
+
}).catch((r) => {
|
|
296
|
+
h.warn("Failed to observe the site's WebMCP tools:", r);
|
|
297
|
+
}), {
|
|
298
|
+
stop() {
|
|
299
|
+
t = !0, s == null || s.abort(), s = null, l = "";
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function O() {
|
|
304
|
+
return /* @__PURE__ */ new Set([
|
|
305
|
+
...Object.keys(S),
|
|
306
|
+
...Object.keys(v())
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
function A(n, o) {
|
|
310
|
+
return {
|
|
311
|
+
name: o.name,
|
|
312
|
+
description: o.description,
|
|
313
|
+
inputSchema: o.inputSchema,
|
|
314
|
+
annotations: o.annotations,
|
|
315
|
+
execute: async (i) => {
|
|
316
|
+
try {
|
|
317
|
+
return await n.callWebMCPTool(o.name, i ?? {});
|
|
318
|
+
} catch (e) {
|
|
319
|
+
return {
|
|
320
|
+
error: `Error running the WordPress site's "${o.name}" tool: ${p(e)}`
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
257
326
|
export {
|
|
258
|
-
|
|
259
|
-
|
|
327
|
+
L as registerWebMCPTools,
|
|
328
|
+
J as startMcpBridge,
|
|
329
|
+
j as startWebMCPSiteToolProxy
|
|
260
330
|
};
|
|
261
331
|
//# 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","../../../../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-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 `document.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): Promise<void> | void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Document {\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 async function registerWebMCPTools(\n\tconfig: PlaygroundBridgeConfig\n): Promise<void> {\n\tconst modelContext = getModelContext();\n\tif (!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 controller = registrationController;\n\tconst signal = controller.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\tconst results = await Promise.allSettled(\n\t\ttools.map(async (tool) => {\n\t\t\tawait modelContext.registerTool(tool, { signal });\n\t\t})\n\t);\n\tconst failedRegistrations: Array<{ name: string; reason: unknown }> = [];\n\tfor (const [index, result] of results.entries()) {\n\t\tif (result.status === 'rejected') {\n\t\t\tfailedRegistrations.push({\n\t\t\t\tname: tools[index].name,\n\t\t\t\treason: result.reason,\n\t\t\t});\n\t\t}\n\t}\n\tif (failedRegistrations.length > 0) {\n\t\tcontroller.abort();\n\t\tif (registrationController === controller) {\n\t\t\tregistrationController = null;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'Failed to register WebMCP tools: ' +\n\t\t\t\tfailedRegistrations\n\t\t\t\t\t.map(\n\t\t\t\t\t\t({ name, reason }) =>\n\t\t\t\t\t\t\t`${name} (${stringifyError(reason)})`\n\t\t\t\t\t)\n\t\t\t\t\t.join(', ')\n\t\t);\n\t}\n}\n\nfunction getModelContext(): ModelContext | undefined {\n\tif (typeof document === 'undefined') {\n\t\treturn undefined;\n\t}\n\tconst modelContext = document.modelContext;\n\treturn typeof modelContext?.registerTool === 'function'\n\t\t? modelContext\n\t\t: undefined;\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","modelContext","getModelContext","controller","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","results","tool","failedRegistrations","index","result","reason","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"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","../../../../packages/playground/mcp/src/webmcp-site-tools.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-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 `document.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\nexport interface 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): Promise<void> | void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Document {\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 async function registerWebMCPTools(\n\tconfig: PlaygroundBridgeConfig\n): Promise<void> {\n\tconst modelContext = getModelContext();\n\tif (!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 controller = registrationController;\n\tconst signal = controller.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\tconst results = await Promise.allSettled(\n\t\ttools.map(async (tool) => {\n\t\t\tawait modelContext.registerTool(tool, { signal });\n\t\t})\n\t);\n\tconst failedRegistrations: Array<{ name: string; reason: unknown }> = [];\n\tfor (const [index, result] of results.entries()) {\n\t\tif (result.status === 'rejected') {\n\t\t\tfailedRegistrations.push({\n\t\t\t\tname: tools[index].name,\n\t\t\t\treason: result.reason,\n\t\t\t});\n\t\t}\n\t}\n\tif (failedRegistrations.length > 0) {\n\t\tcontroller.abort();\n\t\tif (registrationController === controller) {\n\t\t\tregistrationController = null;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'Failed to register WebMCP tools: ' +\n\t\t\t\tfailedRegistrations\n\t\t\t\t\t.map(\n\t\t\t\t\t\t({ name, reason }) =>\n\t\t\t\t\t\t\t`${name} (${stringifyError(reason)})`\n\t\t\t\t\t)\n\t\t\t\t\t.join(', ')\n\t\t);\n\t}\n}\n\nexport function getModelContext(): ModelContext | undefined {\n\tif (typeof document === 'undefined') {\n\t\treturn undefined;\n\t}\n\tconst modelContext = document.modelContext;\n\treturn typeof modelContext?.registerTool === 'function'\n\t\t? modelContext\n\t\t: undefined;\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","/**\n * Re-advertises the WebMCP tools registered inside the WordPress document as\n * tools of the Playground page itself.\n *\n * A plugin running in Playground can register tools with\n * `document.modelContext`, but that registry lives in a nested,\n * service-worker-served iframe an agent never sees. This proxy mirrors those\n * tools onto the top-level `document.modelContext` and forwards each call back\n * into the WordPress document:\n *\n * agent → document.modelContext (this page)\n * → PlaygroundClient.callWebMCPTool() (Comlink)\n * → remote frame (postMessage)\n * → WordPress document (the plugin's execute())\n */\n\nimport type {\n\tPlaygroundClient,\n\tWebMCPToolDescriptor,\n} from '@wp-playground/remote';\nimport { logger } from '@php-wasm/logger';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport type { ModelContextTool } from './webmcp';\nimport { getModelContext } from './webmcp';\n\nexport interface WebMCPSiteToolProxy {\n\tstop(): void;\n}\n\n/**\n * Starts mirroring `client`'s in-site WebMCP tools onto this page.\n *\n * Every announcement carries the full tool list, so the previous registration\n * is aborted and the new list registered in its place. Returns a handle that\n * unregisters everything it registered.\n */\nexport function startWebMCPSiteToolProxy(\n\tclient: PlaygroundClient\n): WebMCPSiteToolProxy {\n\tconst maybeModelContext = getModelContext();\n\tif (!maybeModelContext) {\n\t\t// Nothing can be advertised, so there is nothing to observe for.\n\t\treturn { stop: () => {} };\n\t}\n\t// `register` is hoisted, so it does not inherit the narrowing above.\n\tconst modelContext = maybeModelContext;\n\n\tconst reserved = reservedToolNames();\n\tlet registration: AbortController | null = null;\n\tlet stopped = false;\n\tlet registeredDescriptors = '';\n\n\tasync function register(tools: WebMCPToolDescriptor[]) {\n\t\tif (stopped) {\n\t\t\treturn;\n\t\t}\n\t\t// Registration is asynchronous, so a burst of announcements could\n\t\t// otherwise re-register an unchanged list several times over.\n\t\tconst descriptors = JSON.stringify(tools);\n\t\tif (descriptors === registeredDescriptors) {\n\t\t\treturn;\n\t\t}\n\t\tregisteredDescriptors = descriptors;\n\n\t\tregistration?.abort();\n\t\tregistration = new AbortController();\n\t\tconst { signal } = registration;\n\n\t\tfor (const tool of tools) {\n\t\t\tif (reserved.has(tool.name)) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Skipping the WebMCP tool \"${tool.name}\" registered by ` +\n\t\t\t\t\t\t`the site: Playground already provides a tool under ` +\n\t\t\t\t\t\t`that name.`\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait modelContext.registerTool(proxyTool(client, tool), {\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to register the site's WebMCP tool ` +\n\t\t\t\t\t\t`\"${tool.name}\": ${stringifyError(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (signal.aborted) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tclient\n\t\t.onWebMCPToolsChanged((tools: WebMCPToolDescriptor[]) => {\n\t\t\tvoid register(tools);\n\t\t})\n\t\t.catch((error) => {\n\t\t\tlogger.warn(\"Failed to observe the site's WebMCP tools:\", error);\n\t\t});\n\n\treturn {\n\t\tstop() {\n\t\t\tstopped = true;\n\t\t\tregistration?.abort();\n\t\t\tregistration = null;\n\t\t\tregisteredDescriptors = '';\n\t\t},\n\t};\n}\n\n/**\n * Names Playground registers itself. A site tool may not take one of them\n * over, or an agent asking to execute PHP would reach the site's plugin\n * instead.\n */\nfunction reservedToolNames(): Set<string> {\n\treturn new Set([\n\t\t...Object.keys(toolDefinitions),\n\t\t...Object.keys(getSiteToolDefinitions()),\n\t]);\n}\n\nfunction proxyTool(\n\tclient: PlaygroundClient,\n\ttool: WebMCPToolDescriptor\n): ModelContextTool {\n\treturn {\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tinputSchema: tool.inputSchema,\n\t\tannotations: tool.annotations,\n\t\texecute: async (input) => {\n\t\t\ttry {\n\t\t\t\treturn await client.callWebMCPTool(tool.name, input ?? {});\n\t\t\t} catch (error) {\n\t\t\t\treturn {\n\t\t\t\t\terror:\n\t\t\t\t\t\t`Error running the WordPress site's \"${tool.name}\" ` +\n\t\t\t\t\t\t`tool: ${stringifyError(error)}`,\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","modelContext","getModelContext","controller","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","results","tool","failedRegistrations","index","result","reason","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite","startWebMCPSiteToolProxy","maybeModelContext","reserved","reservedToolNames","registration","registeredDescriptors","register","descriptors","logger","proxyTool"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-playground/mcp",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.54",
|
|
4
4
|
"description": "MCP server for WordPress Playground - enables AI agents to interact with the WordPress Playground website.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
40
40
|
"ws": "8.21.0",
|
|
41
41
|
"zod": "^4.3",
|
|
42
|
-
"@wp-playground/remote": "3.1.
|
|
43
|
-
"@php-wasm/universal": "3.1.
|
|
42
|
+
"@wp-playground/remote": "3.1.54",
|
|
43
|
+
"@php-wasm/universal": "3.1.54",
|
|
44
|
+
"@php-wasm/logger": "3.1.54"
|
|
44
45
|
},
|
|
45
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "b64c3a5e4dad38194f8c8bca58269221564ac1da",
|
|
46
47
|
"packageManager": "npm@10.9.2",
|
|
47
48
|
"overrides": {
|
|
48
49
|
"rollup": "^4.34.6",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-advertises the WebMCP tools registered inside the WordPress document as
|
|
3
|
+
* tools of the Playground page itself.
|
|
4
|
+
*
|
|
5
|
+
* A plugin running in Playground can register tools with
|
|
6
|
+
* `document.modelContext`, but that registry lives in a nested,
|
|
7
|
+
* service-worker-served iframe an agent never sees. This proxy mirrors those
|
|
8
|
+
* tools onto the top-level `document.modelContext` and forwards each call back
|
|
9
|
+
* into the WordPress document:
|
|
10
|
+
*
|
|
11
|
+
* agent → document.modelContext (this page)
|
|
12
|
+
* → PlaygroundClient.callWebMCPTool() (Comlink)
|
|
13
|
+
* → remote frame (postMessage)
|
|
14
|
+
* → WordPress document (the plugin's execute())
|
|
15
|
+
*/
|
|
16
|
+
import type { PlaygroundClient } from '@wp-playground/remote';
|
|
17
|
+
export interface WebMCPSiteToolProxy {
|
|
18
|
+
stop(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Starts mirroring `client`'s in-site WebMCP tools onto this page.
|
|
22
|
+
*
|
|
23
|
+
* Every announcement carries the full tool list, so the previous registration
|
|
24
|
+
* is aborted and the new list registered in its place. Returns a handle that
|
|
25
|
+
* unregisters everything it registered.
|
|
26
|
+
*/
|
|
27
|
+
export declare function startWebMCPSiteToolProxy(client: PlaygroundClient): WebMCPSiteToolProxy;
|
package/webmcp.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* with the running Playground site.
|
|
7
7
|
*/
|
|
8
8
|
import type { PlaygroundBridgeConfig } from './bridge-client';
|
|
9
|
-
interface ModelContextTool {
|
|
9
|
+
export interface ModelContextTool {
|
|
10
10
|
name: string;
|
|
11
11
|
description: string;
|
|
12
12
|
inputSchema?: Record<string, unknown>;
|
|
@@ -34,4 +34,5 @@ declare global {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
export declare function registerWebMCPTools(config: PlaygroundBridgeConfig): Promise<void>;
|
|
37
|
+
export declare function getModelContext(): ModelContext | undefined;
|
|
37
38
|
export {};
|