@wp-playground/mcp 3.1.5 → 3.1.8
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/LICENSE +339 -0
- package/bridge-client.d.ts +27 -0
- package/bridge-server.d.ts +42 -0
- package/client.cjs +2 -0
- package/client.cjs.map +1 -0
- package/client.js +104 -0
- package/client.js.map +1 -0
- package/index.cjs +183 -0
- package/index.cjs.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +12267 -0
- package/index.js.map +1 -0
- package/mcp-server.d.ts +2 -0
- package/package.json +79 -45
- package/tool-executors-ecXfLzk7.cjs +9 -0
- package/tool-executors-ecXfLzk7.cjs.map +1 -0
- package/tool-executors-pMxJ1GXT.js +100 -0
- package/tool-executors-pMxJ1GXT.js.map +1 -0
- package/tools/register-mcp-server-tools.d.ts +3 -0
- package/tools/tool-definitions.d.ts +42 -0
- package/tools/tool-executors.d.ts +63 -0
- package/.eslintrc.json +0 -24
- package/README.md +0 -96
- package/e2e/mcp-tools.spec.ts +0 -679
- package/playwright.config.ts +0 -35
- package/project.json +0 -64
- package/src/bridge-client.ts +0 -196
- package/src/bridge-server.spec.ts +0 -228
- package/src/bridge-server.ts +0 -485
- package/src/index.ts +0 -28
- package/src/mcp-server.ts +0 -34
- package/src/tools/register-mcp-server-tools.ts +0 -347
- package/src/tools/tool-definitions.ts +0 -527
- package/src/tools/tool-executors.ts +0 -205
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -10
- package/vite.config.ts +0 -49
- /package/{src/client.ts → client.d.ts} +0 -0
package/mcp-server.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,46 +1,80 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
2
|
+
"name": "@wp-playground/mcp",
|
|
3
|
+
"version": "3.1.8",
|
|
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.8",
|
|
65
|
+
"@php-wasm/universal": "3.1.8"
|
|
66
|
+
},
|
|
67
|
+
"gitHead": "ba6a9509c9db4e0b7af6f155cf31162a6b659b5f",
|
|
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,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(port: number): 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
|
-
}
|
package/README.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# @wp-playground/mcp
|
|
2
|
-
|
|
3
|
-
MCP server that connects AI providers to a WordPress Playground running in the browser.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
### 1. Configure your MCP client
|
|
8
|
-
|
|
9
|
-
Pick the configuration for your AI tool:
|
|
10
|
-
|
|
11
|
-
#### Claude Code / Claude Desktop
|
|
12
|
-
|
|
13
|
-
Add to your Claude Code `.mcp.json` or Claude Desktop `claude_desktop_config.json`:
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"mcpServers": {
|
|
18
|
-
"wordpress-playground": {
|
|
19
|
-
"type": "stdio",
|
|
20
|
-
"command": "npx",
|
|
21
|
-
"args": ["@wp-playground/mcp"]
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
#### Gemini CLI
|
|
28
|
-
|
|
29
|
-
Add to `~/.gemini/settings.json` (or `.gemini/settings.json` in your project):
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"mcpServers": {
|
|
34
|
-
"wordpress-playground": {
|
|
35
|
-
"command": "npx",
|
|
36
|
-
"args": ["@wp-playground/mcp"]
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 2. Open the Playground website
|
|
43
|
-
|
|
44
|
-
Navigate to https://playground.wordpress.net/?mcp=yes in your browser. The MCP bridge connects automatically.
|
|
45
|
-
|
|
46
|
-
## Development
|
|
47
|
-
|
|
48
|
-
When working on the MCP server or the Playground codebase, run from source instead:
|
|
49
|
-
|
|
50
|
-
### 1. Start the Playground dev server
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npm run dev
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 2. Configure your MCP client
|
|
57
|
-
|
|
58
|
-
> **Note:** Your default `node` must be Node 22+. If it isn't, replace `node` in the command below with the full path to Node 22+ (e.g. `/Users/ME/.nvm/versions/node/v22.22.0/bin/node`).
|
|
59
|
-
|
|
60
|
-
Add to your MCP client config (e.g. Claude Code `.mcp.json` or Claude Desktop `claude_desktop_config.json`):
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
{
|
|
64
|
-
"mcpServers": {
|
|
65
|
-
"wordpress-playground": {
|
|
66
|
-
"type": "stdio",
|
|
67
|
-
"command": "node",
|
|
68
|
-
"args": ["--experimental-strip-types", "--experimental-transform-types", "--import", "ABS_PATH_TO_PLAYGROUND/packages/meta/src/node-es-module-loader/register.mts", "ABS_PATH_TO_PLAYGROUND/packages/playground/mcp/src/index.ts"]
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Replace `ABS_PATH_TO_PLAYGROUND` with the absolute path to your local checkout of this repository.
|
|
75
|
-
|
|
76
|
-
### 3. Open the Playground website
|
|
77
|
-
|
|
78
|
-
Navigate to http://127.0.0.1:5400/website-server/?mcp=yes in your browser. The MCP bridge connects automatically.
|
|
79
|
-
|
|
80
|
-
## How it works
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
AI Client (stdio) → MCP Server (Node.js) → WebSocket (port 7999) → Browser (Playground website)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The MCP server communicates with AI clients via stdio and with the browser via WebSocket. A bridge client (`bridge-client.ts`) integrated into the Playground website via Redux middleware auto-connects to the WebSocket server and proxies commands to the PlaygroundClient API.
|
|
87
|
-
|
|
88
|
-
## Available tools
|
|
89
|
-
|
|
90
|
-
**Site management**: `playground_list_sites`, `playground_open_site`, `playground_rename_site`, `playground_save_site`
|
|
91
|
-
|
|
92
|
-
**Code execution**: `playground_execute_php`, `playground_request`
|
|
93
|
-
|
|
94
|
-
**Navigation & info**: `playground_navigate`, `playground_get_current_url`, `playground_get_site_info`
|
|
95
|
-
|
|
96
|
-
**Filesystem**: `playground_read_file`, `playground_write_file`, `playground_list_files`, `playground_mkdir`, `playground_delete_file`, `playground_delete_directory`, `playground_file_exists`
|