mcp-accessibility-scanner 1.0.9 → 1.0.11
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 +111 -15
- package/build/accessibilityChecker.js +339 -107
- package/build/index.js +896 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -17,6 +17,62 @@ const server = new mcp_js_1.McpServer({
|
|
|
17
17
|
name: "AccessibilityTools",
|
|
18
18
|
version: "1.0.0",
|
|
19
19
|
});
|
|
20
|
+
class SessionManager {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.sessions = new Map();
|
|
23
|
+
this.sessionTimeouts = new Map();
|
|
24
|
+
this.SESSION_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
|
25
|
+
}
|
|
26
|
+
createSession(sessionId_1, viewport_1) {
|
|
27
|
+
return __awaiter(this, arguments, void 0, function* (sessionId, viewport, shouldRunInHeadless = true) {
|
|
28
|
+
if (this.sessions.has(sessionId)) {
|
|
29
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
30
|
+
}
|
|
31
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
32
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
33
|
+
yield scanner.createContext(viewport);
|
|
34
|
+
yield scanner.createPage();
|
|
35
|
+
this.sessions.set(sessionId, scanner);
|
|
36
|
+
this.resetSessionTimeout(sessionId);
|
|
37
|
+
return scanner;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
getSession(sessionId) {
|
|
41
|
+
const session = this.sessions.get(sessionId);
|
|
42
|
+
if (session) {
|
|
43
|
+
this.resetSessionTimeout(sessionId);
|
|
44
|
+
}
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
closeSession(sessionId) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const session = this.sessions.get(sessionId);
|
|
50
|
+
if (session) {
|
|
51
|
+
yield session.cleanup();
|
|
52
|
+
this.sessions.delete(sessionId);
|
|
53
|
+
const timeout = this.sessionTimeouts.get(sessionId);
|
|
54
|
+
if (timeout) {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
this.sessionTimeouts.delete(sessionId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
listSessions() {
|
|
62
|
+
return Array.from(this.sessions.keys());
|
|
63
|
+
}
|
|
64
|
+
resetSessionTimeout(sessionId) {
|
|
65
|
+
const existingTimeout = this.sessionTimeouts.get(sessionId);
|
|
66
|
+
if (existingTimeout) {
|
|
67
|
+
clearTimeout(existingTimeout);
|
|
68
|
+
}
|
|
69
|
+
const timeout = setTimeout(() => {
|
|
70
|
+
this.closeSession(sessionId);
|
|
71
|
+
}, this.SESSION_TIMEOUT);
|
|
72
|
+
this.sessionTimeouts.set(sessionId, timeout);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const sessionManager = new SessionManager();
|
|
20
76
|
const tagValues = [
|
|
21
77
|
"wcag2a", "wcag2aa", "wcag2aaa", "wcag21a", "wcag21aa", "wcag21aaa",
|
|
22
78
|
"wcag22a", "wcag22aa", "wcag22aaa", "section508", "cat.aria", "cat.color",
|
|
@@ -31,7 +87,7 @@ server.registerTool("accessibility-scan", {
|
|
|
31
87
|
url: zod_1.z.string().url().describe("The public URL to scan for accessibility violations"),
|
|
32
88
|
violationsTag: zod_1.z
|
|
33
89
|
.array(zod_1.z.enum(tagValues))
|
|
34
|
-
.min(1)
|
|
90
|
+
.min(1)
|
|
35
91
|
.describe("An array of tags for violation types to check"),
|
|
36
92
|
viewport: zod_1.z
|
|
37
93
|
.object({
|
|
@@ -60,4 +116,843 @@ server.registerTool("accessibility-scan", {
|
|
|
60
116
|
isError: false,
|
|
61
117
|
};
|
|
62
118
|
}));
|
|
119
|
+
server.registerTool("click-element", {
|
|
120
|
+
title: "Click Element",
|
|
121
|
+
description: "Clicks on an element specified by a CSS selector on the current page.",
|
|
122
|
+
inputSchema: zod_1.z.object({
|
|
123
|
+
url: zod_1.z.string().url().describe("The public URL to navigate to"),
|
|
124
|
+
selector: zod_1.z.string().describe("CSS selector for the element to click"),
|
|
125
|
+
viewport: zod_1.z
|
|
126
|
+
.object({
|
|
127
|
+
width: zod_1.z.number().default(1920),
|
|
128
|
+
height: zod_1.z.number().default(1080),
|
|
129
|
+
})
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("Optional viewport dimensions"),
|
|
132
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
133
|
+
}).shape,
|
|
134
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
const { url, selector, viewport, shouldRunInHeadless } = args;
|
|
136
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
137
|
+
try {
|
|
138
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
139
|
+
yield scanner.createContext(viewport);
|
|
140
|
+
yield scanner.createPage();
|
|
141
|
+
yield scanner.navigateToUrl(url);
|
|
142
|
+
yield scanner.clickElement(selector);
|
|
143
|
+
const base64Screenshot = yield scanner.takeScreenshot();
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: JSON.stringify({
|
|
149
|
+
message: `Successfully clicked element: ${selector}`,
|
|
150
|
+
url,
|
|
151
|
+
selector,
|
|
152
|
+
}, null, 2),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "image",
|
|
156
|
+
data: base64Screenshot,
|
|
157
|
+
mimeType: "image/png",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
isError: false,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: JSON.stringify({
|
|
169
|
+
message: `Failed to click element: ${selector}`,
|
|
170
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
171
|
+
url,
|
|
172
|
+
selector,
|
|
173
|
+
}, null, 2),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
isError: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
yield scanner.cleanup();
|
|
181
|
+
}
|
|
182
|
+
}));
|
|
183
|
+
server.registerTool("type-text", {
|
|
184
|
+
title: "Type Text",
|
|
185
|
+
description: "Types text into an input field specified by a CSS selector.",
|
|
186
|
+
inputSchema: zod_1.z.object({
|
|
187
|
+
url: zod_1.z.string().url().describe("The public URL to navigate to"),
|
|
188
|
+
selector: zod_1.z.string().describe("CSS selector for the input field"),
|
|
189
|
+
text: zod_1.z.string().describe("Text to type into the input field"),
|
|
190
|
+
viewport: zod_1.z
|
|
191
|
+
.object({
|
|
192
|
+
width: zod_1.z.number().default(1920),
|
|
193
|
+
height: zod_1.z.number().default(1080),
|
|
194
|
+
})
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("Optional viewport dimensions"),
|
|
197
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
198
|
+
}).shape,
|
|
199
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
|
+
const { url, selector, text, viewport, shouldRunInHeadless } = args;
|
|
201
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
202
|
+
try {
|
|
203
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
204
|
+
yield scanner.createContext(viewport);
|
|
205
|
+
yield scanner.createPage();
|
|
206
|
+
yield scanner.navigateToUrl(url);
|
|
207
|
+
yield scanner.typeText(selector, text);
|
|
208
|
+
const base64Screenshot = yield scanner.takeScreenshot();
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify({
|
|
214
|
+
message: `Successfully typed text into element: ${selector}`,
|
|
215
|
+
url,
|
|
216
|
+
selector,
|
|
217
|
+
text,
|
|
218
|
+
}, null, 2),
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: "image",
|
|
222
|
+
data: base64Screenshot,
|
|
223
|
+
mimeType: "image/png",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
isError: false,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: JSON.stringify({
|
|
235
|
+
message: `Failed to type text into element: ${selector}`,
|
|
236
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
237
|
+
url,
|
|
238
|
+
selector,
|
|
239
|
+
text,
|
|
240
|
+
}, null, 2),
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
isError: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
yield scanner.cleanup();
|
|
248
|
+
}
|
|
249
|
+
}));
|
|
250
|
+
server.registerTool("create-session", {
|
|
251
|
+
title: "Create Browser Session",
|
|
252
|
+
description: "Creates a new persistent browser session that can be used for multiple operations.",
|
|
253
|
+
inputSchema: zod_1.z.object({
|
|
254
|
+
sessionId: zod_1.z.string().describe("Unique identifier for the session"),
|
|
255
|
+
viewport: zod_1.z
|
|
256
|
+
.object({
|
|
257
|
+
width: zod_1.z.number().default(1920),
|
|
258
|
+
height: zod_1.z.number().default(1080),
|
|
259
|
+
})
|
|
260
|
+
.optional()
|
|
261
|
+
.describe("Optional viewport dimensions"),
|
|
262
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
263
|
+
}).shape,
|
|
264
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
265
|
+
const { sessionId, viewport, shouldRunInHeadless } = args;
|
|
266
|
+
try {
|
|
267
|
+
yield sessionManager.createSession(sessionId, viewport, shouldRunInHeadless);
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: JSON.stringify({
|
|
273
|
+
message: `Session ${sessionId} created successfully`,
|
|
274
|
+
sessionId,
|
|
275
|
+
viewport: viewport || { width: 1920, height: 1080 },
|
|
276
|
+
headless: shouldRunInHeadless,
|
|
277
|
+
}, null, 2),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
isError: false,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: JSON.stringify({
|
|
289
|
+
message: `Failed to create session: ${sessionId}`,
|
|
290
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
291
|
+
sessionId,
|
|
292
|
+
}, null, 2),
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
isError: true,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
server.registerTool("navigate-session", {
|
|
300
|
+
title: "Navigate Session",
|
|
301
|
+
description: "Navigates to a URL in an existing browser session.",
|
|
302
|
+
inputSchema: zod_1.z.object({
|
|
303
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
304
|
+
url: zod_1.z.string().url().describe("The URL to navigate to"),
|
|
305
|
+
}).shape,
|
|
306
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
307
|
+
const { sessionId, url } = args;
|
|
308
|
+
try {
|
|
309
|
+
const session = sessionManager.getSession(sessionId);
|
|
310
|
+
if (!session) {
|
|
311
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
312
|
+
}
|
|
313
|
+
yield session.navigateToUrl(url);
|
|
314
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
315
|
+
return {
|
|
316
|
+
content: [
|
|
317
|
+
{
|
|
318
|
+
type: "text",
|
|
319
|
+
text: JSON.stringify({
|
|
320
|
+
message: `Successfully navigated to ${url}`,
|
|
321
|
+
sessionId,
|
|
322
|
+
url,
|
|
323
|
+
}, null, 2),
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
type: "image",
|
|
327
|
+
data: base64Screenshot,
|
|
328
|
+
mimeType: "image/png",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
isError: false,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: JSON.stringify({
|
|
340
|
+
message: `Failed to navigate in session: ${sessionId}`,
|
|
341
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
342
|
+
sessionId,
|
|
343
|
+
url,
|
|
344
|
+
}, null, 2),
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
isError: true,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
server.registerTool("click-session", {
|
|
352
|
+
title: "Click Element in Session",
|
|
353
|
+
description: "Clicks on an element in an existing browser session.",
|
|
354
|
+
inputSchema: zod_1.z.object({
|
|
355
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
356
|
+
selector: zod_1.z.string().describe("CSS selector for the element to click"),
|
|
357
|
+
}).shape,
|
|
358
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
359
|
+
const { sessionId, selector } = args;
|
|
360
|
+
try {
|
|
361
|
+
const session = sessionManager.getSession(sessionId);
|
|
362
|
+
if (!session) {
|
|
363
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
364
|
+
}
|
|
365
|
+
yield session.clickElement(selector);
|
|
366
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
367
|
+
return {
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: JSON.stringify({
|
|
372
|
+
message: `Successfully clicked element: ${selector}`,
|
|
373
|
+
sessionId,
|
|
374
|
+
selector,
|
|
375
|
+
}, null, 2),
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: "image",
|
|
379
|
+
data: base64Screenshot,
|
|
380
|
+
mimeType: "image/png",
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
isError: false,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
return {
|
|
388
|
+
content: [
|
|
389
|
+
{
|
|
390
|
+
type: "text",
|
|
391
|
+
text: JSON.stringify({
|
|
392
|
+
message: `Failed to click element in session: ${sessionId}`,
|
|
393
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
394
|
+
sessionId,
|
|
395
|
+
selector,
|
|
396
|
+
}, null, 2),
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
isError: true,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}));
|
|
403
|
+
server.registerTool("type-session", {
|
|
404
|
+
title: "Type Text in Session",
|
|
405
|
+
description: "Types text into an input field in an existing browser session.",
|
|
406
|
+
inputSchema: zod_1.z.object({
|
|
407
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
408
|
+
selector: zod_1.z.string().describe("CSS selector for the input field"),
|
|
409
|
+
text: zod_1.z.string().describe("Text to type into the input field"),
|
|
410
|
+
}).shape,
|
|
411
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
412
|
+
const { sessionId, selector, text } = args;
|
|
413
|
+
try {
|
|
414
|
+
const session = sessionManager.getSession(sessionId);
|
|
415
|
+
if (!session) {
|
|
416
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
417
|
+
}
|
|
418
|
+
yield session.typeText(selector, text);
|
|
419
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
420
|
+
return {
|
|
421
|
+
content: [
|
|
422
|
+
{
|
|
423
|
+
type: "text",
|
|
424
|
+
text: JSON.stringify({
|
|
425
|
+
message: `Successfully typed text into element: ${selector}`,
|
|
426
|
+
sessionId,
|
|
427
|
+
selector,
|
|
428
|
+
text,
|
|
429
|
+
}, null, 2),
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
type: "image",
|
|
433
|
+
data: base64Screenshot,
|
|
434
|
+
mimeType: "image/png",
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
isError: false,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: "text",
|
|
445
|
+
text: JSON.stringify({
|
|
446
|
+
message: `Failed to type text in session: ${sessionId}`,
|
|
447
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
448
|
+
sessionId,
|
|
449
|
+
selector,
|
|
450
|
+
text,
|
|
451
|
+
}, null, 2),
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
isError: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}));
|
|
458
|
+
server.registerTool("scan-session", {
|
|
459
|
+
title: "Scan Session for Accessibility",
|
|
460
|
+
description: "Runs an accessibility scan on the current page in an existing browser session.",
|
|
461
|
+
inputSchema: zod_1.z.object({
|
|
462
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
463
|
+
violationsTag: zod_1.z
|
|
464
|
+
.array(zod_1.z.enum(tagValues))
|
|
465
|
+
.min(1)
|
|
466
|
+
.describe("An array of tags for violation types to check"),
|
|
467
|
+
}).shape,
|
|
468
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
469
|
+
const { sessionId, violationsTag } = args;
|
|
470
|
+
try {
|
|
471
|
+
const session = sessionManager.getSession(sessionId);
|
|
472
|
+
if (!session) {
|
|
473
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
474
|
+
}
|
|
475
|
+
const { report, base64Screenshot } = yield session.scanViolations(violationsTag);
|
|
476
|
+
return {
|
|
477
|
+
content: [
|
|
478
|
+
{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: JSON.stringify({
|
|
481
|
+
message: "Accessibility scan completed",
|
|
482
|
+
sessionId,
|
|
483
|
+
report,
|
|
484
|
+
}, null, 2),
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
type: "image",
|
|
488
|
+
data: base64Screenshot,
|
|
489
|
+
mimeType: "image/png",
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
isError: false,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
return {
|
|
497
|
+
content: [
|
|
498
|
+
{
|
|
499
|
+
type: "text",
|
|
500
|
+
text: JSON.stringify({
|
|
501
|
+
message: `Failed to scan session: ${sessionId}`,
|
|
502
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
503
|
+
sessionId,
|
|
504
|
+
}, null, 2),
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
isError: true,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}));
|
|
511
|
+
server.registerTool("close-session", {
|
|
512
|
+
title: "Close Browser Session",
|
|
513
|
+
description: "Closes an existing browser session.",
|
|
514
|
+
inputSchema: zod_1.z.object({
|
|
515
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
516
|
+
}).shape,
|
|
517
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
518
|
+
const { sessionId } = args;
|
|
519
|
+
try {
|
|
520
|
+
yield sessionManager.closeSession(sessionId);
|
|
521
|
+
return {
|
|
522
|
+
content: [
|
|
523
|
+
{
|
|
524
|
+
type: "text",
|
|
525
|
+
text: JSON.stringify({
|
|
526
|
+
message: `Session ${sessionId} closed successfully`,
|
|
527
|
+
sessionId,
|
|
528
|
+
}, null, 2),
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
isError: false,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
return {
|
|
536
|
+
content: [
|
|
537
|
+
{
|
|
538
|
+
type: "text",
|
|
539
|
+
text: JSON.stringify({
|
|
540
|
+
message: `Failed to close session: ${sessionId}`,
|
|
541
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
542
|
+
sessionId,
|
|
543
|
+
}, null, 2),
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
isError: true,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}));
|
|
550
|
+
server.registerTool("list-sessions", {
|
|
551
|
+
title: "List Browser Sessions",
|
|
552
|
+
description: "Lists all active browser sessions.",
|
|
553
|
+
inputSchema: zod_1.z.object({}).shape,
|
|
554
|
+
}, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
555
|
+
try {
|
|
556
|
+
const sessions = sessionManager.listSessions();
|
|
557
|
+
return {
|
|
558
|
+
content: [
|
|
559
|
+
{
|
|
560
|
+
type: "text",
|
|
561
|
+
text: JSON.stringify({
|
|
562
|
+
message: "Active browser sessions",
|
|
563
|
+
sessions,
|
|
564
|
+
count: sessions.length,
|
|
565
|
+
}, null, 2),
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
isError: false,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: JSON.stringify({
|
|
577
|
+
message: "Failed to list sessions",
|
|
578
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
579
|
+
}, null, 2),
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
isError: true,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}));
|
|
586
|
+
server.registerTool("click-element-by-text", {
|
|
587
|
+
title: "Click Element by Text",
|
|
588
|
+
description: "Clicks on an element by its visible text content. More reliable than CSS selectors for dynamic content.",
|
|
589
|
+
inputSchema: zod_1.z.object({
|
|
590
|
+
url: zod_1.z.string().url().describe("The public URL to navigate to"),
|
|
591
|
+
text: zod_1.z.string().describe("The visible text of the element to click"),
|
|
592
|
+
elementType: zod_1.z.string().optional().describe("Optional element type (e.g., 'button', 'a', 'div')"),
|
|
593
|
+
viewport: zod_1.z
|
|
594
|
+
.object({
|
|
595
|
+
width: zod_1.z.number().default(1920),
|
|
596
|
+
height: zod_1.z.number().default(1080),
|
|
597
|
+
})
|
|
598
|
+
.optional()
|
|
599
|
+
.describe("Optional viewport dimensions"),
|
|
600
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
601
|
+
}).shape,
|
|
602
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
603
|
+
const { url, text, elementType, viewport, shouldRunInHeadless } = args;
|
|
604
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
605
|
+
try {
|
|
606
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
607
|
+
yield scanner.createContext(viewport);
|
|
608
|
+
yield scanner.createPage();
|
|
609
|
+
yield scanner.navigateToUrl(url);
|
|
610
|
+
yield scanner.clickElementByText(text, elementType);
|
|
611
|
+
const base64Screenshot = yield scanner.takeScreenshot();
|
|
612
|
+
return {
|
|
613
|
+
content: [
|
|
614
|
+
{
|
|
615
|
+
type: "text",
|
|
616
|
+
text: JSON.stringify({
|
|
617
|
+
message: `Successfully clicked element with text: "${text}"`,
|
|
618
|
+
url,
|
|
619
|
+
text,
|
|
620
|
+
elementType,
|
|
621
|
+
}, null, 2),
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
type: "image",
|
|
625
|
+
data: base64Screenshot,
|
|
626
|
+
mimeType: "image/png",
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
isError: false,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
return {
|
|
634
|
+
content: [
|
|
635
|
+
{
|
|
636
|
+
type: "text",
|
|
637
|
+
text: JSON.stringify({
|
|
638
|
+
message: `Failed to click element with text: "${text}"`,
|
|
639
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
640
|
+
url,
|
|
641
|
+
text,
|
|
642
|
+
}, null, 2),
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
isError: true,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
finally {
|
|
649
|
+
yield scanner.cleanup();
|
|
650
|
+
}
|
|
651
|
+
}));
|
|
652
|
+
server.registerTool("type-text-by-label", {
|
|
653
|
+
title: "Type Text by Label",
|
|
654
|
+
description: "Types text into an input field identified by its label text. More intuitive than CSS selectors.",
|
|
655
|
+
inputSchema: zod_1.z.object({
|
|
656
|
+
url: zod_1.z.string().url().describe("The public URL to navigate to"),
|
|
657
|
+
labelText: zod_1.z.string().describe("The label text of the input field"),
|
|
658
|
+
text: zod_1.z.string().describe("Text to type into the input field"),
|
|
659
|
+
viewport: zod_1.z
|
|
660
|
+
.object({
|
|
661
|
+
width: zod_1.z.number().default(1920),
|
|
662
|
+
height: zod_1.z.number().default(1080),
|
|
663
|
+
})
|
|
664
|
+
.optional()
|
|
665
|
+
.describe("Optional viewport dimensions"),
|
|
666
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
667
|
+
}).shape,
|
|
668
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
669
|
+
const { url, labelText, text, viewport, shouldRunInHeadless } = args;
|
|
670
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
671
|
+
try {
|
|
672
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
673
|
+
yield scanner.createContext(viewport);
|
|
674
|
+
yield scanner.createPage();
|
|
675
|
+
yield scanner.navigateToUrl(url);
|
|
676
|
+
yield scanner.typeTextByLabel(labelText, text);
|
|
677
|
+
const base64Screenshot = yield scanner.takeScreenshot();
|
|
678
|
+
return {
|
|
679
|
+
content: [
|
|
680
|
+
{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: JSON.stringify({
|
|
683
|
+
message: `Successfully typed text into field with label: "${labelText}"`,
|
|
684
|
+
url,
|
|
685
|
+
labelText,
|
|
686
|
+
text,
|
|
687
|
+
}, null, 2),
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
type: "image",
|
|
691
|
+
data: base64Screenshot,
|
|
692
|
+
mimeType: "image/png",
|
|
693
|
+
},
|
|
694
|
+
],
|
|
695
|
+
isError: false,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
return {
|
|
700
|
+
content: [
|
|
701
|
+
{
|
|
702
|
+
type: "text",
|
|
703
|
+
text: JSON.stringify({
|
|
704
|
+
message: `Failed to type text into field with label: "${labelText}"`,
|
|
705
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
706
|
+
url,
|
|
707
|
+
labelText,
|
|
708
|
+
text,
|
|
709
|
+
}, null, 2),
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
isError: true,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
yield scanner.cleanup();
|
|
717
|
+
}
|
|
718
|
+
}));
|
|
719
|
+
server.registerTool("analyze-page", {
|
|
720
|
+
title: "Analyze Page",
|
|
721
|
+
description: "Analyzes the current page and returns all interactive elements (buttons, links, inputs) to prevent guessing.",
|
|
722
|
+
inputSchema: zod_1.z.object({
|
|
723
|
+
url: zod_1.z.string().url().describe("The public URL to analyze"),
|
|
724
|
+
viewport: zod_1.z
|
|
725
|
+
.object({
|
|
726
|
+
width: zod_1.z.number().default(1920),
|
|
727
|
+
height: zod_1.z.number().default(1080),
|
|
728
|
+
})
|
|
729
|
+
.optional()
|
|
730
|
+
.describe("Optional viewport dimensions"),
|
|
731
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
732
|
+
}).shape,
|
|
733
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
734
|
+
const { url, viewport, shouldRunInHeadless } = args;
|
|
735
|
+
const scanner = new accessibilityChecker_1.AccessibilityScanner();
|
|
736
|
+
try {
|
|
737
|
+
yield scanner.initialize(shouldRunInHeadless);
|
|
738
|
+
yield scanner.createContext(viewport);
|
|
739
|
+
yield scanner.createPage();
|
|
740
|
+
yield scanner.navigateToUrl(url);
|
|
741
|
+
const analysis = yield scanner.analyzePage();
|
|
742
|
+
const base64Screenshot = yield scanner.takeScreenshot();
|
|
743
|
+
return {
|
|
744
|
+
content: [
|
|
745
|
+
{
|
|
746
|
+
type: "text",
|
|
747
|
+
text: JSON.stringify({
|
|
748
|
+
message: "Page analysis completed",
|
|
749
|
+
url,
|
|
750
|
+
elements: {
|
|
751
|
+
buttons: analysis.buttons,
|
|
752
|
+
links: analysis.links,
|
|
753
|
+
inputs: analysis.inputs,
|
|
754
|
+
totals: {
|
|
755
|
+
buttons: analysis.buttons.length,
|
|
756
|
+
links: analysis.links.length,
|
|
757
|
+
inputs: analysis.inputs.length,
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
}, null, 2),
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
type: "image",
|
|
764
|
+
data: base64Screenshot,
|
|
765
|
+
mimeType: "image/png",
|
|
766
|
+
},
|
|
767
|
+
],
|
|
768
|
+
isError: false,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
return {
|
|
773
|
+
content: [
|
|
774
|
+
{
|
|
775
|
+
type: "text",
|
|
776
|
+
text: JSON.stringify({
|
|
777
|
+
message: "Failed to analyze page",
|
|
778
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
779
|
+
url,
|
|
780
|
+
}, null, 2),
|
|
781
|
+
},
|
|
782
|
+
],
|
|
783
|
+
isError: true,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
finally {
|
|
787
|
+
yield scanner.cleanup();
|
|
788
|
+
}
|
|
789
|
+
}));
|
|
790
|
+
server.registerTool("click-session-by-text", {
|
|
791
|
+
title: "Click Element by Text in Session",
|
|
792
|
+
description: "Clicks on an element by its visible text in an existing browser session.",
|
|
793
|
+
inputSchema: zod_1.z.object({
|
|
794
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
795
|
+
text: zod_1.z.string().describe("The visible text of the element to click"),
|
|
796
|
+
elementType: zod_1.z.string().optional().describe("Optional element type (e.g., 'button', 'a', 'div')"),
|
|
797
|
+
}).shape,
|
|
798
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
799
|
+
const { sessionId, text, elementType } = args;
|
|
800
|
+
try {
|
|
801
|
+
const session = sessionManager.getSession(sessionId);
|
|
802
|
+
if (!session) {
|
|
803
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
804
|
+
}
|
|
805
|
+
yield session.clickElementByText(text, elementType);
|
|
806
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
807
|
+
return {
|
|
808
|
+
content: [
|
|
809
|
+
{
|
|
810
|
+
type: "text",
|
|
811
|
+
text: JSON.stringify({
|
|
812
|
+
message: `Successfully clicked element with text: "${text}"`,
|
|
813
|
+
sessionId,
|
|
814
|
+
text,
|
|
815
|
+
elementType,
|
|
816
|
+
}, null, 2),
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
type: "image",
|
|
820
|
+
data: base64Screenshot,
|
|
821
|
+
mimeType: "image/png",
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
isError: false,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
return {
|
|
829
|
+
content: [
|
|
830
|
+
{
|
|
831
|
+
type: "text",
|
|
832
|
+
text: JSON.stringify({
|
|
833
|
+
message: `Failed to click element with text in session: ${sessionId}`,
|
|
834
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
835
|
+
sessionId,
|
|
836
|
+
text,
|
|
837
|
+
}, null, 2),
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
isError: true,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}));
|
|
844
|
+
server.registerTool("type-session-by-label", {
|
|
845
|
+
title: "Type Text by Label in Session",
|
|
846
|
+
description: "Types text into an input field identified by its label in an existing browser session.",
|
|
847
|
+
inputSchema: zod_1.z.object({
|
|
848
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
849
|
+
labelText: zod_1.z.string().describe("The label text of the input field"),
|
|
850
|
+
text: zod_1.z.string().describe("Text to type into the input field"),
|
|
851
|
+
}).shape,
|
|
852
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
853
|
+
const { sessionId, labelText, text } = args;
|
|
854
|
+
try {
|
|
855
|
+
const session = sessionManager.getSession(sessionId);
|
|
856
|
+
if (!session) {
|
|
857
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
858
|
+
}
|
|
859
|
+
yield session.typeTextByLabel(labelText, text);
|
|
860
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
861
|
+
return {
|
|
862
|
+
content: [
|
|
863
|
+
{
|
|
864
|
+
type: "text",
|
|
865
|
+
text: JSON.stringify({
|
|
866
|
+
message: `Successfully typed text into field with label: "${labelText}"`,
|
|
867
|
+
sessionId,
|
|
868
|
+
labelText,
|
|
869
|
+
text,
|
|
870
|
+
}, null, 2),
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
type: "image",
|
|
874
|
+
data: base64Screenshot,
|
|
875
|
+
mimeType: "image/png",
|
|
876
|
+
},
|
|
877
|
+
],
|
|
878
|
+
isError: false,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
catch (error) {
|
|
882
|
+
return {
|
|
883
|
+
content: [
|
|
884
|
+
{
|
|
885
|
+
type: "text",
|
|
886
|
+
text: JSON.stringify({
|
|
887
|
+
message: `Failed to type text by label in session: ${sessionId}`,
|
|
888
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
889
|
+
sessionId,
|
|
890
|
+
labelText,
|
|
891
|
+
text,
|
|
892
|
+
}, null, 2),
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
isError: true,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}));
|
|
899
|
+
server.registerTool("analyze-session", {
|
|
900
|
+
title: "Analyze Page in Session",
|
|
901
|
+
description: "Analyzes the current page in an existing browser session.",
|
|
902
|
+
inputSchema: zod_1.z.object({
|
|
903
|
+
sessionId: zod_1.z.string().describe("Session identifier"),
|
|
904
|
+
}).shape,
|
|
905
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
906
|
+
const { sessionId } = args;
|
|
907
|
+
try {
|
|
908
|
+
const session = sessionManager.getSession(sessionId);
|
|
909
|
+
if (!session) {
|
|
910
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
911
|
+
}
|
|
912
|
+
const analysis = yield session.analyzePage();
|
|
913
|
+
const base64Screenshot = yield session.takeScreenshot();
|
|
914
|
+
return {
|
|
915
|
+
content: [
|
|
916
|
+
{
|
|
917
|
+
type: "text",
|
|
918
|
+
text: JSON.stringify({
|
|
919
|
+
message: "Page analysis completed",
|
|
920
|
+
sessionId,
|
|
921
|
+
elements: {
|
|
922
|
+
buttons: analysis.buttons,
|
|
923
|
+
links: analysis.links,
|
|
924
|
+
inputs: analysis.inputs,
|
|
925
|
+
totals: {
|
|
926
|
+
buttons: analysis.buttons.length,
|
|
927
|
+
links: analysis.links.length,
|
|
928
|
+
inputs: analysis.inputs.length,
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
}, null, 2),
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
type: "image",
|
|
935
|
+
data: base64Screenshot,
|
|
936
|
+
mimeType: "image/png",
|
|
937
|
+
},
|
|
938
|
+
],
|
|
939
|
+
isError: false,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
catch (error) {
|
|
943
|
+
return {
|
|
944
|
+
content: [
|
|
945
|
+
{
|
|
946
|
+
type: "text",
|
|
947
|
+
text: JSON.stringify({
|
|
948
|
+
message: `Failed to analyze page in session: ${sessionId}`,
|
|
949
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
950
|
+
sessionId,
|
|
951
|
+
}, null, 2),
|
|
952
|
+
},
|
|
953
|
+
],
|
|
954
|
+
isError: true,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}));
|
|
63
958
|
exports.default = server;
|