@wp-playground/mcp 3.1.5 → 3.1.9

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.
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createServer(): McpServer;
package/package.json CHANGED
@@ -1,46 +1,80 @@
1
1
  {
2
- "name": "@wp-playground/mcp",
3
- "version": "3.1.5",
4
- "description": "MCP server for WordPress Playground - enables AI agents to interact with the WordPress Playground website.",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/WordPress/wordpress-playground"
8
- },
9
- "homepage": "https://developer.wordpress.org/playground",
10
- "author": "The WordPress contributors",
11
- "license": "GPL-2.0-or-later",
12
- "type": "module",
13
- "bin": {
14
- "wp-playground-mcp": "./index.js"
15
- },
16
- "exports": {
17
- ".": {
18
- "import": "./src/index.ts",
19
- "require": "./index.cjs"
20
- },
21
- "./client": {
22
- "import": "./src/client.ts",
23
- "require": "./client.cjs"
24
- },
25
- "./package.json": "./package.json"
26
- },
27
- "publishConfig": {
28
- "access": "public",
29
- "directory": "../../../dist/packages/playground/mcp"
30
- },
31
- "main": "./index.cjs",
32
- "module": "./index.js",
33
- "types": "index.d.ts",
34
- "engines": {
35
- "node": ">=20.10.0",
36
- "npm": ">=10.2.3"
37
- },
38
- "dependencies": {
39
- "@modelcontextprotocol/sdk": "^1.27.0",
40
- "ws": "^8.18.0",
41
- "zod": "^4.3"
42
- },
43
- "devDependencies": {
44
- "@types/ws": "^8.18.0"
45
- }
46
- }
2
+ "name": "@wp-playground/mcp",
3
+ "version": "3.1.9",
4
+ "description": "MCP server for WordPress Playground - enables AI agents to interact with the WordPress Playground website.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/WordPress/wordpress-playground"
8
+ },
9
+ "homepage": "https://developer.wordpress.org/playground",
10
+ "author": "The WordPress contributors",
11
+ "license": "GPL-2.0-or-later",
12
+ "type": "module",
13
+ "bin": {
14
+ "wp-playground-mcp": "./index.js"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "import": "./index.js",
19
+ "require": "./index.cjs"
20
+ },
21
+ "./client": {
22
+ "import": "./client.js",
23
+ "require": "./client.cjs"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "directory": "../../../dist/packages/playground/mcp"
30
+ },
31
+ "main": "./index.cjs",
32
+ "module": "./index.js",
33
+ "types": "index.d.ts",
34
+ "engines": {
35
+ "node": ">=20.10.0",
36
+ "npm": ">=10.2.3"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.27.0",
40
+ "@types/ws": "8.18.1",
41
+ "@zip.js/zip.js": "2.7.57",
42
+ "ajv": "8.12.0",
43
+ "async-lock": "1.4.1",
44
+ "clean-git-ref": "2.0.1",
45
+ "crc-32": "1.2.2",
46
+ "diff3": "0.0.4",
47
+ "express": "4.22.0",
48
+ "fast-xml-parser": "^5.5.1",
49
+ "fs-ext-extra-prebuilt": "2.2.7",
50
+ "ignore": "5.3.2",
51
+ "ini": "4.1.2",
52
+ "jsonc-parser": "3.3.1",
53
+ "minimisted": "2.0.1",
54
+ "octokit": "3.1.2",
55
+ "pako": "1.0.10",
56
+ "pify": "2.3.0",
57
+ "readable-stream": "3.6.2",
58
+ "sha.js": "2.4.12",
59
+ "simple-get": "4.0.1",
60
+ "wasm-feature-detect": "1.8.0",
61
+ "ws": "^8.18.0",
62
+ "yargs": "17.7.2",
63
+ "zod": "^4.3",
64
+ "@wp-playground/remote": "3.1.9",
65
+ "@php-wasm/universal": "3.1.9"
66
+ },
67
+ "gitHead": "97cf88501a336453baaac9a860a17329235a71fb",
68
+ "packageManager": "npm@10.9.2",
69
+ "overrides": {
70
+ "rollup": "^4.34.6",
71
+ "react": "18.3.1",
72
+ "react-dom": "18.3.1",
73
+ "typescript": "5.4.5",
74
+ "@playwright/test": "1.55.1",
75
+ "tmp": "0.2.5",
76
+ "form-data": "^4.0.4",
77
+ "lodash": "^4.17.23",
78
+ "glob": "^9.3.0"
79
+ }
80
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";async function u(r){const[e,s]=await Promise.all([r.getCurrentURL(),r.run({code:`<?php
2
+ require_once "/wordpress/wp-load.php";
3
+ echo json_encode([
4
+ "documentRoot" => ABSPATH,
5
+ "wpVersion" => get_bloginfo("version"),
6
+ "siteUrl" => get_site_url(),
7
+ "phpVersion" => phpversion(),
8
+ ]);`}).then(o=>o.text)]);let t;try{t=JSON.parse(s)}catch{t={}}return{url:String(e),documentRoot:t.documentRoot??"/wordpress",siteUrl:t.siteUrl??String(e),wpVersion:t.wpVersion??"unknown",phpVersion:t.phpVersion??"unknown"}}const i={playground_execute_php:(r,e)=>r.run({code:e.code}),playground_request:async(r,e)=>{const s={url:e.url,method:e.method??"GET"};return e.headers&&(s.headers=e.headers),e.body&&(s.body=e.body),await r.request(s)},playground_navigate:async(r,e)=>(await r.goTo(e.path),{url:await r.getCurrentURL()}),playground_get_current_url:async r=>({url:await r.getCurrentURL()}),playground_get_site_info:r=>u(r),playground_read_file:async(r,e)=>({contents:await r.readFileAsText(e.path)}),playground_write_file:async(r,e)=>(await r.writeFile(e.path,e.contents),{success:!0}),playground_list_files:async(r,e)=>({files:await r.listFiles(e.path)}),playground_mkdir:async(r,e)=>(await r.mkdirTree(e.path),{success:!0}),playground_delete_file:async(r,e)=>(await r.unlink(e.path),{success:!0}),playground_delete_directory:async(r,e)=>(await r.rmdir(e.path,{recursive:e.recursive??!1}),{success:!0}),playground_file_exists:async(r,e)=>({exists:await r.fileExists(e.path)})};function d(r){const e=new TextDecoder,s={async run(t){const o=await r.run(t);return{text:e.decode(o.bytes),errors:o.errors,exitCode:o.exitCode}},async request(t){const o=await r.request({url:t.url,method:t.method,headers:t.headers,body:t.body});return{text:e.decode(o.bytes),httpStatusCode:o.httpStatusCode,headers:o.headers}}};return new Proxy(r,{get:(t,o)=>{const a=s[o];if(a!==void 0)return a;const n=t[o];return typeof n=="function"?n.bind(t):n}})}exports.createToolClient=d;exports.toolExecutors=i;
9
+ //# sourceMappingURL=tool-executors-ecXfLzk7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-executors-ecXfLzk7.cjs","sources":["../../../../packages/playground/mcp/src/tools/tool-executors.ts"],"sourcesContent":["/**\n * Shared tool executor functions.\n *\n * Both the MCP server and WebMCP call these executors so that tool\n * output shapes are defined in exactly one place. Each transport\n * provides its own ToolClient implementation that normalises I/O\n * differences (e.g. byte decoding).\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport type { PHPRequest } from '@php-wasm/universal';\n\n/**\n * Minimal client interface consumed by tool executors.\n *\n * - WebMCP implements this by wrapping PlaygroundClient (decoding\n * response bytes via TextDecoder).\n * - The MCP server implements this by wrapping bridge.sendCommand\n * (bytes are already decoded at the bridge-client boundary).\n */\nexport interface ToolClient {\n\trun(options: {\n\t\tcode: string;\n\t}): Promise<{ text: string; errors: string; exitCode: number }>;\n\trequest(options: {\n\t\turl: string;\n\t\tmethod: string;\n\t\theaders?: Record<string, string>;\n\t\tbody?: string;\n\t}): Promise<{\n\t\ttext: string;\n\t\thttpStatusCode: number;\n\t\theaders: Record<string, string[]>;\n\t}>;\n\tgoTo(path: string): Promise<void>;\n\tgetCurrentURL(): Promise<string>;\n\treadFileAsText(path: string): Promise<string>;\n\twriteFile(path: string, contents: string): Promise<void>;\n\tlistFiles(path: string): Promise<string[]>;\n\tmkdirTree(path: string): Promise<void>;\n\tunlink(path: string): Promise<void>;\n\trmdir(path: string, options: { recursive: boolean }): Promise<void>;\n\tfileExists(path: string): Promise<boolean>;\n}\n\nexport interface SiteInfo {\n\turl: string;\n\tdocumentRoot: string;\n\tsiteUrl: string;\n\twpVersion: string;\n\tphpVersion: string;\n}\n\nasync function executeSiteInfo(client: ToolClient): Promise<SiteInfo> {\n\tconst [url, infoText] = await Promise.all([\n\t\tclient.getCurrentURL(),\n\t\tclient\n\t\t\t.run({\n\t\t\t\tcode: `<?php\n\t\t\trequire_once \"/wordpress/wp-load.php\";\n\t\t\techo json_encode([\n\t\t\t\t\"documentRoot\" => ABSPATH,\n\t\t\t\t\"wpVersion\" => get_bloginfo(\"version\"),\n\t\t\t\t\"siteUrl\" => get_site_url(),\n\t\t\t\t\"phpVersion\" => phpversion(),\n\t\t\t]);`,\n\t\t\t})\n\t\t\t.then((resp) => resp.text),\n\t]);\n\n\tlet info: Partial<Omit<SiteInfo, 'url'>>;\n\ttry {\n\t\tinfo = JSON.parse(infoText);\n\t} catch {\n\t\tinfo = {};\n\t}\n\n\treturn {\n\t\turl: String(url),\n\t\tdocumentRoot: info.documentRoot ?? '/wordpress',\n\t\tsiteUrl: info.siteUrl ?? String(url),\n\t\twpVersion: info.wpVersion ?? 'unknown',\n\t\tphpVersion: info.phpVersion ?? 'unknown',\n\t};\n}\n\nexport const toolExecutors: Record<\n\tstring,\n\t(client: ToolClient, input: Record<string, unknown>) => Promise<unknown>\n> = {\n\tplayground_execute_php: (client, input) =>\n\t\tclient.run({ code: input['code'] as string }),\n\n\tplayground_request: async (client, input) => {\n\t\tconst options: {\n\t\t\turl: string;\n\t\t\tmethod: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\tbody?: string;\n\t\t} = {\n\t\t\turl: input['url'] as string,\n\t\t\tmethod: (input['method'] as string) ?? 'GET',\n\t\t};\n\t\tif (input['headers']) {\n\t\t\toptions.headers = input['headers'] as Record<string, string>;\n\t\t}\n\t\tif (input['body']) {\n\t\t\toptions.body = input['body'] as string;\n\t\t}\n\t\treturn await client.request(options);\n\t},\n\n\tplayground_navigate: async (client, input) => {\n\t\tawait client.goTo(input['path'] as string);\n\t\treturn { url: await client.getCurrentURL() };\n\t},\n\n\tplayground_get_current_url: async (client) => ({\n\t\turl: await client.getCurrentURL(),\n\t}),\n\n\tplayground_get_site_info: (client): Promise<SiteInfo> =>\n\t\texecuteSiteInfo(client),\n\n\tplayground_read_file: async (client, input) => ({\n\t\tcontents: await client.readFileAsText(input['path'] as string),\n\t}),\n\n\tplayground_write_file: async (client, input) => {\n\t\tawait client.writeFile(\n\t\t\tinput['path'] as string,\n\t\t\tinput['contents'] as string\n\t\t);\n\t\treturn { success: true };\n\t},\n\n\tplayground_list_files: async (client, input) => ({\n\t\tfiles: await client.listFiles(input['path'] as string),\n\t}),\n\n\tplayground_mkdir: async (client, input) => {\n\t\tawait client.mkdirTree(input['path'] as string);\n\t\treturn { success: true };\n\t},\n\n\tplayground_delete_file: async (client, input) => {\n\t\tawait client.unlink(input['path'] as string);\n\t\treturn { success: true };\n\t},\n\n\tplayground_delete_directory: async (client, input) => {\n\t\tawait client.rmdir(input['path'] as string, {\n\t\t\trecursive: (input['recursive'] as boolean) ?? false,\n\t\t});\n\t\treturn { success: true };\n\t},\n\n\tplayground_file_exists: async (client, input) => ({\n\t\texists: await client.fileExists(input['path'] as string),\n\t}),\n};\n\n/**\n * Wrap a PlaygroundClient as a ToolClient.\n *\n * Most methods pass through directly. Only `run` and `request`\n * are intercepted to decode PHP/HTTP response bytes into plain\n * strings via TextDecoder.\n */\nexport function createToolClient(client: PlaygroundClient): ToolClient {\n\tconst decoder = new TextDecoder();\n\tconst overrides: Partial<ToolClient> = {\n\t\tasync run(options) {\n\t\t\tconst resp = await client.run(options);\n\t\t\treturn {\n\t\t\t\ttext: decoder.decode(resp.bytes),\n\t\t\t\terrors: resp.errors,\n\t\t\t\texitCode: resp.exitCode,\n\t\t\t};\n\t\t},\n\t\tasync request(options) {\n\t\t\tconst resp = await client.request({\n\t\t\t\turl: options.url,\n\t\t\t\tmethod: options.method as PHPRequest['method'],\n\t\t\t\theaders: options.headers,\n\t\t\t\tbody: options.body,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\ttext: decoder.decode(resp.bytes),\n\t\t\t\thttpStatusCode: resp.httpStatusCode,\n\t\t\t\theaders: resp.headers,\n\t\t\t};\n\t\t},\n\t};\n\treturn new Proxy(client as unknown as ToolClient, {\n\t\tget: (target, prop: string) => {\n\t\t\tconst override = (overrides as Record<string, unknown>)[prop];\n\t\t\tif (override !== undefined) {\n\t\t\t\treturn override;\n\t\t\t}\n\t\t\tconst val = (target as unknown as Record<string, unknown>)[prop];\n\t\t\treturn typeof val === 'function' ? val.bind(target) : val;\n\t\t},\n\t});\n}\n"],"names":["executeSiteInfo","client","url","infoText","resp","info","toolExecutors","input","options","createToolClient","decoder","overrides","target","prop","override","val"],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ async function u(r) {
2
+ const [e, o] = await Promise.all([
3
+ r.getCurrentURL(),
4
+ r.run({
5
+ code: `<?php
6
+ require_once "/wordpress/wp-load.php";
7
+ echo json_encode([
8
+ "documentRoot" => ABSPATH,
9
+ "wpVersion" => get_bloginfo("version"),
10
+ "siteUrl" => get_site_url(),
11
+ "phpVersion" => phpversion(),
12
+ ]);`
13
+ }).then((s) => s.text)
14
+ ]);
15
+ let t;
16
+ try {
17
+ t = JSON.parse(o);
18
+ } catch {
19
+ t = {};
20
+ }
21
+ return {
22
+ url: String(e),
23
+ documentRoot: t.documentRoot ?? "/wordpress",
24
+ siteUrl: t.siteUrl ?? String(e),
25
+ wpVersion: t.wpVersion ?? "unknown",
26
+ phpVersion: t.phpVersion ?? "unknown"
27
+ };
28
+ }
29
+ const d = {
30
+ playground_execute_php: (r, e) => r.run({ code: e.code }),
31
+ playground_request: async (r, e) => {
32
+ const o = {
33
+ url: e.url,
34
+ method: e.method ?? "GET"
35
+ };
36
+ return e.headers && (o.headers = e.headers), e.body && (o.body = e.body), await r.request(o);
37
+ },
38
+ playground_navigate: async (r, e) => (await r.goTo(e.path), { url: await r.getCurrentURL() }),
39
+ playground_get_current_url: async (r) => ({
40
+ url: await r.getCurrentURL()
41
+ }),
42
+ playground_get_site_info: (r) => u(r),
43
+ playground_read_file: async (r, e) => ({
44
+ contents: await r.readFileAsText(e.path)
45
+ }),
46
+ playground_write_file: async (r, e) => (await r.writeFile(
47
+ e.path,
48
+ e.contents
49
+ ), { success: !0 }),
50
+ playground_list_files: async (r, e) => ({
51
+ files: await r.listFiles(e.path)
52
+ }),
53
+ playground_mkdir: async (r, e) => (await r.mkdirTree(e.path), { success: !0 }),
54
+ playground_delete_file: async (r, e) => (await r.unlink(e.path), { success: !0 }),
55
+ playground_delete_directory: async (r, e) => (await r.rmdir(e.path, {
56
+ recursive: e.recursive ?? !1
57
+ }), { success: !0 }),
58
+ playground_file_exists: async (r, e) => ({
59
+ exists: await r.fileExists(e.path)
60
+ })
61
+ };
62
+ function i(r) {
63
+ const e = new TextDecoder(), o = {
64
+ async run(t) {
65
+ const s = await r.run(t);
66
+ return {
67
+ text: e.decode(s.bytes),
68
+ errors: s.errors,
69
+ exitCode: s.exitCode
70
+ };
71
+ },
72
+ async request(t) {
73
+ const s = await r.request({
74
+ url: t.url,
75
+ method: t.method,
76
+ headers: t.headers,
77
+ body: t.body
78
+ });
79
+ return {
80
+ text: e.decode(s.bytes),
81
+ httpStatusCode: s.httpStatusCode,
82
+ headers: s.headers
83
+ };
84
+ }
85
+ };
86
+ return new Proxy(r, {
87
+ get: (t, s) => {
88
+ const a = o[s];
89
+ if (a !== void 0)
90
+ return a;
91
+ const n = t[s];
92
+ return typeof n == "function" ? n.bind(t) : n;
93
+ }
94
+ });
95
+ }
96
+ export {
97
+ i as c,
98
+ d as t
99
+ };
100
+ //# sourceMappingURL=tool-executors-pMxJ1GXT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-executors-pMxJ1GXT.js","sources":["../../../../packages/playground/mcp/src/tools/tool-executors.ts"],"sourcesContent":["/**\n * Shared tool executor functions.\n *\n * Both the MCP server and WebMCP call these executors so that tool\n * output shapes are defined in exactly one place. Each transport\n * provides its own ToolClient implementation that normalises I/O\n * differences (e.g. byte decoding).\n */\n\nimport type { PlaygroundClient } from '@wp-playground/remote';\nimport type { PHPRequest } from '@php-wasm/universal';\n\n/**\n * Minimal client interface consumed by tool executors.\n *\n * - WebMCP implements this by wrapping PlaygroundClient (decoding\n * response bytes via TextDecoder).\n * - The MCP server implements this by wrapping bridge.sendCommand\n * (bytes are already decoded at the bridge-client boundary).\n */\nexport interface ToolClient {\n\trun(options: {\n\t\tcode: string;\n\t}): Promise<{ text: string; errors: string; exitCode: number }>;\n\trequest(options: {\n\t\turl: string;\n\t\tmethod: string;\n\t\theaders?: Record<string, string>;\n\t\tbody?: string;\n\t}): Promise<{\n\t\ttext: string;\n\t\thttpStatusCode: number;\n\t\theaders: Record<string, string[]>;\n\t}>;\n\tgoTo(path: string): Promise<void>;\n\tgetCurrentURL(): Promise<string>;\n\treadFileAsText(path: string): Promise<string>;\n\twriteFile(path: string, contents: string): Promise<void>;\n\tlistFiles(path: string): Promise<string[]>;\n\tmkdirTree(path: string): Promise<void>;\n\tunlink(path: string): Promise<void>;\n\trmdir(path: string, options: { recursive: boolean }): Promise<void>;\n\tfileExists(path: string): Promise<boolean>;\n}\n\nexport interface SiteInfo {\n\turl: string;\n\tdocumentRoot: string;\n\tsiteUrl: string;\n\twpVersion: string;\n\tphpVersion: string;\n}\n\nasync function executeSiteInfo(client: ToolClient): Promise<SiteInfo> {\n\tconst [url, infoText] = await Promise.all([\n\t\tclient.getCurrentURL(),\n\t\tclient\n\t\t\t.run({\n\t\t\t\tcode: `<?php\n\t\t\trequire_once \"/wordpress/wp-load.php\";\n\t\t\techo json_encode([\n\t\t\t\t\"documentRoot\" => ABSPATH,\n\t\t\t\t\"wpVersion\" => get_bloginfo(\"version\"),\n\t\t\t\t\"siteUrl\" => get_site_url(),\n\t\t\t\t\"phpVersion\" => phpversion(),\n\t\t\t]);`,\n\t\t\t})\n\t\t\t.then((resp) => resp.text),\n\t]);\n\n\tlet info: Partial<Omit<SiteInfo, 'url'>>;\n\ttry {\n\t\tinfo = JSON.parse(infoText);\n\t} catch {\n\t\tinfo = {};\n\t}\n\n\treturn {\n\t\turl: String(url),\n\t\tdocumentRoot: info.documentRoot ?? '/wordpress',\n\t\tsiteUrl: info.siteUrl ?? String(url),\n\t\twpVersion: info.wpVersion ?? 'unknown',\n\t\tphpVersion: info.phpVersion ?? 'unknown',\n\t};\n}\n\nexport const toolExecutors: Record<\n\tstring,\n\t(client: ToolClient, input: Record<string, unknown>) => Promise<unknown>\n> = {\n\tplayground_execute_php: (client, input) =>\n\t\tclient.run({ code: input['code'] as string }),\n\n\tplayground_request: async (client, input) => {\n\t\tconst options: {\n\t\t\turl: string;\n\t\t\tmethod: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\tbody?: string;\n\t\t} = {\n\t\t\turl: input['url'] as string,\n\t\t\tmethod: (input['method'] as string) ?? 'GET',\n\t\t};\n\t\tif (input['headers']) {\n\t\t\toptions.headers = input['headers'] as Record<string, string>;\n\t\t}\n\t\tif (input['body']) {\n\t\t\toptions.body = input['body'] as string;\n\t\t}\n\t\treturn await client.request(options);\n\t},\n\n\tplayground_navigate: async (client, input) => {\n\t\tawait client.goTo(input['path'] as string);\n\t\treturn { url: await client.getCurrentURL() };\n\t},\n\n\tplayground_get_current_url: async (client) => ({\n\t\turl: await client.getCurrentURL(),\n\t}),\n\n\tplayground_get_site_info: (client): Promise<SiteInfo> =>\n\t\texecuteSiteInfo(client),\n\n\tplayground_read_file: async (client, input) => ({\n\t\tcontents: await client.readFileAsText(input['path'] as string),\n\t}),\n\n\tplayground_write_file: async (client, input) => {\n\t\tawait client.writeFile(\n\t\t\tinput['path'] as string,\n\t\t\tinput['contents'] as string\n\t\t);\n\t\treturn { success: true };\n\t},\n\n\tplayground_list_files: async (client, input) => ({\n\t\tfiles: await client.listFiles(input['path'] as string),\n\t}),\n\n\tplayground_mkdir: async (client, input) => {\n\t\tawait client.mkdirTree(input['path'] as string);\n\t\treturn { success: true };\n\t},\n\n\tplayground_delete_file: async (client, input) => {\n\t\tawait client.unlink(input['path'] as string);\n\t\treturn { success: true };\n\t},\n\n\tplayground_delete_directory: async (client, input) => {\n\t\tawait client.rmdir(input['path'] as string, {\n\t\t\trecursive: (input['recursive'] as boolean) ?? false,\n\t\t});\n\t\treturn { success: true };\n\t},\n\n\tplayground_file_exists: async (client, input) => ({\n\t\texists: await client.fileExists(input['path'] as string),\n\t}),\n};\n\n/**\n * Wrap a PlaygroundClient as a ToolClient.\n *\n * Most methods pass through directly. Only `run` and `request`\n * are intercepted to decode PHP/HTTP response bytes into plain\n * strings via TextDecoder.\n */\nexport function createToolClient(client: PlaygroundClient): ToolClient {\n\tconst decoder = new TextDecoder();\n\tconst overrides: Partial<ToolClient> = {\n\t\tasync run(options) {\n\t\t\tconst resp = await client.run(options);\n\t\t\treturn {\n\t\t\t\ttext: decoder.decode(resp.bytes),\n\t\t\t\terrors: resp.errors,\n\t\t\t\texitCode: resp.exitCode,\n\t\t\t};\n\t\t},\n\t\tasync request(options) {\n\t\t\tconst resp = await client.request({\n\t\t\t\turl: options.url,\n\t\t\t\tmethod: options.method as PHPRequest['method'],\n\t\t\t\theaders: options.headers,\n\t\t\t\tbody: options.body,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\ttext: decoder.decode(resp.bytes),\n\t\t\t\thttpStatusCode: resp.httpStatusCode,\n\t\t\t\theaders: resp.headers,\n\t\t\t};\n\t\t},\n\t};\n\treturn new Proxy(client as unknown as ToolClient, {\n\t\tget: (target, prop: string) => {\n\t\t\tconst override = (overrides as Record<string, unknown>)[prop];\n\t\t\tif (override !== undefined) {\n\t\t\t\treturn override;\n\t\t\t}\n\t\t\tconst val = (target as unknown as Record<string, unknown>)[prop];\n\t\t\treturn typeof val === 'function' ? val.bind(target) : val;\n\t\t},\n\t});\n}\n"],"names":["executeSiteInfo","client","url","infoText","resp","info","toolExecutors","input","options","createToolClient","decoder","overrides","target","prop","override","val"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { PlaygroundBridge } from '../bridge-server';
3
+ export declare function registerMcpServerTools(server: McpServer, bridge: PlaygroundBridge, port: number): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Tool metadata and schema helpers for WordPress Playground.
3
+ *
4
+ * Pure data — no execution logic. Both the MCP server and
5
+ * WebMCP import these for consistent descriptions, annotations,
6
+ * and schema conversion.
7
+ */
8
+ export interface ToolAnnotations {
9
+ readOnlyHint?: boolean;
10
+ destructiveHint?: boolean;
11
+ idempotentHint?: boolean;
12
+ openWorldHint?: boolean;
13
+ }
14
+ export type ToolParamType = 'string' | 'boolean' | 'object';
15
+ export interface ToolParam {
16
+ name: string;
17
+ type: ToolParamType;
18
+ description: string;
19
+ required: boolean;
20
+ additionalProperties?: boolean;
21
+ default?: unknown;
22
+ }
23
+ export interface ToolDefinition {
24
+ title: string;
25
+ description: string;
26
+ errorPrefix: string;
27
+ annotations: ToolAnnotations;
28
+ params: ToolParam[];
29
+ }
30
+ export declare function playgroundUrl(port: number): string;
31
+ export declare const toolDefinitions: Record<string, ToolDefinition>;
32
+ export declare function getSiteToolDefinitions(): Record<string, ToolDefinition>;
33
+ export declare function stringifyError(error: unknown): string;
34
+ /**
35
+ * Translate internal Playground storage types to user-facing names.
36
+ */
37
+ export declare function presentStorage(raw: string): string;
38
+ /**
39
+ * Convert ToolParam[] to a plain JSON Schema object.
40
+ * Used by WebMCP which expects raw JSON Schema (not Zod).
41
+ */
42
+ export declare function paramsToJsonSchema(params: ToolParam[]): Record<string, unknown>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared tool executor functions.
3
+ *
4
+ * Both the MCP server and WebMCP call these executors so that tool
5
+ * output shapes are defined in exactly one place. Each transport
6
+ * provides its own ToolClient implementation that normalises I/O
7
+ * differences (e.g. byte decoding).
8
+ */
9
+ import type { PlaygroundClient } from '@wp-playground/remote';
10
+ /**
11
+ * Minimal client interface consumed by tool executors.
12
+ *
13
+ * - WebMCP implements this by wrapping PlaygroundClient (decoding
14
+ * response bytes via TextDecoder).
15
+ * - The MCP server implements this by wrapping bridge.sendCommand
16
+ * (bytes are already decoded at the bridge-client boundary).
17
+ */
18
+ export interface ToolClient {
19
+ run(options: {
20
+ code: string;
21
+ }): Promise<{
22
+ text: string;
23
+ errors: string;
24
+ exitCode: number;
25
+ }>;
26
+ request(options: {
27
+ url: string;
28
+ method: string;
29
+ headers?: Record<string, string>;
30
+ body?: string;
31
+ }): Promise<{
32
+ text: string;
33
+ httpStatusCode: number;
34
+ headers: Record<string, string[]>;
35
+ }>;
36
+ goTo(path: string): Promise<void>;
37
+ getCurrentURL(): Promise<string>;
38
+ readFileAsText(path: string): Promise<string>;
39
+ writeFile(path: string, contents: string): Promise<void>;
40
+ listFiles(path: string): Promise<string[]>;
41
+ mkdirTree(path: string): Promise<void>;
42
+ unlink(path: string): Promise<void>;
43
+ rmdir(path: string, options: {
44
+ recursive: boolean;
45
+ }): Promise<void>;
46
+ fileExists(path: string): Promise<boolean>;
47
+ }
48
+ export interface SiteInfo {
49
+ url: string;
50
+ documentRoot: string;
51
+ siteUrl: string;
52
+ wpVersion: string;
53
+ phpVersion: string;
54
+ }
55
+ export declare const toolExecutors: Record<string, (client: ToolClient, input: Record<string, unknown>) => Promise<unknown>>;
56
+ /**
57
+ * Wrap a PlaygroundClient as a ToolClient.
58
+ *
59
+ * Most methods pass through directly. Only `run` and `request`
60
+ * are intercepted to decode PHP/HTTP response bytes into plain
61
+ * strings via TextDecoder.
62
+ */
63
+ export declare function createToolClient(client: PlaygroundClient): ToolClient;
package/.eslintrc.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "extends": ["../../../.eslintrc.json"],
3
- "ignorePatterns": ["!**/*"],
4
- "overrides": [
5
- {
6
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
- "rules": {
8
- "no-console": 0
9
- }
10
- },
11
- {
12
- "files": ["*.ts", "*.tsx"],
13
- "rules": {}
14
- },
15
- {
16
- "files": ["*.js", "*.jsx"],
17
- "rules": {}
18
- },
19
- {
20
- "files": ["*.spec.ts"],
21
- "rules": {}
22
- }
23
- ]
24
- }