@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.
@@ -1,347 +0,0 @@
1
- // The MCP SDK expects Zod v3 schemas. We install zod@4 which
2
- // re-exports a v3-compatible API via the "zod/v3" subpath.
3
- // Once @modelcontextprotocol implements support for JSON schemas, we can remove the zod dependency.
4
- import { z } from 'zod/v3';
5
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
- import type { PlaygroundBridge } from '../bridge-server';
7
- import {
8
- toolDefinitions,
9
- getSiteToolDefinitions,
10
- playgroundUrl,
11
- presentStorage,
12
- stringifyError,
13
- } from './tool-definitions';
14
- import type { ToolParam } from './tool-definitions';
15
- import { toolExecutors } from './tool-executors';
16
- import type { ToolClient } from './tool-executors';
17
- function errorResult(prefix: string, error: unknown) {
18
- return {
19
- content: [
20
- {
21
- type: 'text' as const,
22
- text: `${prefix}: ${stringifyError(error)}`,
23
- },
24
- ],
25
- isError: true,
26
- };
27
- }
28
-
29
- const siteIdSchema = z
30
- .string()
31
- .describe(
32
- 'Target site ID. Call playground_list_sites first to discover ' +
33
- 'available site IDs.'
34
- );
35
-
36
- /**
37
- * Convert shared ToolParam[] to a Zod schema object suitable
38
- * for McpServer.registerTool(). Always includes siteId as the
39
- * first parameter.
40
- */
41
- function paramsToZodSchema(params: ToolParam[]): Record<string, z.ZodType> {
42
- const schema: Record<string, z.ZodType> = {
43
- siteId: siteIdSchema,
44
- };
45
-
46
- for (const param of params) {
47
- let zodType: z.ZodType;
48
- switch (param.type) {
49
- case 'string':
50
- zodType = z.string();
51
- break;
52
- case 'boolean':
53
- zodType = z.boolean();
54
- break;
55
- case 'object':
56
- zodType = z.record(z.string(), z.string());
57
- break;
58
- default:
59
- throw new Error(
60
- `Unknown param type "${param.type}" for "${param.name}"`
61
- );
62
- }
63
-
64
- if (!param.required) {
65
- zodType = zodType.optional();
66
- if (param.default !== undefined) {
67
- zodType = (zodType as z.ZodOptional<z.ZodType>).default(
68
- param.default
69
- );
70
- }
71
- }
72
-
73
- zodType = zodType.describe(param.description);
74
- schema[param.name] = zodType;
75
- }
76
-
77
- return schema;
78
- }
79
-
80
- export function registerMcpServerTools(
81
- server: McpServer,
82
- bridge: PlaygroundBridge,
83
- port: number
84
- ) {
85
- const sendCommand = bridge.sendCommand.bind(bridge);
86
- const siteToolDefinitions = getSiteToolDefinitions(port);
87
- const url = playgroundUrl(port);
88
-
89
- // -- Site management tools --
90
- // These operate on the bridge itself, not on a PlaygroundClient.
91
- // Definitions are shared with WebMCP via getSiteToolDefinitions.
92
-
93
- const listSites = siteToolDefinitions['playground_list_sites'];
94
- server.registerTool(
95
- 'playground_list_sites',
96
- {
97
- title: listSites.title,
98
- description: listSites.description,
99
- inputSchema: z.object({}),
100
- annotations: listSites.annotations,
101
- },
102
- async () => {
103
- const tabCount = bridge.getTabCount();
104
- const sites = bridge.listSites();
105
- if (sites.length === 0) {
106
- return {
107
- content: [
108
- {
109
- type: 'text' as const,
110
- text: JSON.stringify({
111
- connectedTabs: tabCount,
112
- sites: [],
113
- message: bridge.isConnected()
114
- ? 'No sites are loaded.'
115
- : `No browser connected. Open the Playground website at ${url} to connect.`,
116
- }),
117
- },
118
- ],
119
- };
120
- }
121
- return {
122
- content: [
123
- {
124
- type: 'text' as const,
125
- text: JSON.stringify({
126
- connectedTabs: tabCount,
127
- sites: sites.map((s) => ({
128
- siteId: s.siteId,
129
- name: s.name,
130
- storage: s.storage,
131
- isActive: s.isActive,
132
- url: url,
133
- })),
134
- }),
135
- },
136
- ],
137
- };
138
- }
139
- );
140
-
141
- const openSite = siteToolDefinitions['playground_open_site'];
142
- server.registerTool(
143
- 'playground_open_site',
144
- {
145
- title: openSite.title,
146
- description: openSite.description,
147
- inputSchema: {
148
- siteId: siteIdSchema,
149
- },
150
- annotations: openSite.annotations,
151
- },
152
- async ({ siteId }) => {
153
- try {
154
- await bridge.sendCommand(siteId, '__open_site');
155
- const site = await bridge.waitForSiteActive(siteId, 30000);
156
- return {
157
- content: [
158
- {
159
- type: 'text' as const,
160
- text: JSON.stringify({
161
- siteId,
162
- name: site.siteName,
163
- isActive: true,
164
- }),
165
- },
166
- ],
167
- };
168
- } catch (error) {
169
- return errorResult(openSite.errorPrefix, error);
170
- }
171
- }
172
- );
173
-
174
- const renameSite = siteToolDefinitions['playground_rename_site'];
175
- server.registerTool(
176
- 'playground_rename_site',
177
- {
178
- title: renameSite.title,
179
- description: renameSite.description,
180
- inputSchema: paramsToZodSchema(renameSite.params),
181
- annotations: renameSite.annotations,
182
- },
183
- async ({ siteId, newName }) => {
184
- try {
185
- await bridge.sendCommand(siteId, '__rename_site', [newName]);
186
- return {
187
- content: [
188
- {
189
- type: 'text' as const,
190
- text: JSON.stringify({
191
- success: true,
192
- siteId,
193
- newName,
194
- }),
195
- },
196
- ],
197
- };
198
- } catch (error) {
199
- return errorResult(renameSite.errorPrefix, error);
200
- }
201
- }
202
- );
203
-
204
- const saveSite = siteToolDefinitions['playground_save_site'];
205
- server.registerTool(
206
- 'playground_save_site',
207
- {
208
- title: saveSite.title,
209
- description: saveSite.description,
210
- inputSchema: {
211
- siteId: siteIdSchema,
212
- },
213
- annotations: saveSite.annotations,
214
- },
215
- async ({ siteId }) => {
216
- try {
217
- const sites = bridge.listSites();
218
- const site = sites.find((s) => s.siteId === siteId);
219
- if (!site) {
220
- return errorResult(
221
- 'Error saving site',
222
- new Error(`Unknown site: ${siteId}`)
223
- );
224
- }
225
- if (site.storage !== 'temporary') {
226
- return {
227
- content: [
228
- {
229
- type: 'text' as const,
230
- text: JSON.stringify({
231
- success: true,
232
- alreadySaved: true,
233
- siteId,
234
- name: site.name,
235
- storage: site.storage,
236
- }),
237
- },
238
- ],
239
- };
240
- }
241
- const result = (await bridge.sendCommand(
242
- siteId,
243
- '__save_site'
244
- )) as { slug: string; storage: string };
245
- return {
246
- content: [
247
- {
248
- type: 'text' as const,
249
- text: JSON.stringify({
250
- success: true,
251
- alreadySaved: false,
252
- siteId,
253
- name: site.name,
254
- storage: presentStorage(result.storage),
255
- }),
256
- },
257
- ],
258
- };
259
- } catch (error) {
260
- return errorResult(saveSite.errorPrefix, error);
261
- }
262
- }
263
- );
264
-
265
- // -- Per-site tools (shared executors) --
266
-
267
- for (const [name, def] of Object.entries(toolDefinitions)) {
268
- const executor = toolExecutors[name];
269
- if (!executor) {
270
- continue;
271
- }
272
- server.registerTool(
273
- name,
274
- {
275
- title: def.title,
276
- description: def.description,
277
- inputSchema: paramsToZodSchema(def.params),
278
- annotations: def.annotations,
279
- },
280
- async (args: Record<string, unknown>) => {
281
- const { siteId, ...input } = args;
282
- try {
283
- const id = siteId as string;
284
- const client: ToolClient = {
285
- run: (...args) =>
286
- sendCommand(id, 'run', args) as ReturnType<
287
- ToolClient['run']
288
- >,
289
- request: (...args) =>
290
- sendCommand(id, 'request', args) as ReturnType<
291
- ToolClient['request']
292
- >,
293
- goTo: (...args) =>
294
- sendCommand(id, 'goTo', args) as ReturnType<
295
- ToolClient['goTo']
296
- >,
297
- getCurrentURL: () =>
298
- sendCommand(id, 'getCurrentURL', []) as ReturnType<
299
- ToolClient['getCurrentURL']
300
- >,
301
- readFileAsText: (...args) =>
302
- sendCommand(
303
- id,
304
- 'readFileAsText',
305
- args
306
- ) as ReturnType<ToolClient['readFileAsText']>,
307
- writeFile: (...args) =>
308
- sendCommand(id, 'writeFile', args) as ReturnType<
309
- ToolClient['writeFile']
310
- >,
311
- listFiles: (...args) =>
312
- sendCommand(id, 'listFiles', args) as ReturnType<
313
- ToolClient['listFiles']
314
- >,
315
- mkdirTree: (...args) =>
316
- sendCommand(id, 'mkdirTree', args) as ReturnType<
317
- ToolClient['mkdirTree']
318
- >,
319
- unlink: (...args) =>
320
- sendCommand(id, 'unlink', args) as ReturnType<
321
- ToolClient['unlink']
322
- >,
323
- rmdir: (...args) =>
324
- sendCommand(id, 'rmdir', args) as ReturnType<
325
- ToolClient['rmdir']
326
- >,
327
- fileExists: (...args) =>
328
- sendCommand(id, 'fileExists', args) as ReturnType<
329
- ToolClient['fileExists']
330
- >,
331
- };
332
- const result = await executor(client, input);
333
- return {
334
- content: [
335
- {
336
- type: 'text' as const,
337
- text: JSON.stringify(result),
338
- },
339
- ],
340
- };
341
- } catch (error) {
342
- return errorResult(def.errorPrefix, error);
343
- }
344
- }
345
- );
346
- }
347
- }