davidberenstein1957 commited on
Commit
ceb347e
·
verified ·
1 Parent(s): 6be72f7

Uploaded index.ts from GitHub repository

Browse files
Files changed (1) hide show
  1. index.ts +408 -0
index.ts ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListResourcesRequestSchema,
8
+ ListToolsRequestSchema,
9
+ ReadResourceRequestSchema,
10
+ CallToolResult,
11
+ TextContent,
12
+ ImageContent,
13
+ Tool,
14
+ } from "@modelcontextprotocol/sdk/types.js";
15
+ import puppeteer, { Browser, Page } from "puppeteer";
16
+
17
+ // Define the tools once to avoid repetition
18
+ const TOOLS: Tool[] = [
19
+ {
20
+ name: "puppeteer_navigate",
21
+ description: "Navigate to a URL",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ url: { type: "string" },
26
+ },
27
+ required: ["url"],
28
+ },
29
+ },
30
+ {
31
+ name: "puppeteer_screenshot",
32
+ description: "Take a screenshot of the current page or a specific element",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ name: { type: "string", description: "Name for the screenshot" },
37
+ selector: { type: "string", description: "CSS selector for element to screenshot" },
38
+ width: { type: "number", description: "Width in pixels (default: 800)" },
39
+ height: { type: "number", description: "Height in pixels (default: 600)" },
40
+ },
41
+ required: ["name"],
42
+ },
43
+ },
44
+ {
45
+ name: "puppeteer_click",
46
+ description: "Click an element on the page",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ selector: { type: "string", description: "CSS selector for element to click" },
51
+ },
52
+ required: ["selector"],
53
+ },
54
+ },
55
+ {
56
+ name: "puppeteer_fill",
57
+ description: "Fill out an input field",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ selector: { type: "string", description: "CSS selector for input field" },
62
+ value: { type: "string", description: "Value to fill" },
63
+ },
64
+ required: ["selector", "value"],
65
+ },
66
+ },
67
+ {
68
+ name: "puppeteer_select",
69
+ description: "Select an element on the page with Select tag",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ selector: { type: "string", description: "CSS selector for element to select" },
74
+ value: { type: "string", description: "Value to select" },
75
+ },
76
+ required: ["selector", "value"],
77
+ },
78
+ },
79
+ {
80
+ name: "puppeteer_hover",
81
+ description: "Hover an element on the page",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ selector: { type: "string", description: "CSS selector for element to hover" },
86
+ },
87
+ required: ["selector"],
88
+ },
89
+ },
90
+ {
91
+ name: "puppeteer_evaluate",
92
+ description: "Execute JavaScript in the browser console",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ script: { type: "string", description: "JavaScript code to execute" },
97
+ },
98
+ required: ["script"],
99
+ },
100
+ },
101
+ ];
102
+
103
+ // Global state
104
+ let browser: Browser | undefined;
105
+ let page: Page | undefined;
106
+ const consoleLogs: string[] = [];
107
+ const screenshots = new Map<string, string>();
108
+
109
+ async function ensureBrowser() {
110
+ if (!browser) {
111
+ const npx_args = { headless: false }
112
+ const docker_args = { headless: true, args: ["--no-sandbox", "--single-process", "--no-zygote"] }
113
+ browser = await puppeteer.launch(process.env.DOCKER_CONTAINER ? docker_args : npx_args);
114
+ const pages = await browser.pages();
115
+ page = pages[0];
116
+
117
+ page.on("console", (msg) => {
118
+ const logEntry = `[${msg.type()}] ${msg.text()}`;
119
+ consoleLogs.push(logEntry);
120
+ server.notification({
121
+ method: "notifications/resources/updated",
122
+ params: { uri: "console://logs" },
123
+ });
124
+ });
125
+ }
126
+ return page!;
127
+ }
128
+
129
+ declare global {
130
+ interface Window {
131
+ mcpHelper: {
132
+ logs: string[],
133
+ originalConsole: Partial<typeof console>,
134
+ }
135
+ }
136
+ }
137
+
138
+ async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
139
+ const page = await ensureBrowser();
140
+
141
+ switch (name) {
142
+ case "puppeteer_navigate":
143
+ await page.goto(args.url);
144
+ return {
145
+ content: [{
146
+ type: "text",
147
+ text: `Navigated to ${args.url}`,
148
+ }],
149
+ isError: false,
150
+ };
151
+
152
+ case "puppeteer_screenshot": {
153
+ const width = args.width ?? 800;
154
+ const height = args.height ?? 600;
155
+ await page.setViewport({ width, height });
156
+
157
+ const screenshot = await (args.selector ?
158
+ (await page.$(args.selector))?.screenshot({ encoding: "base64" }) :
159
+ page.screenshot({ encoding: "base64", fullPage: false }));
160
+
161
+ if (!screenshot) {
162
+ return {
163
+ content: [{
164
+ type: "text",
165
+ text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed",
166
+ }],
167
+ isError: true,
168
+ };
169
+ }
170
+
171
+ screenshots.set(args.name, screenshot as string);
172
+ server.notification({
173
+ method: "notifications/resources/list_changed",
174
+ });
175
+
176
+ return {
177
+ content: [
178
+ {
179
+ type: "text",
180
+ text: `Screenshot '${args.name}' taken at ${width}x${height}`,
181
+ } as TextContent,
182
+ {
183
+ type: "image",
184
+ data: screenshot,
185
+ mimeType: "image/png",
186
+ } as ImageContent,
187
+ ],
188
+ isError: false,
189
+ };
190
+ }
191
+
192
+ case "puppeteer_click":
193
+ try {
194
+ await page.click(args.selector);
195
+ return {
196
+ content: [{
197
+ type: "text",
198
+ text: `Clicked: ${args.selector}`,
199
+ }],
200
+ isError: false,
201
+ };
202
+ } catch (error) {
203
+ return {
204
+ content: [{
205
+ type: "text",
206
+ text: `Failed to click ${args.selector}: ${(error as Error).message}`,
207
+ }],
208
+ isError: true,
209
+ };
210
+ }
211
+
212
+ case "puppeteer_fill":
213
+ try {
214
+ await page.waitForSelector(args.selector);
215
+ await page.type(args.selector, args.value);
216
+ return {
217
+ content: [{
218
+ type: "text",
219
+ text: `Filled ${args.selector} with: ${args.value}`,
220
+ }],
221
+ isError: false,
222
+ };
223
+ } catch (error) {
224
+ return {
225
+ content: [{
226
+ type: "text",
227
+ text: `Failed to fill ${args.selector}: ${(error as Error).message}`,
228
+ }],
229
+ isError: true,
230
+ };
231
+ }
232
+
233
+ case "puppeteer_select":
234
+ try {
235
+ await page.waitForSelector(args.selector);
236
+ await page.select(args.selector, args.value);
237
+ return {
238
+ content: [{
239
+ type: "text",
240
+ text: `Selected ${args.selector} with: ${args.value}`,
241
+ }],
242
+ isError: false,
243
+ };
244
+ } catch (error) {
245
+ return {
246
+ content: [{
247
+ type: "text",
248
+ text: `Failed to select ${args.selector}: ${(error as Error).message}`,
249
+ }],
250
+ isError: true,
251
+ };
252
+ }
253
+
254
+ case "puppeteer_hover":
255
+ try {
256
+ await page.waitForSelector(args.selector);
257
+ await page.hover(args.selector);
258
+ return {
259
+ content: [{
260
+ type: "text",
261
+ text: `Hovered ${args.selector}`,
262
+ }],
263
+ isError: false,
264
+ };
265
+ } catch (error) {
266
+ return {
267
+ content: [{
268
+ type: "text",
269
+ text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
270
+ }],
271
+ isError: true,
272
+ };
273
+ }
274
+
275
+ case "puppeteer_evaluate":
276
+ try {
277
+ await page.evaluate(() => {
278
+ window.mcpHelper = {
279
+ logs: [],
280
+ originalConsole: { ...console },
281
+ };
282
+
283
+ ['log', 'info', 'warn', 'error'].forEach(method => {
284
+ (console as any)[method] = (...args: any[]) => {
285
+ window.mcpHelper.logs.push(`[${method}] ${args.join(' ')}`);
286
+ (window.mcpHelper.originalConsole as any)[method](...args);
287
+ };
288
+ } );
289
+ } );
290
+
291
+ const result = await page.evaluate( args.script );
292
+
293
+ const logs = await page.evaluate(() => {
294
+ Object.assign(console, window.mcpHelper.originalConsole);
295
+ const logs = window.mcpHelper.logs;
296
+ delete ( window as any).mcpHelper;
297
+ return logs;
298
+ });
299
+
300
+ return {
301
+ content: [
302
+ {
303
+ type: "text",
304
+ text: `Execution result:\n${JSON.stringify(result, null, 2)}\n\nConsole output:\n${logs.join('\n')}`,
305
+ },
306
+ ],
307
+ isError: false,
308
+ };
309
+ } catch (error) {
310
+ return {
311
+ content: [{
312
+ type: "text",
313
+ text: `Script execution failed: ${(error as Error).message}`,
314
+ }],
315
+ isError: true,
316
+ };
317
+ }
318
+
319
+ default:
320
+ return {
321
+ content: [{
322
+ type: "text",
323
+ text: `Unknown tool: ${name}`,
324
+ }],
325
+ isError: true,
326
+ };
327
+ }
328
+ }
329
+
330
+ const server = new Server(
331
+ {
332
+ name: "example-servers/puppeteer",
333
+ version: "0.1.0",
334
+ },
335
+ {
336
+ capabilities: {
337
+ resources: {},
338
+ tools: {},
339
+ },
340
+ },
341
+ );
342
+
343
+
344
+ // Setup request handlers
345
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
346
+ resources: [
347
+ {
348
+ uri: "console://logs",
349
+ mimeType: "text/plain",
350
+ name: "Browser console logs",
351
+ },
352
+ ...Array.from(screenshots.keys()).map(name => ({
353
+ uri: `screenshot://${name}`,
354
+ mimeType: "image/png",
355
+ name: `Screenshot: ${name}`,
356
+ })),
357
+ ],
358
+ }));
359
+
360
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
361
+ const uri = request.params.uri.toString();
362
+
363
+ if (uri === "console://logs") {
364
+ return {
365
+ contents: [{
366
+ uri,
367
+ mimeType: "text/plain",
368
+ text: consoleLogs.join("\n"),
369
+ }],
370
+ };
371
+ }
372
+
373
+ if (uri.startsWith("screenshot://")) {
374
+ const name = uri.split("://")[1];
375
+ const screenshot = screenshots.get(name);
376
+ if (screenshot) {
377
+ return {
378
+ contents: [{
379
+ uri,
380
+ mimeType: "image/png",
381
+ blob: screenshot,
382
+ }],
383
+ };
384
+ }
385
+ }
386
+
387
+ throw new Error(`Resource not found: ${uri}`);
388
+ });
389
+
390
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
391
+ tools: TOOLS,
392
+ }));
393
+
394
+ server.setRequestHandler(CallToolRequestSchema, async (request) =>
395
+ handleToolCall(request.params.name, request.params.arguments ?? {})
396
+ );
397
+
398
+ async function runServer() {
399
+ const transport = new StdioServerTransport();
400
+ await server.connect(transport);
401
+ }
402
+
403
+ runServer().catch(console.error);
404
+
405
+ process.stdin.on("close", () => {
406
+ console.error("Puppeteer MCP Server closed");
407
+ server.close();
408
+ });