@wp-playground/mcp 3.1.51 → 3.1.53
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 +78 -1
- package/client.cjs +1 -1
- package/client.cjs.map +1 -1
- package/client.d.ts +2 -0
- package/client.js +196 -105
- package/client.js.map +1 -1
- package/package.json +5 -4
- package/webmcp-site-tools.d.ts +27 -0
- package/webmcp.d.ts +6 -5
package/README.md
CHANGED
|
@@ -82,10 +82,87 @@ The MCP server communicates with AI clients via stdio and with the browser via W
|
|
|
82
82
|
|
|
83
83
|
## WebMCP
|
|
84
84
|
|
|
85
|
-
The [Playground Website](https://playground.wordpress.net/) also supports [WebMCP](https://github.com/webmachinelearning/webmcp) — a browser-native MCP proposal that exposes tools via `
|
|
85
|
+
The [Playground Website](https://playground.wordpress.net/) also supports [WebMCP](https://github.com/webmachinelearning/webmcp) — a browser-native MCP proposal that exposes tools via `document.modelContext`. When a Playground site loads, its tools are registered automatically with no CLI or WebSocket bridge needed.
|
|
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 `navigator.modelContext` (the\n * Chrome WebMCP API) so that browser-side AI agents can interact\n * with the running Playground site.\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tformatStorageLabel,\n\tparamsToJsonSchema,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport { toolExecutors, createToolClient } from './tools/tool-executors';\nimport type { PlaygroundBridgeConfig } from './bridge-client';\n\nconst siteToolDefinitions = getSiteToolDefinitions();\n\n// -- WebMCP type declarations --\n\ninterface ModelContextTool {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: Record<string, unknown>;\n\texecute: (\n\t\tinput: Record<string, unknown>,\n\t\tclient: ModelContextClient\n\t) => Promise<unknown>;\n\tannotations?: { readOnlyHint?: boolean; destructiveHint?: boolean };\n}\n\ninterface ModelContextClient {\n\trequestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContext {\n\tprovideContext(options: { tools: ModelContextTool[] }): void;\n\tregisterTool(\n\t\ttool: ModelContextTool,\n\t\toptions?: { signal?: AbortSignal }\n\t): void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Navigator {\n\t\tmodelContext?: ModelContext;\n\t}\n}\n\n// -- Registration --\n\nlet registrationController: AbortController | null = null;\n\nfunction getActiveSite(config: PlaygroundBridgeConfig) {\n\tconst sites = config.list();\n\tconst active = sites.find((s) => s.isActive);\n\tif (!active) {\n\t\tthrow new Error('No active Playground site');\n\t}\n\treturn active;\n}\n\nexport function registerWebMCPTools(config: PlaygroundBridgeConfig): void {\n\tif (typeof navigator === 'undefined' || !navigator.modelContext) {\n\t\treturn;\n\t}\n\n\t// Abort any previous registration before re-registering.\n\tregistrationController?.abort();\n\tregistrationController = new AbortController();\n\tconst signal = registrationController.signal;\n\n\tfunction getActiveClient(): PlaygroundClient {\n\t\tconst client = config.getClient();\n\t\tif (!client) {\n\t\t\tthrow new Error('No client for active site');\n\t\t}\n\t\treturn client;\n\t}\n\n\t// Per-site tools\n\tconst tools: ModelContextTool[] = Object.entries(toolDefinitions).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\tdescription: def.description,\n\t\t\tinputSchema: paramsToJsonSchema(def.params),\n\t\t\tannotations: def.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst executor = toolExecutors[name];\n\t\t\t\t\tif (!executor) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\terror: `No executor for \"${name}\"`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst adapter = createToolClient(getActiveClient());\n\t\t\t\t\treturn await executor(adapter, input);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${def.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t);\n\n\t// Site management tools\n\ttools.push(...createSiteManagementTools(config));\n\n\tfor (const tool of tools) {\n\t\tnavigator.modelContext.registerTool(tool, { signal });\n\t}\n}\n\nfunction createSiteManagementTools(\n\tconfig: PlaygroundBridgeConfig\n): ModelContextTool[] {\n\tconst listDef = siteToolDefinitions['playground_list_sites'];\n\tconst saveDef = siteToolDefinitions['playground_save_in_browser'];\n\tconst renameDef = siteToolDefinitions['playground_rename_site'];\n\tconst websiteUrlDef = siteToolDefinitions['playground_get_website_url'];\n\n\treturn [\n\t\t{\n\t\t\tname: 'playground_list_sites',\n\t\t\tdescription: listDef.description,\n\t\t\tannotations: listDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tconnectedTabs: 1,\n\t\t\t\t\t\tsites: config.list().map((s) => ({\n\t\t\t\t\t\t\tsiteId: s.slug,\n\t\t\t\t\t\t\tname: s.name,\n\t\t\t\t\t\t\tstorage: formatStorageLabel(s.storage),\n\t\t\t\t\t\t\tisActive: s.isActive,\n\t\t\t\t\t\t})),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${listDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_save_in_browser',\n\t\t\tdescription: saveDef.description,\n\t\t\tannotations: saveDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst site = getActiveSite(config);\n\t\t\t\t\tconst storage = formatStorageLabel(site.storage);\n\t\t\t\t\tif (storage !== 'temporary') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\talreadySaved: true,\n\t\t\t\t\t\t\tsiteId: site.slug,\n\t\t\t\t\t\t\tname: site.name,\n\t\t\t\t\t\t\tstorage,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst saved = await config.saveInBrowser();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\talreadySaved: false,\n\t\t\t\t\t\tsiteId: saved.slug,\n\t\t\t\t\t\tname: site.name ?? saved.slug,\n\t\t\t\t\t\tstorage: formatStorageLabel(saved.storage),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${saveDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_rename_site',\n\t\t\tdescription: renameDef.description,\n\t\t\tinputSchema: paramsToJsonSchema(renameDef.params),\n\t\t\tannotations: renameDef.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst newName = input['newName'] as string;\n\t\t\t\t\tconst sites = config.list();\n\t\t\t\t\tconst activeSite = sites.find((s) => s.isActive);\n\t\t\t\t\tawait config.rename(newName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tsiteId: activeSite?.slug,\n\t\t\t\t\t\tnewName,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${renameDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_get_website_url',\n\t\t\tdescription: websiteUrlDef.description,\n\t\t\tannotations: websiteUrlDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl: window.location.href,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${websiteUrlDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t];\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient","siteToolDefinitions","getSiteToolDefinitions","registrationController","getActiveSite","active","s","registerWebMCPTools","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","tool","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"mappings":"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"}
|
|
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,147 +1,169 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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) {
|
|
5
|
+
const i = crypto.randomUUID();
|
|
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
|
-
const
|
|
13
|
+
const d = await fetch(
|
|
13
14
|
`http://127.0.0.1:${o}/bridge-token`
|
|
14
15
|
);
|
|
15
|
-
if (!
|
|
16
|
-
|
|
16
|
+
if (!d.ok) {
|
|
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
|
-
var
|
|
27
|
-
|
|
28
|
-
}), e.addEventListener("message", async (
|
|
29
|
-
let
|
|
27
|
+
var d;
|
|
28
|
+
s = "", u(e), (d = n.onConnect) == null || d.call(n);
|
|
29
|
+
}), e.addEventListener("message", async (d) => {
|
|
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:
|
|
38
|
+
const { id: f, method: P, args: $, siteSlug: x } = c;
|
|
38
39
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const y = await W(
|
|
41
|
+
n,
|
|
42
|
+
P,
|
|
43
|
+
$ || [],
|
|
44
|
+
x,
|
|
44
45
|
o
|
|
45
46
|
);
|
|
46
|
-
(e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(JSON.stringify({ id:
|
|
47
|
-
} catch (
|
|
48
|
-
const
|
|
47
|
+
(e == null ? void 0 : e.readyState) === WebSocket.OPEN && e.send(JSON.stringify({ id: f, type: "response", value: y }));
|
|
48
|
+
} catch (y) {
|
|
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
|
-
id:
|
|
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 g =
|
|
98
|
-
let
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
98
|
+
const g = v();
|
|
99
|
+
let m = null;
|
|
100
|
+
function k(n) {
|
|
101
|
+
const i = n.list().find((e) => e.isActive);
|
|
102
|
+
if (!i)
|
|
102
103
|
throw new Error("No active Playground site");
|
|
103
|
-
return
|
|
104
|
+
return i;
|
|
104
105
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
106
|
+
async function L(n) {
|
|
107
|
+
const o = C();
|
|
108
|
+
if (!o)
|
|
107
109
|
return;
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
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)
|
|
113
115
|
throw new Error("No client for active site");
|
|
114
|
-
return
|
|
116
|
+
return r;
|
|
115
117
|
}
|
|
116
|
-
const
|
|
117
|
-
([
|
|
118
|
-
name:
|
|
119
|
-
description:
|
|
120
|
-
inputSchema:
|
|
121
|
-
annotations:
|
|
122
|
-
execute: async (
|
|
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,
|
|
124
|
+
execute: async (d) => {
|
|
123
125
|
try {
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
+
const c = E[r];
|
|
127
|
+
if (!c)
|
|
126
128
|
return {
|
|
127
|
-
error: `No executor for "${
|
|
129
|
+
error: `No executor for "${r}"`
|
|
128
130
|
};
|
|
129
|
-
const
|
|
130
|
-
return await
|
|
131
|
-
} catch (
|
|
131
|
+
const f = b(s());
|
|
132
|
+
return await c(f, d);
|
|
133
|
+
} catch (c) {
|
|
132
134
|
return {
|
|
133
|
-
error: `${
|
|
135
|
+
error: `${a.errorPrefix}: ${p(c)}`
|
|
134
136
|
};
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
})
|
|
138
140
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
t.push(...M(n));
|
|
142
|
+
const l = await Promise.allSettled(
|
|
143
|
+
t.map(async (r) => {
|
|
144
|
+
await o.registerTool(r, { signal: e });
|
|
145
|
+
})
|
|
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
|
|
151
|
+
});
|
|
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)})`
|
|
156
|
+
).join(", ")
|
|
157
|
+
);
|
|
142
158
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
159
|
+
function C() {
|
|
160
|
+
if (typeof document > "u")
|
|
161
|
+
return;
|
|
162
|
+
const n = document.modelContext;
|
|
163
|
+
return typeof (n == null ? void 0 : n.registerTool) == "function" ? n : void 0;
|
|
164
|
+
}
|
|
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;
|
|
145
167
|
return [
|
|
146
168
|
{
|
|
147
169
|
name: "playground_list_sites",
|
|
@@ -151,7 +173,7 @@ function k(r) {
|
|
|
151
173
|
try {
|
|
152
174
|
return {
|
|
153
175
|
connectedTabs: 1,
|
|
154
|
-
sites:
|
|
176
|
+
sites: n.list().map((t) => ({
|
|
155
177
|
siteId: t.slug,
|
|
156
178
|
name: t.name,
|
|
157
179
|
storage: w(t.storage),
|
|
@@ -160,37 +182,37 @@ function k(r) {
|
|
|
160
182
|
};
|
|
161
183
|
} catch (t) {
|
|
162
184
|
return {
|
|
163
|
-
error: `${o.errorPrefix}: ${
|
|
185
|
+
error: `${o.errorPrefix}: ${p(t)}`
|
|
164
186
|
};
|
|
165
187
|
}
|
|
166
188
|
}
|
|
167
189
|
},
|
|
168
190
|
{
|
|
169
191
|
name: "playground_save_in_browser",
|
|
170
|
-
description:
|
|
171
|
-
annotations:
|
|
192
|
+
description: i.description,
|
|
193
|
+
annotations: i.annotations,
|
|
172
194
|
execute: async () => {
|
|
173
195
|
try {
|
|
174
|
-
const t =
|
|
175
|
-
if (
|
|
196
|
+
const t = k(n), l = w(t.storage);
|
|
197
|
+
if (l !== "temporary")
|
|
176
198
|
return {
|
|
177
199
|
success: !0,
|
|
178
200
|
alreadySaved: !0,
|
|
179
201
|
siteId: t.slug,
|
|
180
202
|
name: t.name,
|
|
181
|
-
storage:
|
|
203
|
+
storage: l
|
|
182
204
|
};
|
|
183
|
-
const
|
|
205
|
+
const u = await n.saveInBrowser();
|
|
184
206
|
return {
|
|
185
207
|
success: !0,
|
|
186
208
|
alreadySaved: !1,
|
|
187
|
-
siteId:
|
|
188
|
-
name: t.name ??
|
|
189
|
-
storage: w(
|
|
209
|
+
siteId: u.slug,
|
|
210
|
+
name: t.name ?? u.slug,
|
|
211
|
+
storage: w(u.storage)
|
|
190
212
|
};
|
|
191
213
|
} catch (t) {
|
|
192
214
|
return {
|
|
193
|
-
error: `${
|
|
215
|
+
error: `${i.errorPrefix}: ${p(t)}`
|
|
194
216
|
};
|
|
195
217
|
}
|
|
196
218
|
}
|
|
@@ -198,27 +220,27 @@ function k(r) {
|
|
|
198
220
|
{
|
|
199
221
|
name: "playground_rename_site",
|
|
200
222
|
description: e.description,
|
|
201
|
-
inputSchema:
|
|
223
|
+
inputSchema: _(e.params),
|
|
202
224
|
annotations: e.annotations,
|
|
203
225
|
execute: async (t) => {
|
|
204
226
|
try {
|
|
205
|
-
const
|
|
206
|
-
return await
|
|
227
|
+
const l = t.newName, r = n.list().find((a) => a.isActive);
|
|
228
|
+
return await n.rename(l), {
|
|
207
229
|
success: !0,
|
|
208
|
-
siteId:
|
|
209
|
-
newName:
|
|
230
|
+
siteId: r == null ? void 0 : r.slug,
|
|
231
|
+
newName: l
|
|
210
232
|
};
|
|
211
|
-
} catch (
|
|
233
|
+
} catch (l) {
|
|
212
234
|
return {
|
|
213
|
-
error: `${e.errorPrefix}: ${
|
|
235
|
+
error: `${e.errorPrefix}: ${p(l)}`
|
|
214
236
|
};
|
|
215
237
|
}
|
|
216
238
|
}
|
|
217
239
|
},
|
|
218
240
|
{
|
|
219
241
|
name: "playground_get_website_url",
|
|
220
|
-
description:
|
|
221
|
-
annotations:
|
|
242
|
+
description: s.description,
|
|
243
|
+
annotations: s.annotations,
|
|
222
244
|
execute: async () => {
|
|
223
245
|
try {
|
|
224
246
|
return {
|
|
@@ -226,15 +248,84 @@ function k(r) {
|
|
|
226
248
|
};
|
|
227
249
|
} catch (t) {
|
|
228
250
|
return {
|
|
229
|
-
error: `${
|
|
251
|
+
error: `${s.errorPrefix}: ${p(t)}`
|
|
230
252
|
};
|
|
231
253
|
}
|
|
232
254
|
}
|
|
233
255
|
}
|
|
234
256
|
];
|
|
235
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
|
+
}
|
|
236
326
|
export {
|
|
237
|
-
|
|
238
|
-
|
|
327
|
+
L as registerWebMCPTools,
|
|
328
|
+
J as startMcpBridge,
|
|
329
|
+
j as startWebMCPSiteToolProxy
|
|
239
330
|
};
|
|
240
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 `navigator.modelContext` (the\n * Chrome WebMCP API) so that browser-side AI agents can interact\n * with the running Playground site.\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport {\n\ttoolDefinitions,\n\tgetSiteToolDefinitions,\n\tformatStorageLabel,\n\tparamsToJsonSchema,\n\tstringifyError,\n} from './tools/tool-definitions';\nimport { toolExecutors, createToolClient } from './tools/tool-executors';\nimport type { PlaygroundBridgeConfig } from './bridge-client';\n\nconst siteToolDefinitions = getSiteToolDefinitions();\n\n// -- WebMCP type declarations --\n\ninterface ModelContextTool {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: Record<string, unknown>;\n\texecute: (\n\t\tinput: Record<string, unknown>,\n\t\tclient: ModelContextClient\n\t) => Promise<unknown>;\n\tannotations?: { readOnlyHint?: boolean; destructiveHint?: boolean };\n}\n\ninterface ModelContextClient {\n\trequestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContext {\n\tprovideContext(options: { tools: ModelContextTool[] }): void;\n\tregisterTool(\n\t\ttool: ModelContextTool,\n\t\toptions?: { signal?: AbortSignal }\n\t): void;\n\treadonly tools: ModelContextTool[];\n}\n\ndeclare global {\n\tinterface Navigator {\n\t\tmodelContext?: ModelContext;\n\t}\n}\n\n// -- Registration --\n\nlet registrationController: AbortController | null = null;\n\nfunction getActiveSite(config: PlaygroundBridgeConfig) {\n\tconst sites = config.list();\n\tconst active = sites.find((s) => s.isActive);\n\tif (!active) {\n\t\tthrow new Error('No active Playground site');\n\t}\n\treturn active;\n}\n\nexport function registerWebMCPTools(config: PlaygroundBridgeConfig): void {\n\tif (typeof navigator === 'undefined' || !navigator.modelContext) {\n\t\treturn;\n\t}\n\n\t// Abort any previous registration before re-registering.\n\tregistrationController?.abort();\n\tregistrationController = new AbortController();\n\tconst signal = registrationController.signal;\n\n\tfunction getActiveClient(): PlaygroundClient {\n\t\tconst client = config.getClient();\n\t\tif (!client) {\n\t\t\tthrow new Error('No client for active site');\n\t\t}\n\t\treturn client;\n\t}\n\n\t// Per-site tools\n\tconst tools: ModelContextTool[] = Object.entries(toolDefinitions).map(\n\t\t([name, def]) => ({\n\t\t\tname,\n\t\t\tdescription: def.description,\n\t\t\tinputSchema: paramsToJsonSchema(def.params),\n\t\t\tannotations: def.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst executor = toolExecutors[name];\n\t\t\t\t\tif (!executor) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\terror: `No executor for \"${name}\"`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst adapter = createToolClient(getActiveClient());\n\t\t\t\t\treturn await executor(adapter, input);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${def.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t);\n\n\t// Site management tools\n\ttools.push(...createSiteManagementTools(config));\n\n\tfor (const tool of tools) {\n\t\tnavigator.modelContext.registerTool(tool, { signal });\n\t}\n}\n\nfunction createSiteManagementTools(\n\tconfig: PlaygroundBridgeConfig\n): ModelContextTool[] {\n\tconst listDef = siteToolDefinitions['playground_list_sites'];\n\tconst saveDef = siteToolDefinitions['playground_save_in_browser'];\n\tconst renameDef = siteToolDefinitions['playground_rename_site'];\n\tconst websiteUrlDef = siteToolDefinitions['playground_get_website_url'];\n\n\treturn [\n\t\t{\n\t\t\tname: 'playground_list_sites',\n\t\t\tdescription: listDef.description,\n\t\t\tannotations: listDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tconnectedTabs: 1,\n\t\t\t\t\t\tsites: config.list().map((s) => ({\n\t\t\t\t\t\t\tsiteId: s.slug,\n\t\t\t\t\t\t\tname: s.name,\n\t\t\t\t\t\t\tstorage: formatStorageLabel(s.storage),\n\t\t\t\t\t\t\tisActive: s.isActive,\n\t\t\t\t\t\t})),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${listDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_save_in_browser',\n\t\t\tdescription: saveDef.description,\n\t\t\tannotations: saveDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst site = getActiveSite(config);\n\t\t\t\t\tconst storage = formatStorageLabel(site.storage);\n\t\t\t\t\tif (storage !== 'temporary') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\talreadySaved: true,\n\t\t\t\t\t\t\tsiteId: site.slug,\n\t\t\t\t\t\t\tname: site.name,\n\t\t\t\t\t\t\tstorage,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tconst saved = await config.saveInBrowser();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\talreadySaved: false,\n\t\t\t\t\t\tsiteId: saved.slug,\n\t\t\t\t\t\tname: site.name ?? saved.slug,\n\t\t\t\t\t\tstorage: formatStorageLabel(saved.storage),\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${saveDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_rename_site',\n\t\t\tdescription: renameDef.description,\n\t\t\tinputSchema: paramsToJsonSchema(renameDef.params),\n\t\t\tannotations: renameDef.annotations,\n\t\t\texecute: async (input) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst newName = input['newName'] as string;\n\t\t\t\t\tconst sites = config.list();\n\t\t\t\t\tconst activeSite = sites.find((s) => s.isActive);\n\t\t\t\t\tawait config.rename(newName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tsiteId: activeSite?.slug,\n\t\t\t\t\t\tnewName,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${renameDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: 'playground_get_website_url',\n\t\t\tdescription: websiteUrlDef.description,\n\t\t\tannotations: websiteUrlDef.annotations,\n\t\t\texecute: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl: window.location.href,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: `${websiteUrlDef.errorPrefix}: ${stringifyError(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t];\n}\n"],"names":["RECONNECT_INTERVAL_MS","startMcpBridge","config","port","tabId","ws","previousSitesSerialized","reconnectTimer","stopped","sendSitesRegistration","socket","sites","serialized","connect","response","scheduleReconnect","token","_a","event","message","id","method","args","siteSlug","value","handleCommand","error","errorMsg","url","newName","playgroundClient","fn","createToolClient","siteToolDefinitions","getSiteToolDefinitions","registrationController","getActiveSite","active","s","registerWebMCPTools","signal","getActiveClient","client","tools","toolDefinitions","name","def","paramsToJsonSchema","input","executor","toolExecutors","adapter","stringifyError","createSiteManagementTools","tool","listDef","saveDef","renameDef","websiteUrlDef","formatStorageLabel","site","storage","saved","activeSite"],"mappings":"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"}
|
|
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.53",
|
|
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.53",
|
|
43
|
+
"@php-wasm/universal": "3.1.53",
|
|
44
|
+
"@php-wasm/logger": "3.1.53"
|
|
44
45
|
},
|
|
45
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "8cd60ace8c3d32c8cb2b569b7471fdd98f88013e",
|
|
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
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebMCP registration for WordPress Playground.
|
|
3
3
|
*
|
|
4
|
-
* Registers playground tools with `
|
|
4
|
+
* Registers playground tools with `document.modelContext` (the
|
|
5
5
|
* Chrome WebMCP API) so that browser-side AI agents can interact
|
|
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>;
|
|
@@ -25,13 +25,14 @@ interface ModelContext {
|
|
|
25
25
|
}): void;
|
|
26
26
|
registerTool(tool: ModelContextTool, options?: {
|
|
27
27
|
signal?: AbortSignal;
|
|
28
|
-
}): void;
|
|
28
|
+
}): Promise<void> | void;
|
|
29
29
|
readonly tools: ModelContextTool[];
|
|
30
30
|
}
|
|
31
31
|
declare global {
|
|
32
|
-
interface
|
|
32
|
+
interface Document {
|
|
33
33
|
modelContext?: ModelContext;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
export declare function registerWebMCPTools(config: PlaygroundBridgeConfig): void
|
|
36
|
+
export declare function registerWebMCPTools(config: PlaygroundBridgeConfig): Promise<void>;
|
|
37
|
+
export declare function getModelContext(): ModelContext | undefined;
|
|
37
38
|
export {};
|