@sparkleideas/browser 3.0.0-alpha.18
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 +730 -0
- package/agents/architect.yaml +11 -0
- package/agents/coder.yaml +11 -0
- package/agents/reviewer.yaml +10 -0
- package/agents/security-architect.yaml +10 -0
- package/agents/tester.yaml +10 -0
- package/docker/Dockerfile +22 -0
- package/docker/docker-compose.yml +52 -0
- package/docker/test-fixtures/index.html +61 -0
- package/package.json +56 -0
- package/skills/browser/SKILL.md +204 -0
- package/src/agent/index.ts +35 -0
- package/src/application/browser-service.ts +570 -0
- package/src/domain/types.ts +324 -0
- package/src/index.ts +156 -0
- package/src/infrastructure/agent-browser-adapter.ts +654 -0
- package/src/infrastructure/hooks-integration.ts +170 -0
- package/src/infrastructure/memory-integration.ts +449 -0
- package/src/infrastructure/reasoningbank-adapter.ts +282 -0
- package/src/infrastructure/security-integration.ts +528 -0
- package/src/infrastructure/workflow-templates.ts +479 -0
- package/src/mcp-tools/browser-tools.ts +1210 -0
- package/src/mcp-tools/index.ts +6 -0
- package/src/skill/index.ts +24 -0
- package/tests/agent-browser-adapter.test.ts +328 -0
- package/tests/browser-service.test.ts +137 -0
- package/tests/e2e/browser-e2e.test.ts +175 -0
- package/tests/memory-integration.test.ts +277 -0
- package/tests/reasoningbank-adapter.test.ts +219 -0
- package/tests/security-integration.test.ts +194 -0
- package/tests/workflow-templates.test.ts +231 -0
- package/tmp.json +0 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sparkleideas/browser - MCP Tools
|
|
3
|
+
* 50+ browser automation tools for claude-flow MCP server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AgentBrowserAdapter } from '../infrastructure/agent-browser-adapter.js';
|
|
7
|
+
import type { ActionResult, Snapshot } from '../domain/types.js';
|
|
8
|
+
|
|
9
|
+
// Session registry for multi-agent coordination
|
|
10
|
+
const sessions = new Map<string, AgentBrowserAdapter>();
|
|
11
|
+
|
|
12
|
+
function getAdapter(sessionId?: string): AgentBrowserAdapter {
|
|
13
|
+
const id = sessionId || 'default';
|
|
14
|
+
if (!sessions.has(id)) {
|
|
15
|
+
sessions.set(id, new AgentBrowserAdapter({ session: id }));
|
|
16
|
+
}
|
|
17
|
+
return sessions.get(id)!;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MCPTool {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
category: string;
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object';
|
|
26
|
+
properties: Record<string, unknown>;
|
|
27
|
+
required?: string[];
|
|
28
|
+
};
|
|
29
|
+
handler: (input: Record<string, unknown>) => Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Navigation Tools
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
const navigationTools: MCPTool[] = [
|
|
37
|
+
{
|
|
38
|
+
name: 'browser/open',
|
|
39
|
+
description: 'Navigate to a URL. Returns page title and final URL after redirects.',
|
|
40
|
+
category: 'browser-navigation',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
url: { type: 'string', description: 'URL to navigate to' },
|
|
45
|
+
session: { type: 'string', description: 'Session ID for isolated browser instance' },
|
|
46
|
+
waitUntil: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
enum: ['load', 'domcontentloaded', 'networkidle'],
|
|
49
|
+
description: 'When to consider navigation complete',
|
|
50
|
+
},
|
|
51
|
+
headers: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
description: 'HTTP headers to set (scoped to URL origin)',
|
|
54
|
+
additionalProperties: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['url'],
|
|
58
|
+
},
|
|
59
|
+
handler: async (input) => {
|
|
60
|
+
const adapter = getAdapter(input.session as string);
|
|
61
|
+
return adapter.open({
|
|
62
|
+
url: input.url as string,
|
|
63
|
+
waitUntil: input.waitUntil as 'load' | 'domcontentloaded' | 'networkidle',
|
|
64
|
+
headers: input.headers as Record<string, string>,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'browser/back',
|
|
70
|
+
description: 'Navigate back in browser history',
|
|
71
|
+
category: 'browser-navigation',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
session: { type: 'string', description: 'Session ID' },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
handler: async (input) => {
|
|
79
|
+
const adapter = getAdapter(input.session as string);
|
|
80
|
+
return adapter.back();
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'browser/forward',
|
|
85
|
+
description: 'Navigate forward in browser history',
|
|
86
|
+
category: 'browser-navigation',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
session: { type: 'string', description: 'Session ID' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
handler: async (input) => {
|
|
94
|
+
const adapter = getAdapter(input.session as string);
|
|
95
|
+
return adapter.forward();
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'browser/reload',
|
|
100
|
+
description: 'Reload the current page',
|
|
101
|
+
category: 'browser-navigation',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
session: { type: 'string', description: 'Session ID' },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
handler: async (input) => {
|
|
109
|
+
const adapter = getAdapter(input.session as string);
|
|
110
|
+
return adapter.reload();
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'browser/close',
|
|
115
|
+
description: 'Close the browser session',
|
|
116
|
+
category: 'browser-navigation',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
session: { type: 'string', description: 'Session ID' },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
handler: async (input) => {
|
|
124
|
+
const adapter = getAdapter(input.session as string);
|
|
125
|
+
const result = await adapter.close();
|
|
126
|
+
sessions.delete(input.session as string || 'default');
|
|
127
|
+
return result;
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Snapshot Tools (AI-Optimized)
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
const snapshotTools: MCPTool[] = [
|
|
137
|
+
{
|
|
138
|
+
name: 'browser/snapshot',
|
|
139
|
+
description: 'Get accessibility tree with element refs (@e1, @e2). Best for AI - use refs to interact with elements. Returns structured tree with interactive elements highlighted.',
|
|
140
|
+
category: 'browser-snapshot',
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
session: { type: 'string', description: 'Session ID' },
|
|
145
|
+
interactive: { type: 'boolean', description: 'Only show interactive elements (buttons, links, inputs)', default: true },
|
|
146
|
+
compact: { type: 'boolean', description: 'Remove empty structural elements', default: true },
|
|
147
|
+
depth: { type: 'number', description: 'Limit tree depth (e.g., 3 levels)' },
|
|
148
|
+
selector: { type: 'string', description: 'Scope snapshot to CSS selector' },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
handler: async (input) => {
|
|
152
|
+
const adapter = getAdapter(input.session as string);
|
|
153
|
+
return adapter.snapshot({
|
|
154
|
+
interactive: input.interactive !== false,
|
|
155
|
+
compact: input.compact !== false,
|
|
156
|
+
depth: input.depth as number,
|
|
157
|
+
selector: input.selector as string,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'browser/screenshot',
|
|
163
|
+
description: 'Capture screenshot. Returns base64 PNG if no path specified.',
|
|
164
|
+
category: 'browser-snapshot',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
session: { type: 'string', description: 'Session ID' },
|
|
169
|
+
path: { type: 'string', description: 'File path to save (optional, returns base64 if omitted)' },
|
|
170
|
+
fullPage: { type: 'boolean', description: 'Capture full scrollable page', default: false },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
handler: async (input) => {
|
|
174
|
+
const adapter = getAdapter(input.session as string);
|
|
175
|
+
return adapter.screenshot({
|
|
176
|
+
path: input.path as string,
|
|
177
|
+
fullPage: input.fullPage as boolean,
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'browser/pdf',
|
|
183
|
+
description: 'Save page as PDF',
|
|
184
|
+
category: 'browser-snapshot',
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
session: { type: 'string', description: 'Session ID' },
|
|
189
|
+
path: { type: 'string', description: 'File path to save PDF' },
|
|
190
|
+
},
|
|
191
|
+
required: ['path'],
|
|
192
|
+
},
|
|
193
|
+
handler: async (input) => {
|
|
194
|
+
const adapter = getAdapter(input.session as string);
|
|
195
|
+
return adapter.pdf(input.path as string);
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Interaction Tools
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
const interactionTools: MCPTool[] = [
|
|
205
|
+
{
|
|
206
|
+
name: 'browser/click',
|
|
207
|
+
description: 'Click an element. Use @e1 refs from snapshot or CSS selectors.',
|
|
208
|
+
category: 'browser-interaction',
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {
|
|
212
|
+
session: { type: 'string', description: 'Session ID' },
|
|
213
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
214
|
+
button: { type: 'string', enum: ['left', 'right', 'middle'], default: 'left' },
|
|
215
|
+
clickCount: { type: 'number', description: 'Number of clicks (2 for double-click)' },
|
|
216
|
+
force: { type: 'boolean', description: 'Force click even if element is not visible' },
|
|
217
|
+
},
|
|
218
|
+
required: ['target'],
|
|
219
|
+
},
|
|
220
|
+
handler: async (input) => {
|
|
221
|
+
const adapter = getAdapter(input.session as string);
|
|
222
|
+
return adapter.click({
|
|
223
|
+
target: input.target as string,
|
|
224
|
+
button: input.button as 'left' | 'right' | 'middle',
|
|
225
|
+
clickCount: input.clickCount as number,
|
|
226
|
+
force: input.force as boolean,
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'browser/fill',
|
|
232
|
+
description: 'Clear and fill an input field. Use @e1 refs from snapshot.',
|
|
233
|
+
category: 'browser-interaction',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
session: { type: 'string', description: 'Session ID' },
|
|
238
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
239
|
+
value: { type: 'string', description: 'Text to fill' },
|
|
240
|
+
force: { type: 'boolean', description: 'Force fill even if element is not visible' },
|
|
241
|
+
},
|
|
242
|
+
required: ['target', 'value'],
|
|
243
|
+
},
|
|
244
|
+
handler: async (input) => {
|
|
245
|
+
const adapter = getAdapter(input.session as string);
|
|
246
|
+
return adapter.fill({
|
|
247
|
+
target: input.target as string,
|
|
248
|
+
value: input.value as string,
|
|
249
|
+
force: input.force as boolean,
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'browser/type',
|
|
255
|
+
description: 'Type text character by character (with key events). Slower than fill but simulates real typing.',
|
|
256
|
+
category: 'browser-interaction',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
session: { type: 'string', description: 'Session ID' },
|
|
261
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
262
|
+
text: { type: 'string', description: 'Text to type' },
|
|
263
|
+
delay: { type: 'number', description: 'Delay between keystrokes in ms' },
|
|
264
|
+
},
|
|
265
|
+
required: ['target', 'text'],
|
|
266
|
+
},
|
|
267
|
+
handler: async (input) => {
|
|
268
|
+
const adapter = getAdapter(input.session as string);
|
|
269
|
+
return adapter.type({
|
|
270
|
+
target: input.target as string,
|
|
271
|
+
text: input.text as string,
|
|
272
|
+
delay: input.delay as number,
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'browser/press',
|
|
278
|
+
description: 'Press a keyboard key (Enter, Tab, Escape, Control+a, etc.)',
|
|
279
|
+
category: 'browser-interaction',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
session: { type: 'string', description: 'Session ID' },
|
|
284
|
+
key: { type: 'string', description: 'Key to press (Enter, Tab, Control+a, etc.)' },
|
|
285
|
+
delay: { type: 'number', description: 'Key hold duration in ms' },
|
|
286
|
+
},
|
|
287
|
+
required: ['key'],
|
|
288
|
+
},
|
|
289
|
+
handler: async (input) => {
|
|
290
|
+
const adapter = getAdapter(input.session as string);
|
|
291
|
+
return adapter.press(input.key as string, input.delay as number);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'browser/hover',
|
|
296
|
+
description: 'Hover over an element',
|
|
297
|
+
category: 'browser-interaction',
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: {
|
|
301
|
+
session: { type: 'string', description: 'Session ID' },
|
|
302
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
303
|
+
},
|
|
304
|
+
required: ['target'],
|
|
305
|
+
},
|
|
306
|
+
handler: async (input) => {
|
|
307
|
+
const adapter = getAdapter(input.session as string);
|
|
308
|
+
return adapter.hover(input.target as string);
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'browser/select',
|
|
313
|
+
description: 'Select dropdown option by value',
|
|
314
|
+
category: 'browser-interaction',
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
session: { type: 'string', description: 'Session ID' },
|
|
319
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
320
|
+
value: { type: 'string', description: 'Option value to select' },
|
|
321
|
+
},
|
|
322
|
+
required: ['target', 'value'],
|
|
323
|
+
},
|
|
324
|
+
handler: async (input) => {
|
|
325
|
+
const adapter = getAdapter(input.session as string);
|
|
326
|
+
return adapter.select(input.target as string, input.value as string);
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: 'browser/check',
|
|
331
|
+
description: 'Check a checkbox',
|
|
332
|
+
category: 'browser-interaction',
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: 'object',
|
|
335
|
+
properties: {
|
|
336
|
+
session: { type: 'string', description: 'Session ID' },
|
|
337
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
338
|
+
},
|
|
339
|
+
required: ['target'],
|
|
340
|
+
},
|
|
341
|
+
handler: async (input) => {
|
|
342
|
+
const adapter = getAdapter(input.session as string);
|
|
343
|
+
return adapter.check(input.target as string);
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'browser/uncheck',
|
|
348
|
+
description: 'Uncheck a checkbox',
|
|
349
|
+
category: 'browser-interaction',
|
|
350
|
+
inputSchema: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
properties: {
|
|
353
|
+
session: { type: 'string', description: 'Session ID' },
|
|
354
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
355
|
+
},
|
|
356
|
+
required: ['target'],
|
|
357
|
+
},
|
|
358
|
+
handler: async (input) => {
|
|
359
|
+
const adapter = getAdapter(input.session as string);
|
|
360
|
+
return adapter.uncheck(input.target as string);
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: 'browser/scroll',
|
|
365
|
+
description: 'Scroll the page or element',
|
|
366
|
+
category: 'browser-interaction',
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: 'object',
|
|
369
|
+
properties: {
|
|
370
|
+
session: { type: 'string', description: 'Session ID' },
|
|
371
|
+
direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], description: 'Scroll direction' },
|
|
372
|
+
pixels: { type: 'number', description: 'Pixels to scroll (default: viewport height)' },
|
|
373
|
+
},
|
|
374
|
+
required: ['direction'],
|
|
375
|
+
},
|
|
376
|
+
handler: async (input) => {
|
|
377
|
+
const adapter = getAdapter(input.session as string);
|
|
378
|
+
return adapter.scroll(
|
|
379
|
+
input.direction as 'up' | 'down' | 'left' | 'right',
|
|
380
|
+
input.pixels as number
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: 'browser/upload',
|
|
386
|
+
description: 'Upload files to a file input',
|
|
387
|
+
category: 'browser-interaction',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
session: { type: 'string', description: 'Session ID' },
|
|
392
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
393
|
+
files: { type: 'array', items: { type: 'string' }, description: 'File paths to upload' },
|
|
394
|
+
},
|
|
395
|
+
required: ['target', 'files'],
|
|
396
|
+
},
|
|
397
|
+
handler: async (input) => {
|
|
398
|
+
const adapter = getAdapter(input.session as string);
|
|
399
|
+
return adapter.upload(input.target as string, input.files as string[]);
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
// ============================================================================
|
|
405
|
+
// Get Info Tools
|
|
406
|
+
// ============================================================================
|
|
407
|
+
|
|
408
|
+
const getInfoTools: MCPTool[] = [
|
|
409
|
+
{
|
|
410
|
+
name: 'browser/get-text',
|
|
411
|
+
description: 'Get text content of an element',
|
|
412
|
+
category: 'browser-info',
|
|
413
|
+
inputSchema: {
|
|
414
|
+
type: 'object',
|
|
415
|
+
properties: {
|
|
416
|
+
session: { type: 'string', description: 'Session ID' },
|
|
417
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
418
|
+
},
|
|
419
|
+
required: ['target'],
|
|
420
|
+
},
|
|
421
|
+
handler: async (input) => {
|
|
422
|
+
const adapter = getAdapter(input.session as string);
|
|
423
|
+
return adapter.getText(input.target as string);
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'browser/get-html',
|
|
428
|
+
description: 'Get innerHTML of an element',
|
|
429
|
+
category: 'browser-info',
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
session: { type: 'string', description: 'Session ID' },
|
|
434
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
435
|
+
},
|
|
436
|
+
required: ['target'],
|
|
437
|
+
},
|
|
438
|
+
handler: async (input) => {
|
|
439
|
+
const adapter = getAdapter(input.session as string);
|
|
440
|
+
return adapter.getHtml(input.target as string);
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'browser/get-value',
|
|
445
|
+
description: 'Get value of an input element',
|
|
446
|
+
category: 'browser-info',
|
|
447
|
+
inputSchema: {
|
|
448
|
+
type: 'object',
|
|
449
|
+
properties: {
|
|
450
|
+
session: { type: 'string', description: 'Session ID' },
|
|
451
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
452
|
+
},
|
|
453
|
+
required: ['target'],
|
|
454
|
+
},
|
|
455
|
+
handler: async (input) => {
|
|
456
|
+
const adapter = getAdapter(input.session as string);
|
|
457
|
+
return adapter.getValue(input.target as string);
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'browser/get-attr',
|
|
462
|
+
description: 'Get an attribute value from an element',
|
|
463
|
+
category: 'browser-info',
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: 'object',
|
|
466
|
+
properties: {
|
|
467
|
+
session: { type: 'string', description: 'Session ID' },
|
|
468
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
469
|
+
attribute: { type: 'string', description: 'Attribute name (href, src, data-*, etc.)' },
|
|
470
|
+
},
|
|
471
|
+
required: ['target', 'attribute'],
|
|
472
|
+
},
|
|
473
|
+
handler: async (input) => {
|
|
474
|
+
const adapter = getAdapter(input.session as string);
|
|
475
|
+
return adapter.getAttr(input.target as string, input.attribute as string);
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: 'browser/get-title',
|
|
480
|
+
description: 'Get the page title',
|
|
481
|
+
category: 'browser-info',
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: 'object',
|
|
484
|
+
properties: {
|
|
485
|
+
session: { type: 'string', description: 'Session ID' },
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
handler: async (input) => {
|
|
489
|
+
const adapter = getAdapter(input.session as string);
|
|
490
|
+
return adapter.getTitle();
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: 'browser/get-url',
|
|
495
|
+
description: 'Get the current page URL',
|
|
496
|
+
category: 'browser-info',
|
|
497
|
+
inputSchema: {
|
|
498
|
+
type: 'object',
|
|
499
|
+
properties: {
|
|
500
|
+
session: { type: 'string', description: 'Session ID' },
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
handler: async (input) => {
|
|
504
|
+
const adapter = getAdapter(input.session as string);
|
|
505
|
+
return adapter.getUrl();
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'browser/get-count',
|
|
510
|
+
description: 'Count elements matching a selector',
|
|
511
|
+
category: 'browser-info',
|
|
512
|
+
inputSchema: {
|
|
513
|
+
type: 'object',
|
|
514
|
+
properties: {
|
|
515
|
+
session: { type: 'string', description: 'Session ID' },
|
|
516
|
+
selector: { type: 'string', description: 'CSS selector to count' },
|
|
517
|
+
},
|
|
518
|
+
required: ['selector'],
|
|
519
|
+
},
|
|
520
|
+
handler: async (input) => {
|
|
521
|
+
const adapter = getAdapter(input.session as string);
|
|
522
|
+
return adapter.getCount(input.selector as string);
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
// ============================================================================
|
|
528
|
+
// State Check Tools
|
|
529
|
+
// ============================================================================
|
|
530
|
+
|
|
531
|
+
const stateTools: MCPTool[] = [
|
|
532
|
+
{
|
|
533
|
+
name: 'browser/is-visible',
|
|
534
|
+
description: 'Check if an element is visible',
|
|
535
|
+
category: 'browser-state',
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: 'object',
|
|
538
|
+
properties: {
|
|
539
|
+
session: { type: 'string', description: 'Session ID' },
|
|
540
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
541
|
+
},
|
|
542
|
+
required: ['target'],
|
|
543
|
+
},
|
|
544
|
+
handler: async (input) => {
|
|
545
|
+
const adapter = getAdapter(input.session as string);
|
|
546
|
+
return adapter.isVisible(input.target as string);
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'browser/is-enabled',
|
|
551
|
+
description: 'Check if an element is enabled',
|
|
552
|
+
category: 'browser-state',
|
|
553
|
+
inputSchema: {
|
|
554
|
+
type: 'object',
|
|
555
|
+
properties: {
|
|
556
|
+
session: { type: 'string', description: 'Session ID' },
|
|
557
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
558
|
+
},
|
|
559
|
+
required: ['target'],
|
|
560
|
+
},
|
|
561
|
+
handler: async (input) => {
|
|
562
|
+
const adapter = getAdapter(input.session as string);
|
|
563
|
+
return adapter.isEnabled(input.target as string);
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: 'browser/is-checked',
|
|
568
|
+
description: 'Check if a checkbox is checked',
|
|
569
|
+
category: 'browser-state',
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: 'object',
|
|
572
|
+
properties: {
|
|
573
|
+
session: { type: 'string', description: 'Session ID' },
|
|
574
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
575
|
+
},
|
|
576
|
+
required: ['target'],
|
|
577
|
+
},
|
|
578
|
+
handler: async (input) => {
|
|
579
|
+
const adapter = getAdapter(input.session as string);
|
|
580
|
+
return adapter.isChecked(input.target as string);
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
// ============================================================================
|
|
586
|
+
// Wait Tools
|
|
587
|
+
// ============================================================================
|
|
588
|
+
|
|
589
|
+
const waitTools: MCPTool[] = [
|
|
590
|
+
{
|
|
591
|
+
name: 'browser/wait',
|
|
592
|
+
description: 'Wait for element, time, text, URL, or load state',
|
|
593
|
+
category: 'browser-wait',
|
|
594
|
+
inputSchema: {
|
|
595
|
+
type: 'object',
|
|
596
|
+
properties: {
|
|
597
|
+
session: { type: 'string', description: 'Session ID' },
|
|
598
|
+
selector: { type: 'string', description: 'Wait for element to be visible' },
|
|
599
|
+
timeout: { type: 'number', description: 'Wait for milliseconds' },
|
|
600
|
+
text: { type: 'string', description: 'Wait for text to appear on page' },
|
|
601
|
+
url: { type: 'string', description: 'Wait for URL pattern (glob)' },
|
|
602
|
+
load: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle'], description: 'Wait for load state' },
|
|
603
|
+
fn: { type: 'string', description: 'Wait for JavaScript condition to be true' },
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
handler: async (input) => {
|
|
607
|
+
const adapter = getAdapter(input.session as string);
|
|
608
|
+
return adapter.wait({
|
|
609
|
+
selector: input.selector as string,
|
|
610
|
+
timeout: input.timeout as number,
|
|
611
|
+
text: input.text as string,
|
|
612
|
+
url: input.url as string,
|
|
613
|
+
load: input.load as 'load' | 'domcontentloaded' | 'networkidle',
|
|
614
|
+
fn: input.fn as string,
|
|
615
|
+
});
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// JavaScript Execution
|
|
622
|
+
// ============================================================================
|
|
623
|
+
|
|
624
|
+
const evalTools: MCPTool[] = [
|
|
625
|
+
{
|
|
626
|
+
name: 'browser/eval',
|
|
627
|
+
description: 'Execute JavaScript in the page context',
|
|
628
|
+
category: 'browser-eval',
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: 'object',
|
|
631
|
+
properties: {
|
|
632
|
+
session: { type: 'string', description: 'Session ID' },
|
|
633
|
+
script: { type: 'string', description: 'JavaScript code to execute' },
|
|
634
|
+
},
|
|
635
|
+
required: ['script'],
|
|
636
|
+
},
|
|
637
|
+
handler: async (input) => {
|
|
638
|
+
const adapter = getAdapter(input.session as string);
|
|
639
|
+
return adapter.eval({ script: input.script as string });
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
];
|
|
643
|
+
|
|
644
|
+
// ============================================================================
|
|
645
|
+
// Storage Tools
|
|
646
|
+
// ============================================================================
|
|
647
|
+
|
|
648
|
+
const storageTools: MCPTool[] = [
|
|
649
|
+
{
|
|
650
|
+
name: 'browser/cookies-get',
|
|
651
|
+
description: 'Get all cookies for the current page',
|
|
652
|
+
category: 'browser-storage',
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: 'object',
|
|
655
|
+
properties: {
|
|
656
|
+
session: { type: 'string', description: 'Session ID' },
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
handler: async (input) => {
|
|
660
|
+
const adapter = getAdapter(input.session as string);
|
|
661
|
+
return adapter.getCookies();
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
name: 'browser/cookies-set',
|
|
666
|
+
description: 'Set a cookie',
|
|
667
|
+
category: 'browser-storage',
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: 'object',
|
|
670
|
+
properties: {
|
|
671
|
+
session: { type: 'string', description: 'Session ID' },
|
|
672
|
+
name: { type: 'string', description: 'Cookie name' },
|
|
673
|
+
value: { type: 'string', description: 'Cookie value' },
|
|
674
|
+
},
|
|
675
|
+
required: ['name', 'value'],
|
|
676
|
+
},
|
|
677
|
+
handler: async (input) => {
|
|
678
|
+
const adapter = getAdapter(input.session as string);
|
|
679
|
+
return adapter.setCookie(input.name as string, input.value as string);
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
name: 'browser/cookies-clear',
|
|
684
|
+
description: 'Clear all cookies',
|
|
685
|
+
category: 'browser-storage',
|
|
686
|
+
inputSchema: {
|
|
687
|
+
type: 'object',
|
|
688
|
+
properties: {
|
|
689
|
+
session: { type: 'string', description: 'Session ID' },
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
handler: async (input) => {
|
|
693
|
+
const adapter = getAdapter(input.session as string);
|
|
694
|
+
return adapter.clearCookies();
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
name: 'browser/localstorage-get',
|
|
699
|
+
description: 'Get localStorage value (or all if no key)',
|
|
700
|
+
category: 'browser-storage',
|
|
701
|
+
inputSchema: {
|
|
702
|
+
type: 'object',
|
|
703
|
+
properties: {
|
|
704
|
+
session: { type: 'string', description: 'Session ID' },
|
|
705
|
+
key: { type: 'string', description: 'Key to get (omit for all)' },
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
handler: async (input) => {
|
|
709
|
+
const adapter = getAdapter(input.session as string);
|
|
710
|
+
return adapter.getLocalStorage(input.key as string);
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: 'browser/localstorage-set',
|
|
715
|
+
description: 'Set localStorage value',
|
|
716
|
+
category: 'browser-storage',
|
|
717
|
+
inputSchema: {
|
|
718
|
+
type: 'object',
|
|
719
|
+
properties: {
|
|
720
|
+
session: { type: 'string', description: 'Session ID' },
|
|
721
|
+
key: { type: 'string', description: 'Key to set' },
|
|
722
|
+
value: { type: 'string', description: 'Value to set' },
|
|
723
|
+
},
|
|
724
|
+
required: ['key', 'value'],
|
|
725
|
+
},
|
|
726
|
+
handler: async (input) => {
|
|
727
|
+
const adapter = getAdapter(input.session as string);
|
|
728
|
+
return adapter.setLocalStorage(input.key as string, input.value as string);
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
];
|
|
732
|
+
|
|
733
|
+
// ============================================================================
|
|
734
|
+
// Network Tools
|
|
735
|
+
// ============================================================================
|
|
736
|
+
|
|
737
|
+
const networkTools: MCPTool[] = [
|
|
738
|
+
{
|
|
739
|
+
name: 'browser/network-route',
|
|
740
|
+
description: 'Intercept, block, or mock network requests',
|
|
741
|
+
category: 'browser-network',
|
|
742
|
+
inputSchema: {
|
|
743
|
+
type: 'object',
|
|
744
|
+
properties: {
|
|
745
|
+
session: { type: 'string', description: 'Session ID' },
|
|
746
|
+
urlPattern: { type: 'string', description: 'URL pattern to match (glob)' },
|
|
747
|
+
abort: { type: 'boolean', description: 'Block matching requests' },
|
|
748
|
+
body: { type: 'string', description: 'Mock response body (JSON string)' },
|
|
749
|
+
status: { type: 'number', description: 'Mock response status code' },
|
|
750
|
+
},
|
|
751
|
+
required: ['urlPattern'],
|
|
752
|
+
},
|
|
753
|
+
handler: async (input) => {
|
|
754
|
+
const adapter = getAdapter(input.session as string);
|
|
755
|
+
return adapter.networkRoute({
|
|
756
|
+
urlPattern: input.urlPattern as string,
|
|
757
|
+
abort: input.abort as boolean,
|
|
758
|
+
body: input.body as string,
|
|
759
|
+
status: input.status as number,
|
|
760
|
+
});
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
name: 'browser/network-unroute',
|
|
765
|
+
description: 'Remove network route',
|
|
766
|
+
category: 'browser-network',
|
|
767
|
+
inputSchema: {
|
|
768
|
+
type: 'object',
|
|
769
|
+
properties: {
|
|
770
|
+
session: { type: 'string', description: 'Session ID' },
|
|
771
|
+
urlPattern: { type: 'string', description: 'URL pattern to remove (omit for all)' },
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
handler: async (input) => {
|
|
775
|
+
const adapter = getAdapter(input.session as string);
|
|
776
|
+
return adapter.networkUnroute(input.urlPattern as string);
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: 'browser/network-requests',
|
|
781
|
+
description: 'Get tracked network requests',
|
|
782
|
+
category: 'browser-network',
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: 'object',
|
|
785
|
+
properties: {
|
|
786
|
+
session: { type: 'string', description: 'Session ID' },
|
|
787
|
+
filter: { type: 'string', description: 'Filter by URL substring' },
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
handler: async (input) => {
|
|
791
|
+
const adapter = getAdapter(input.session as string);
|
|
792
|
+
return adapter.networkRequests(input.filter as string);
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
];
|
|
796
|
+
|
|
797
|
+
// ============================================================================
|
|
798
|
+
// Tab & Session Tools
|
|
799
|
+
// ============================================================================
|
|
800
|
+
|
|
801
|
+
const tabTools: MCPTool[] = [
|
|
802
|
+
{
|
|
803
|
+
name: 'browser/tab-list',
|
|
804
|
+
description: 'List all open tabs',
|
|
805
|
+
category: 'browser-tabs',
|
|
806
|
+
inputSchema: {
|
|
807
|
+
type: 'object',
|
|
808
|
+
properties: {
|
|
809
|
+
session: { type: 'string', description: 'Session ID' },
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
handler: async (input) => {
|
|
813
|
+
const adapter = getAdapter(input.session as string);
|
|
814
|
+
return adapter.listTabs();
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: 'browser/tab-new',
|
|
819
|
+
description: 'Open a new tab',
|
|
820
|
+
category: 'browser-tabs',
|
|
821
|
+
inputSchema: {
|
|
822
|
+
type: 'object',
|
|
823
|
+
properties: {
|
|
824
|
+
session: { type: 'string', description: 'Session ID' },
|
|
825
|
+
url: { type: 'string', description: 'URL to open in new tab' },
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
handler: async (input) => {
|
|
829
|
+
const adapter = getAdapter(input.session as string);
|
|
830
|
+
return adapter.newTab(input.url as string);
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
name: 'browser/tab-switch',
|
|
835
|
+
description: 'Switch to a specific tab',
|
|
836
|
+
category: 'browser-tabs',
|
|
837
|
+
inputSchema: {
|
|
838
|
+
type: 'object',
|
|
839
|
+
properties: {
|
|
840
|
+
session: { type: 'string', description: 'Session ID' },
|
|
841
|
+
index: { type: 'number', description: 'Tab index (0-based)' },
|
|
842
|
+
},
|
|
843
|
+
required: ['index'],
|
|
844
|
+
},
|
|
845
|
+
handler: async (input) => {
|
|
846
|
+
const adapter = getAdapter(input.session as string);
|
|
847
|
+
return adapter.switchTab(input.index as number);
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
name: 'browser/tab-close',
|
|
852
|
+
description: 'Close a tab',
|
|
853
|
+
category: 'browser-tabs',
|
|
854
|
+
inputSchema: {
|
|
855
|
+
type: 'object',
|
|
856
|
+
properties: {
|
|
857
|
+
session: { type: 'string', description: 'Session ID' },
|
|
858
|
+
index: { type: 'number', description: 'Tab index to close (current if omitted)' },
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
handler: async (input) => {
|
|
862
|
+
const adapter = getAdapter(input.session as string);
|
|
863
|
+
return adapter.closeTab(input.index as number);
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: 'browser/session-list',
|
|
868
|
+
description: 'List all active browser sessions',
|
|
869
|
+
category: 'browser-session',
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: 'object',
|
|
872
|
+
properties: {},
|
|
873
|
+
},
|
|
874
|
+
handler: async () => {
|
|
875
|
+
const adapter = getAdapter();
|
|
876
|
+
return adapter.listSessions();
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
];
|
|
880
|
+
|
|
881
|
+
// ============================================================================
|
|
882
|
+
// Settings Tools
|
|
883
|
+
// ============================================================================
|
|
884
|
+
|
|
885
|
+
const settingsTools: MCPTool[] = [
|
|
886
|
+
{
|
|
887
|
+
name: 'browser/set-viewport',
|
|
888
|
+
description: 'Set browser viewport size',
|
|
889
|
+
category: 'browser-settings',
|
|
890
|
+
inputSchema: {
|
|
891
|
+
type: 'object',
|
|
892
|
+
properties: {
|
|
893
|
+
session: { type: 'string', description: 'Session ID' },
|
|
894
|
+
width: { type: 'number', description: 'Viewport width' },
|
|
895
|
+
height: { type: 'number', description: 'Viewport height' },
|
|
896
|
+
},
|
|
897
|
+
required: ['width', 'height'],
|
|
898
|
+
},
|
|
899
|
+
handler: async (input) => {
|
|
900
|
+
const adapter = getAdapter(input.session as string);
|
|
901
|
+
return adapter.setViewport(input.width as number, input.height as number);
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: 'browser/set-device',
|
|
906
|
+
description: 'Emulate a device (iPhone 14, Pixel 5, etc.)',
|
|
907
|
+
category: 'browser-settings',
|
|
908
|
+
inputSchema: {
|
|
909
|
+
type: 'object',
|
|
910
|
+
properties: {
|
|
911
|
+
session: { type: 'string', description: 'Session ID' },
|
|
912
|
+
device: { type: 'string', description: 'Device name (e.g., "iPhone 14", "Pixel 5")' },
|
|
913
|
+
},
|
|
914
|
+
required: ['device'],
|
|
915
|
+
},
|
|
916
|
+
handler: async (input) => {
|
|
917
|
+
const adapter = getAdapter(input.session as string);
|
|
918
|
+
return adapter.setDevice(input.device as string);
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: 'browser/set-geolocation',
|
|
923
|
+
description: 'Set geolocation',
|
|
924
|
+
category: 'browser-settings',
|
|
925
|
+
inputSchema: {
|
|
926
|
+
type: 'object',
|
|
927
|
+
properties: {
|
|
928
|
+
session: { type: 'string', description: 'Session ID' },
|
|
929
|
+
latitude: { type: 'number', description: 'Latitude' },
|
|
930
|
+
longitude: { type: 'number', description: 'Longitude' },
|
|
931
|
+
},
|
|
932
|
+
required: ['latitude', 'longitude'],
|
|
933
|
+
},
|
|
934
|
+
handler: async (input) => {
|
|
935
|
+
const adapter = getAdapter(input.session as string);
|
|
936
|
+
return adapter.setGeolocation(input.latitude as number, input.longitude as number);
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: 'browser/set-offline',
|
|
941
|
+
description: 'Toggle offline mode',
|
|
942
|
+
category: 'browser-settings',
|
|
943
|
+
inputSchema: {
|
|
944
|
+
type: 'object',
|
|
945
|
+
properties: {
|
|
946
|
+
session: { type: 'string', description: 'Session ID' },
|
|
947
|
+
enabled: { type: 'boolean', description: 'Enable offline mode' },
|
|
948
|
+
},
|
|
949
|
+
required: ['enabled'],
|
|
950
|
+
},
|
|
951
|
+
handler: async (input) => {
|
|
952
|
+
const adapter = getAdapter(input.session as string);
|
|
953
|
+
return adapter.setOffline(input.enabled as boolean);
|
|
954
|
+
},
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: 'browser/set-media',
|
|
958
|
+
description: 'Emulate color scheme (dark/light mode)',
|
|
959
|
+
category: 'browser-settings',
|
|
960
|
+
inputSchema: {
|
|
961
|
+
type: 'object',
|
|
962
|
+
properties: {
|
|
963
|
+
session: { type: 'string', description: 'Session ID' },
|
|
964
|
+
scheme: { type: 'string', enum: ['dark', 'light'], description: 'Color scheme' },
|
|
965
|
+
},
|
|
966
|
+
required: ['scheme'],
|
|
967
|
+
},
|
|
968
|
+
handler: async (input) => {
|
|
969
|
+
const adapter = getAdapter(input.session as string);
|
|
970
|
+
return adapter.setMedia(input.scheme as 'dark' | 'light');
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
];
|
|
974
|
+
|
|
975
|
+
// ============================================================================
|
|
976
|
+
// Debug Tools
|
|
977
|
+
// ============================================================================
|
|
978
|
+
|
|
979
|
+
const debugTools: MCPTool[] = [
|
|
980
|
+
{
|
|
981
|
+
name: 'browser/trace-start',
|
|
982
|
+
description: 'Start recording a trace for debugging',
|
|
983
|
+
category: 'browser-debug',
|
|
984
|
+
inputSchema: {
|
|
985
|
+
type: 'object',
|
|
986
|
+
properties: {
|
|
987
|
+
session: { type: 'string', description: 'Session ID' },
|
|
988
|
+
path: { type: 'string', description: 'Path to save trace' },
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
handler: async (input) => {
|
|
992
|
+
const adapter = getAdapter(input.session as string);
|
|
993
|
+
return adapter.traceStart(input.path as string);
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
name: 'browser/trace-stop',
|
|
998
|
+
description: 'Stop recording trace and save',
|
|
999
|
+
category: 'browser-debug',
|
|
1000
|
+
inputSchema: {
|
|
1001
|
+
type: 'object',
|
|
1002
|
+
properties: {
|
|
1003
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1004
|
+
path: { type: 'string', description: 'Path to save trace' },
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
handler: async (input) => {
|
|
1008
|
+
const adapter = getAdapter(input.session as string);
|
|
1009
|
+
return adapter.traceStop(input.path as string);
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
name: 'browser/console',
|
|
1014
|
+
description: 'Get console messages',
|
|
1015
|
+
category: 'browser-debug',
|
|
1016
|
+
inputSchema: {
|
|
1017
|
+
type: 'object',
|
|
1018
|
+
properties: {
|
|
1019
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1020
|
+
clear: { type: 'boolean', description: 'Clear console after getting messages' },
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
handler: async (input) => {
|
|
1024
|
+
const adapter = getAdapter(input.session as string);
|
|
1025
|
+
if (input.clear) {
|
|
1026
|
+
return adapter.clearConsole();
|
|
1027
|
+
}
|
|
1028
|
+
return adapter.getConsole();
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
name: 'browser/errors',
|
|
1033
|
+
description: 'Get page errors',
|
|
1034
|
+
category: 'browser-debug',
|
|
1035
|
+
inputSchema: {
|
|
1036
|
+
type: 'object',
|
|
1037
|
+
properties: {
|
|
1038
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1039
|
+
clear: { type: 'boolean', description: 'Clear errors after getting' },
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
handler: async (input) => {
|
|
1043
|
+
const adapter = getAdapter(input.session as string);
|
|
1044
|
+
if (input.clear) {
|
|
1045
|
+
return adapter.clearErrors();
|
|
1046
|
+
}
|
|
1047
|
+
return adapter.getErrors();
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
name: 'browser/highlight',
|
|
1052
|
+
description: 'Highlight an element on the page (for visual debugging)',
|
|
1053
|
+
category: 'browser-debug',
|
|
1054
|
+
inputSchema: {
|
|
1055
|
+
type: 'object',
|
|
1056
|
+
properties: {
|
|
1057
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1058
|
+
target: { type: 'string', description: 'Element ref (@e1) or CSS selector' },
|
|
1059
|
+
},
|
|
1060
|
+
required: ['target'],
|
|
1061
|
+
},
|
|
1062
|
+
handler: async (input) => {
|
|
1063
|
+
const adapter = getAdapter(input.session as string);
|
|
1064
|
+
return adapter.highlight(input.target as string);
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
name: 'browser/state-save',
|
|
1069
|
+
description: 'Save authentication state (cookies, localStorage) to file',
|
|
1070
|
+
category: 'browser-debug',
|
|
1071
|
+
inputSchema: {
|
|
1072
|
+
type: 'object',
|
|
1073
|
+
properties: {
|
|
1074
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1075
|
+
path: { type: 'string', description: 'Path to save state file' },
|
|
1076
|
+
},
|
|
1077
|
+
required: ['path'],
|
|
1078
|
+
},
|
|
1079
|
+
handler: async (input) => {
|
|
1080
|
+
const adapter = getAdapter(input.session as string);
|
|
1081
|
+
return adapter.saveState(input.path as string);
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
name: 'browser/state-load',
|
|
1086
|
+
description: 'Load authentication state from file',
|
|
1087
|
+
category: 'browser-debug',
|
|
1088
|
+
inputSchema: {
|
|
1089
|
+
type: 'object',
|
|
1090
|
+
properties: {
|
|
1091
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1092
|
+
path: { type: 'string', description: 'Path to state file' },
|
|
1093
|
+
},
|
|
1094
|
+
required: ['path'],
|
|
1095
|
+
},
|
|
1096
|
+
handler: async (input) => {
|
|
1097
|
+
const adapter = getAdapter(input.session as string);
|
|
1098
|
+
return adapter.loadState(input.path as string);
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
];
|
|
1102
|
+
|
|
1103
|
+
// ============================================================================
|
|
1104
|
+
// Semantic Locator Tools (Find Commands)
|
|
1105
|
+
// ============================================================================
|
|
1106
|
+
|
|
1107
|
+
const findTools: MCPTool[] = [
|
|
1108
|
+
{
|
|
1109
|
+
name: 'browser/find-role',
|
|
1110
|
+
description: 'Find element by ARIA role and perform action',
|
|
1111
|
+
category: 'browser-find',
|
|
1112
|
+
inputSchema: {
|
|
1113
|
+
type: 'object',
|
|
1114
|
+
properties: {
|
|
1115
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1116
|
+
role: { type: 'string', description: 'ARIA role (button, link, textbox, etc.)' },
|
|
1117
|
+
action: { type: 'string', enum: ['click', 'fill', 'check', 'hover', 'text'], description: 'Action to perform' },
|
|
1118
|
+
name: { type: 'string', description: 'Accessible name to match' },
|
|
1119
|
+
value: { type: 'string', description: 'Value for fill action' },
|
|
1120
|
+
exact: { type: 'boolean', description: 'Exact text match' },
|
|
1121
|
+
},
|
|
1122
|
+
required: ['role', 'action'],
|
|
1123
|
+
},
|
|
1124
|
+
handler: async (input) => {
|
|
1125
|
+
const adapter = getAdapter(input.session as string);
|
|
1126
|
+
return adapter.findByRole(input.role as string, input.action as string, {
|
|
1127
|
+
name: input.name as string,
|
|
1128
|
+
exact: input.exact as boolean,
|
|
1129
|
+
});
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
name: 'browser/find-text',
|
|
1134
|
+
description: 'Find element by text content and perform action',
|
|
1135
|
+
category: 'browser-find',
|
|
1136
|
+
inputSchema: {
|
|
1137
|
+
type: 'object',
|
|
1138
|
+
properties: {
|
|
1139
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1140
|
+
text: { type: 'string', description: 'Text to find' },
|
|
1141
|
+
action: { type: 'string', enum: ['click', 'hover', 'text'], description: 'Action to perform' },
|
|
1142
|
+
},
|
|
1143
|
+
required: ['text', 'action'],
|
|
1144
|
+
},
|
|
1145
|
+
handler: async (input) => {
|
|
1146
|
+
const adapter = getAdapter(input.session as string);
|
|
1147
|
+
return adapter.findByText(input.text as string, input.action as string);
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
name: 'browser/find-label',
|
|
1152
|
+
description: 'Find input by label and perform action',
|
|
1153
|
+
category: 'browser-find',
|
|
1154
|
+
inputSchema: {
|
|
1155
|
+
type: 'object',
|
|
1156
|
+
properties: {
|
|
1157
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1158
|
+
label: { type: 'string', description: 'Label text' },
|
|
1159
|
+
action: { type: 'string', enum: ['click', 'fill', 'check', 'hover', 'text'], description: 'Action to perform' },
|
|
1160
|
+
value: { type: 'string', description: 'Value for fill action' },
|
|
1161
|
+
},
|
|
1162
|
+
required: ['label', 'action'],
|
|
1163
|
+
},
|
|
1164
|
+
handler: async (input) => {
|
|
1165
|
+
const adapter = getAdapter(input.session as string);
|
|
1166
|
+
return adapter.findByLabel(input.label as string, input.action as string, input.value as string);
|
|
1167
|
+
},
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: 'browser/find-testid',
|
|
1171
|
+
description: 'Find element by data-testid and perform action',
|
|
1172
|
+
category: 'browser-find',
|
|
1173
|
+
inputSchema: {
|
|
1174
|
+
type: 'object',
|
|
1175
|
+
properties: {
|
|
1176
|
+
session: { type: 'string', description: 'Session ID' },
|
|
1177
|
+
testId: { type: 'string', description: 'data-testid value' },
|
|
1178
|
+
action: { type: 'string', enum: ['click', 'fill', 'check', 'hover', 'text'], description: 'Action to perform' },
|
|
1179
|
+
value: { type: 'string', description: 'Value for fill action' },
|
|
1180
|
+
},
|
|
1181
|
+
required: ['testId', 'action'],
|
|
1182
|
+
},
|
|
1183
|
+
handler: async (input) => {
|
|
1184
|
+
const adapter = getAdapter(input.session as string);
|
|
1185
|
+
return adapter.findByTestId(input.testId as string, input.action as string, input.value as string);
|
|
1186
|
+
},
|
|
1187
|
+
},
|
|
1188
|
+
];
|
|
1189
|
+
|
|
1190
|
+
// ============================================================================
|
|
1191
|
+
// Export All Tools
|
|
1192
|
+
// ============================================================================
|
|
1193
|
+
|
|
1194
|
+
export const browserTools: MCPTool[] = [
|
|
1195
|
+
...navigationTools,
|
|
1196
|
+
...snapshotTools,
|
|
1197
|
+
...interactionTools,
|
|
1198
|
+
...getInfoTools,
|
|
1199
|
+
...stateTools,
|
|
1200
|
+
...waitTools,
|
|
1201
|
+
...evalTools,
|
|
1202
|
+
...storageTools,
|
|
1203
|
+
...networkTools,
|
|
1204
|
+
...tabTools,
|
|
1205
|
+
...settingsTools,
|
|
1206
|
+
...debugTools,
|
|
1207
|
+
...findTools,
|
|
1208
|
+
];
|
|
1209
|
+
|
|
1210
|
+
export default browserTools;
|