@everydaydevopsio/pilot 0.6.2 → 0.7.0
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/AGENTS.md +11 -0
- package/CHANGELOG.md +45 -0
- package/README.md +105 -11
- package/dist/browser/browser-manager.d.ts +7 -1
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.js +196 -40
- package/dist/browser/browser-manager.js.map +1 -1
- package/dist/browser/chrome-launcher.d.ts +13 -2
- package/dist/browser/chrome-launcher.d.ts.map +1 -1
- package/dist/browser/chrome-launcher.js +33 -4
- package/dist/browser/chrome-launcher.js.map +1 -1
- package/dist/browser/driver/capabilities.d.ts +22 -0
- package/dist/browser/driver/capabilities.d.ts.map +1 -0
- package/dist/browser/driver/capabilities.js +70 -0
- package/dist/browser/driver/capabilities.js.map +1 -0
- package/dist/browser/driver/cdp-driver.d.ts +77 -0
- package/dist/browser/driver/cdp-driver.d.ts.map +1 -0
- package/dist/browser/driver/cdp-driver.js +190 -0
- package/dist/browser/driver/cdp-driver.js.map +1 -0
- package/dist/browser/driver/factory.d.ts +10 -0
- package/dist/browser/driver/factory.d.ts.map +1 -0
- package/dist/browser/driver/factory.js +23 -0
- package/dist/browser/driver/factory.js.map +1 -0
- package/dist/browser/driver/index.d.ts +6 -0
- package/dist/browser/driver/index.d.ts.map +1 -0
- package/dist/browser/driver/index.js +5 -0
- package/dist/browser/driver/index.js.map +1 -0
- package/dist/browser/driver/types.d.ts +106 -0
- package/dist/browser/driver/types.d.ts.map +1 -0
- package/dist/browser/driver/types.js +25 -0
- package/dist/browser/driver/types.js.map +1 -0
- package/dist/browser/sessions.d.ts +49 -0
- package/dist/browser/sessions.d.ts.map +1 -0
- package/dist/browser/sessions.js +249 -0
- package/dist/browser/sessions.js.map +1 -0
- package/dist/browser/types.d.ts +13 -0
- package/dist/browser/types.d.ts.map +1 -1
- package/dist/cli/skill-template.d.ts +1 -1
- package/dist/cli/skill-template.d.ts.map +1 -1
- package/dist/cli/skill-template.js +1 -1
- package/dist/cli/skill-template.js.map +1 -1
- package/dist/commands/connect.d.ts +2 -2
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +2 -2
- package/dist/commands/connect.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -15
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +32 -74
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/browser.d.ts +2 -3
- package/dist/mcp/tools/browser.d.ts.map +1 -1
- package/dist/mcp/tools/browser.js +85 -70
- package/dist/mcp/tools/browser.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts +2 -3
- package/dist/mcp/tools/connect.d.ts.map +1 -1
- package/dist/mcp/tools/connect.js +9 -9
- package/dist/mcp/tools/connect.js.map +1 -1
- package/dist/mcp/tools/driver-context.d.ts +14 -0
- package/dist/mcp/tools/driver-context.d.ts.map +1 -0
- package/dist/mcp/tools/driver-context.js +43 -0
- package/dist/mcp/tools/driver-context.js.map +1 -0
- package/dist/mcp/tools/files.d.ts.map +1 -1
- package/dist/mcp/tools/files.js +5 -15
- package/dist/mcp/tools/files.js.map +1 -1
- package/dist/mcp/tools/interaction.d.ts.map +1 -1
- package/dist/mcp/tools/interaction.js +7 -26
- package/dist/mcp/tools/interaction.js.map +1 -1
- package/dist/mcp/tools/names.d.ts +1 -1
- package/dist/mcp/tools/names.d.ts.map +1 -1
- package/dist/mcp/tools/names.js +2 -0
- package/dist/mcp/tools/names.js.map +1 -1
- package/dist/mcp/tools/network.d.ts.map +1 -1
- package/dist/mcp/tools/network.js +3 -11
- package/dist/mcp/tools/network.js.map +1 -1
- package/dist/mcp/tools/performance.d.ts.map +1 -1
- package/dist/mcp/tools/performance.js +2 -12
- package/dist/mcp/tools/performance.js.map +1 -1
- package/dist/mcp/tools/snapshot.d.ts.map +1 -1
- package/dist/mcp/tools/snapshot.js +3 -14
- package/dist/mcp/tools/snapshot.js.map +1 -1
- package/dist/mcp/tools/styles.d.ts.map +1 -1
- package/dist/mcp/tools/styles.js +2 -11
- package/dist/mcp/tools/styles.js.map +1 -1
- package/package.json +1 -1
- package/skills/pilot/SKILL.md +9 -0
- package/src/browser/browser-manager.ts +248 -41
- package/src/browser/chrome-launcher.ts +40 -4
- package/src/browser/driver/capabilities.ts +88 -0
- package/src/browser/driver/cdp-driver.ts +260 -0
- package/src/browser/driver/factory.ts +33 -0
- package/src/browser/driver/index.ts +22 -0
- package/src/browser/driver/types.ts +156 -0
- package/src/browser/sessions.ts +299 -0
- package/src/browser/types.ts +14 -0
- package/src/cli/skill-template.ts +1 -1
- package/src/commands/connect.ts +3 -3
- package/src/mcp/server.ts +48 -91
- package/src/mcp/tools/browser.ts +105 -87
- package/src/mcp/tools/connect.ts +10 -11
- package/src/mcp/tools/driver-context.ts +56 -0
- package/src/mcp/tools/files.ts +5 -16
- package/src/mcp/tools/interaction.ts +7 -27
- package/src/mcp/tools/names.ts +2 -0
- package/src/mcp/tools/network.ts +2 -12
- package/src/mcp/tools/performance.ts +2 -13
- package/src/mcp/tools/snapshot.ts +3 -15
- package/src/mcp/tools/styles.ts +2 -12
package/src/mcp/tools/browser.ts
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import type {
|
|
4
|
-
import
|
|
5
|
-
import type { BrowserContext } from '../server.js';
|
|
6
|
-
import { executeScreenshot } from '../../commands/screenshot.js';
|
|
7
|
-
import { executeNavigate } from '../../commands/navigate.js';
|
|
8
|
-
import { executeClick } from '../../commands/click.js';
|
|
9
|
-
import { executeType } from '../../commands/type.js';
|
|
10
|
-
import { executeEvaluate } from '../../commands/evaluate.js';
|
|
11
|
-
import { executeWait } from '../../commands/wait.js';
|
|
12
|
-
import { executePageInfo } from '../../commands/page_info.js';
|
|
3
|
+
import type { BrowserContext, DriverFactory } from '../server.js';
|
|
4
|
+
import { DEFAULT_STARTUP_TIMEOUT_MS } from '../../browser/chrome-launcher.js';
|
|
13
5
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from '../../
|
|
19
|
-
import {
|
|
20
|
-
VIEWPORT_PRESETS,
|
|
21
|
-
applyViewport,
|
|
22
|
-
type ViewportConfig
|
|
23
|
-
} from '../../viewport.js';
|
|
6
|
+
listProfiles,
|
|
7
|
+
pilotDataDir,
|
|
8
|
+
stopPilotBrowsers
|
|
9
|
+
} from '../../browser/sessions.js';
|
|
10
|
+
import { VIEWPORT_PRESETS } from '../../viewport.js';
|
|
11
|
+
import { requireDriver } from './driver-context.js';
|
|
24
12
|
|
|
25
13
|
// Raw shapes for MCP SDK (expects ZodRawShape, not ZodObject)
|
|
26
14
|
|
|
@@ -185,9 +173,9 @@ const startShape = {
|
|
|
185
173
|
.number()
|
|
186
174
|
.int()
|
|
187
175
|
.positive()
|
|
188
|
-
.default(
|
|
176
|
+
.default(DEFAULT_STARTUP_TIMEOUT_MS)
|
|
189
177
|
.describe(
|
|
190
|
-
|
|
178
|
+
`Timeout waiting for Chrome DevTools to become ready, in milliseconds. Defaults to ${DEFAULT_STARTUP_TIMEOUT_MS}. Startup fails immediately if Chrome exits, so raising this only helps when Chrome is slow to start (for example a first run on a Linux server with software rendering).`
|
|
191
179
|
)
|
|
192
180
|
};
|
|
193
181
|
|
|
@@ -242,22 +230,10 @@ const viewportResizeShape = {
|
|
|
242
230
|
)
|
|
243
231
|
};
|
|
244
232
|
|
|
245
|
-
function requireClient(context: BrowserContext): {
|
|
246
|
-
client: Client;
|
|
247
|
-
manager: BrowserManager;
|
|
248
|
-
} {
|
|
249
|
-
const manager = context.manager;
|
|
250
|
-
if (!manager || !manager.isConnected()) {
|
|
251
|
-
throw new Error('Browser not started. Call browser_start first.');
|
|
252
|
-
}
|
|
253
|
-
const client = manager.getClient()!;
|
|
254
|
-
return { client, manager };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
233
|
export function registerBrowserTools(
|
|
258
234
|
server: McpServer,
|
|
259
235
|
context: BrowserContext,
|
|
260
|
-
|
|
236
|
+
makeDriver: DriverFactory
|
|
261
237
|
): void {
|
|
262
238
|
server.tool(
|
|
263
239
|
'browser_start',
|
|
@@ -274,7 +250,7 @@ export function registerBrowserTools(
|
|
|
274
250
|
responsive,
|
|
275
251
|
startupTimeoutMs
|
|
276
252
|
}) => {
|
|
277
|
-
if (context.
|
|
253
|
+
if (context.driver?.isConnected()) {
|
|
278
254
|
return {
|
|
279
255
|
content: [
|
|
280
256
|
{
|
|
@@ -285,14 +261,14 @@ export function registerBrowserTools(
|
|
|
285
261
|
};
|
|
286
262
|
}
|
|
287
263
|
|
|
288
|
-
if (context.
|
|
289
|
-
await context.
|
|
290
|
-
context.
|
|
264
|
+
if (context.driver) {
|
|
265
|
+
await context.driver.destroy();
|
|
266
|
+
context.driver = null;
|
|
291
267
|
}
|
|
292
268
|
|
|
293
|
-
const
|
|
269
|
+
const driver = makeDriver();
|
|
294
270
|
const { headless: effectiveHeadless, viewport: effectiveViewport } =
|
|
295
|
-
await
|
|
271
|
+
await driver.launch({
|
|
296
272
|
headless,
|
|
297
273
|
chromePath,
|
|
298
274
|
profileName,
|
|
@@ -303,7 +279,7 @@ export function registerBrowserTools(
|
|
|
303
279
|
responsive,
|
|
304
280
|
startupTimeoutMs
|
|
305
281
|
});
|
|
306
|
-
context.
|
|
282
|
+
context.driver = driver;
|
|
307
283
|
|
|
308
284
|
return {
|
|
309
285
|
content: [
|
|
@@ -321,14 +297,19 @@ export function registerBrowserTools(
|
|
|
321
297
|
'Stop the Chrome browser and free all resources.',
|
|
322
298
|
{},
|
|
323
299
|
async () => {
|
|
324
|
-
if (!context.
|
|
300
|
+
if (!context.driver) {
|
|
325
301
|
return {
|
|
326
|
-
content: [
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: 'text' as const,
|
|
305
|
+
text: 'Browser is not running. This Pilot session is not tracking a launched or connected browser; a failed browser_start stops the Chrome it launched and reports the outcome in its error.'
|
|
306
|
+
}
|
|
307
|
+
]
|
|
327
308
|
};
|
|
328
309
|
}
|
|
329
310
|
|
|
330
|
-
await context.
|
|
331
|
-
context.
|
|
311
|
+
await context.driver.destroy();
|
|
312
|
+
context.driver = null;
|
|
332
313
|
|
|
333
314
|
return {
|
|
334
315
|
content: [{ type: 'text' as const, text: 'Browser stopped.' }]
|
|
@@ -336,13 +317,70 @@ export function registerBrowserTools(
|
|
|
336
317
|
}
|
|
337
318
|
);
|
|
338
319
|
|
|
320
|
+
server.tool(
|
|
321
|
+
'browser_status',
|
|
322
|
+
'Show the browser tracked by this Pilot session and the lock state of every Pilot profile on this machine (including browsers started by other Pilot sessions). Read-only. Use it to diagnose "profile already in use" errors.',
|
|
323
|
+
{},
|
|
324
|
+
async () => {
|
|
325
|
+
const status = {
|
|
326
|
+
session: context.driver?.getSessionInfo() ?? {
|
|
327
|
+
mode: 'none',
|
|
328
|
+
connected: false
|
|
329
|
+
},
|
|
330
|
+
dataDir: pilotDataDir(),
|
|
331
|
+
profiles: listProfiles()
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
content: [
|
|
335
|
+
{ type: 'text' as const, text: JSON.stringify(status, null, 2) }
|
|
336
|
+
]
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
server.tool(
|
|
342
|
+
'browser_stop_all',
|
|
343
|
+
'Stop every Pilot-managed Chrome on this machine: the browser in this session plus any Chrome holding a Pilot profile lock, including ones started by other Pilot sessions. Only processes whose command line shows Chrome using a Pilot profile directory are stopped; browsers on other hosts and unverifiable locks are skipped. Also removes stale locks. Ask the user before calling this, since it can close browsers other agents are using.',
|
|
344
|
+
{},
|
|
345
|
+
async () => {
|
|
346
|
+
const lines: string[] = [];
|
|
347
|
+
if (context.driver) {
|
|
348
|
+
const info = context.driver.getSessionInfo();
|
|
349
|
+
await context.driver.destroy();
|
|
350
|
+
context.driver = null;
|
|
351
|
+
if (info.mode === 'launched') {
|
|
352
|
+
lines.push(
|
|
353
|
+
`this session: stopped Chrome pid ${info.pid} (profile "${info.profile}")`
|
|
354
|
+
);
|
|
355
|
+
} else if (info.mode === 'external') {
|
|
356
|
+
lines.push(
|
|
357
|
+
`this session: disconnected from ${info.host}:${info.cdpPort} (not stopped; Pilot did not launch it)`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const result of await stopPilotBrowsers()) {
|
|
362
|
+
lines.push(`${result.profile}: ${result.outcome} - ${result.detail}`);
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
content: [
|
|
366
|
+
{
|
|
367
|
+
type: 'text' as const,
|
|
368
|
+
text:
|
|
369
|
+
lines.length > 0
|
|
370
|
+
? lines.join('\n')
|
|
371
|
+
: 'No Pilot-managed browsers were running.'
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
339
378
|
server.tool(
|
|
340
379
|
'browser_screenshot',
|
|
341
380
|
'Capture a screenshot of the current browser viewport or full page',
|
|
342
381
|
screenshotShape,
|
|
343
382
|
async ({ format, quality, fullPage }) => {
|
|
344
|
-
const
|
|
345
|
-
const result = await executeScreenshot(client, {
|
|
383
|
+
const result = await requireDriver(context).screenshot({
|
|
346
384
|
format,
|
|
347
385
|
quality,
|
|
348
386
|
fullPage
|
|
@@ -377,8 +415,7 @@ export function registerBrowserTools(
|
|
|
377
415
|
'Navigate to a URL and wait for page load',
|
|
378
416
|
navigateShape,
|
|
379
417
|
async ({ url, waitUntil, timeoutMs }) => {
|
|
380
|
-
const
|
|
381
|
-
const result = await executeNavigate(client, manager, {
|
|
418
|
+
const result = await requireDriver(context).navigate({
|
|
382
419
|
url,
|
|
383
420
|
waitUntil,
|
|
384
421
|
timeoutMs
|
|
@@ -416,8 +453,7 @@ export function registerBrowserTools(
|
|
|
416
453
|
};
|
|
417
454
|
}
|
|
418
455
|
|
|
419
|
-
const
|
|
420
|
-
const result = await executeClick(client, params, context.elementRefMap);
|
|
456
|
+
const result = await requireDriver(context).click(params);
|
|
421
457
|
|
|
422
458
|
return {
|
|
423
459
|
content: [
|
|
@@ -435,8 +471,7 @@ export function registerBrowserTools(
|
|
|
435
471
|
'Type text into the focused element, a ref (from browser_snapshot), or a CSS selector. Generates keystroke events (use browser_fill to replace a field value entirely).',
|
|
436
472
|
typeShape,
|
|
437
473
|
async (params) => {
|
|
438
|
-
|
|
439
|
-
await executeType(client, params, context.elementRefMap);
|
|
474
|
+
await requireDriver(context).type(params);
|
|
440
475
|
|
|
441
476
|
const target = params.ref ?? params.selector;
|
|
442
477
|
return {
|
|
@@ -455,8 +490,7 @@ export function registerBrowserTools(
|
|
|
455
490
|
'Execute JavaScript in the page context and return the result',
|
|
456
491
|
evaluateShape,
|
|
457
492
|
async ({ expression, awaitPromise, timeoutMs }) => {
|
|
458
|
-
const
|
|
459
|
-
const result = await executeEvaluate(client, {
|
|
493
|
+
const result = await requireDriver(context).evaluate({
|
|
460
494
|
expression,
|
|
461
495
|
awaitPromise,
|
|
462
496
|
timeoutMs
|
|
@@ -499,8 +533,7 @@ export function registerBrowserTools(
|
|
|
499
533
|
};
|
|
500
534
|
}
|
|
501
535
|
|
|
502
|
-
const
|
|
503
|
-
const result = await executeWait(client, manager, params);
|
|
536
|
+
const result = await requireDriver(context).wait(params);
|
|
504
537
|
|
|
505
538
|
return {
|
|
506
539
|
content: [
|
|
@@ -518,8 +551,7 @@ export function registerBrowserTools(
|
|
|
518
551
|
'Get information about the current page (URL, title, ready state)',
|
|
519
552
|
{},
|
|
520
553
|
async () => {
|
|
521
|
-
const
|
|
522
|
-
const result = await executePageInfo(client);
|
|
554
|
+
const result = await requireDriver(context).pageInfo();
|
|
523
555
|
|
|
524
556
|
return {
|
|
525
557
|
content: [
|
|
@@ -537,8 +569,7 @@ export function registerBrowserTools(
|
|
|
537
569
|
'List all open browser tabs with their target IDs, URLs, titles, and active status',
|
|
538
570
|
{},
|
|
539
571
|
async () => {
|
|
540
|
-
const
|
|
541
|
-
const tabs = await executeListTabs(manager);
|
|
572
|
+
const tabs = await requireDriver(context).listTabs();
|
|
542
573
|
|
|
543
574
|
return {
|
|
544
575
|
content: [
|
|
@@ -556,8 +587,7 @@ export function registerBrowserTools(
|
|
|
556
587
|
'Open a new browser tab, optionally navigating to a URL',
|
|
557
588
|
newTabShape,
|
|
558
589
|
async ({ url }) => {
|
|
559
|
-
const
|
|
560
|
-
const result = await executeNewTab(manager, url);
|
|
590
|
+
const result = await requireDriver(context).newTab(url);
|
|
561
591
|
|
|
562
592
|
return {
|
|
563
593
|
content: [
|
|
@@ -575,8 +605,7 @@ export function registerBrowserTools(
|
|
|
575
605
|
'Close a browser tab by its target ID. Cannot close the currently active tab.',
|
|
576
606
|
closeTabShape,
|
|
577
607
|
async ({ targetId }) => {
|
|
578
|
-
|
|
579
|
-
await executeCloseTab(manager, targetId);
|
|
608
|
+
await requireDriver(context).closeTab(targetId);
|
|
580
609
|
|
|
581
610
|
return {
|
|
582
611
|
content: [
|
|
@@ -594,8 +623,7 @@ export function registerBrowserTools(
|
|
|
594
623
|
'Switch the active browser tab to the one with the given target ID. All subsequent commands will target this tab.',
|
|
595
624
|
switchTabShape,
|
|
596
625
|
async ({ targetId }) => {
|
|
597
|
-
|
|
598
|
-
await executeSwitchTab(manager, targetId);
|
|
626
|
+
await requireDriver(context).switchTab(targetId);
|
|
599
627
|
|
|
600
628
|
return {
|
|
601
629
|
content: [
|
|
@@ -613,22 +641,12 @@ export function registerBrowserTools(
|
|
|
613
641
|
'Resize the browser viewport to new dimensions. Updates both the window size and the rendering viewport. Disables responsive mode so the viewport stays locked at the specified size.',
|
|
614
642
|
viewportResizeShape,
|
|
615
643
|
async ({ width, height, deviceScaleFactor, mobile }) => {
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
const current = manager.getViewportConfig();
|
|
619
|
-
const effectiveMobile = mobile ?? current?.mobile ?? false;
|
|
620
|
-
const newConfig: ViewportConfig = {
|
|
644
|
+
const newConfig = await requireDriver(context).resizeViewport({
|
|
621
645
|
width,
|
|
622
646
|
height,
|
|
623
|
-
deviceScaleFactor
|
|
624
|
-
mobile
|
|
625
|
-
|
|
626
|
-
...(effectiveMobile &&
|
|
627
|
-
current?.userAgent && { userAgent: current.userAgent })
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
await applyViewport(client, newConfig);
|
|
631
|
-
manager.setViewportConfig(newConfig);
|
|
647
|
+
deviceScaleFactor,
|
|
648
|
+
mobile
|
|
649
|
+
});
|
|
632
650
|
|
|
633
651
|
return {
|
|
634
652
|
content: [
|
|
@@ -646,11 +664,11 @@ export function registerBrowserTools(
|
|
|
646
664
|
'Manage browser tabs. Actions: "list" shows all tabs, "new" opens a tab (optional url), "select" switches to a tab (requires targetId), "close" closes a tab (requires targetId).',
|
|
647
665
|
tabsShape,
|
|
648
666
|
async ({ action, targetId, url }) => {
|
|
649
|
-
const
|
|
667
|
+
const driver = requireDriver(context);
|
|
650
668
|
|
|
651
669
|
switch (action) {
|
|
652
670
|
case 'list': {
|
|
653
|
-
const tabs = await
|
|
671
|
+
const tabs = await driver.listTabs();
|
|
654
672
|
return {
|
|
655
673
|
content: [
|
|
656
674
|
{ type: 'text' as const, text: JSON.stringify(tabs, null, 2) }
|
|
@@ -659,7 +677,7 @@ export function registerBrowserTools(
|
|
|
659
677
|
}
|
|
660
678
|
|
|
661
679
|
case 'new': {
|
|
662
|
-
const result = await
|
|
680
|
+
const result = await driver.newTab(url);
|
|
663
681
|
return {
|
|
664
682
|
content: [
|
|
665
683
|
{
|
|
@@ -682,7 +700,7 @@ export function registerBrowserTools(
|
|
|
682
700
|
isError: true
|
|
683
701
|
};
|
|
684
702
|
}
|
|
685
|
-
await
|
|
703
|
+
await driver.switchTab(targetId);
|
|
686
704
|
return {
|
|
687
705
|
content: [
|
|
688
706
|
{
|
|
@@ -705,7 +723,7 @@ export function registerBrowserTools(
|
|
|
705
723
|
isError: true
|
|
706
724
|
};
|
|
707
725
|
}
|
|
708
|
-
await
|
|
726
|
+
await driver.closeTab(targetId);
|
|
709
727
|
return {
|
|
710
728
|
content: [
|
|
711
729
|
{
|
package/src/mcp/tools/connect.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import type { BrowserContext } from '../server.js';
|
|
4
|
-
import type { BrowserManager } from '../../browser/browser-manager.js';
|
|
3
|
+
import type { BrowserContext, DriverFactory } from '../server.js';
|
|
5
4
|
import { executeConnect, formatConnectResult } from '../../commands/connect.js';
|
|
6
5
|
|
|
7
6
|
const connectShape = {
|
|
@@ -21,14 +20,14 @@ const connectShape = {
|
|
|
21
20
|
export function registerConnectTools(
|
|
22
21
|
server: McpServer,
|
|
23
22
|
context: BrowserContext,
|
|
24
|
-
|
|
23
|
+
makeDriver: DriverFactory
|
|
25
24
|
): void {
|
|
26
25
|
server.tool(
|
|
27
26
|
'browser_connect',
|
|
28
27
|
'Connect to an existing Chrome instance with remote debugging enabled. Start Chrome with --remote-debugging-port=PORT, then use this tool to connect. Lists available tabs after connecting. Use browser_switch_tab to select a specific tab.',
|
|
29
28
|
connectShape,
|
|
30
29
|
async ({ host, port }) => {
|
|
31
|
-
if (context.
|
|
30
|
+
if (context.driver?.isConnected()) {
|
|
32
31
|
return {
|
|
33
32
|
content: [
|
|
34
33
|
{
|
|
@@ -39,22 +38,22 @@ export function registerConnectTools(
|
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
if (context.
|
|
43
|
-
await context.
|
|
44
|
-
context.
|
|
41
|
+
if (context.driver) {
|
|
42
|
+
await context.driver.destroy();
|
|
43
|
+
context.driver = null;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const
|
|
46
|
+
const driver = makeDriver();
|
|
48
47
|
try {
|
|
49
|
-
const result = await executeConnect(
|
|
50
|
-
context.
|
|
48
|
+
const result = await executeConnect(driver, { host, port });
|
|
49
|
+
context.driver = driver;
|
|
51
50
|
return {
|
|
52
51
|
content: [
|
|
53
52
|
{ type: 'text' as const, text: formatConnectResult(result) }
|
|
54
53
|
]
|
|
55
54
|
};
|
|
56
55
|
} catch (err) {
|
|
57
|
-
await
|
|
56
|
+
await driver.destroy();
|
|
58
57
|
throw err;
|
|
59
58
|
}
|
|
60
59
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import {
|
|
3
|
+
TOOL_CAPABILITIES,
|
|
4
|
+
assertToolSupported
|
|
5
|
+
} from '../../browser/driver/capabilities.js';
|
|
6
|
+
import type { BrowserDriver } from '../../browser/driver/types.js';
|
|
7
|
+
import type { ToolName } from './names.js';
|
|
8
|
+
|
|
9
|
+
/** Return the connected driver or fail with the standard "not started" error. */
|
|
10
|
+
export function requireDriver(context: {
|
|
11
|
+
driver: BrowserDriver | null;
|
|
12
|
+
}): BrowserDriver {
|
|
13
|
+
const driver = context.driver;
|
|
14
|
+
if (!driver || !driver.isConnected()) {
|
|
15
|
+
throw new Error('Browser not started. Call browser_start first.');
|
|
16
|
+
}
|
|
17
|
+
return driver;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isToolName(name: string): name is ToolName {
|
|
21
|
+
return Object.prototype.hasOwnProperty.call(TOOL_CAPABILITIES, name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wrap an McpServer so every registered tool first checks that the active
|
|
26
|
+
* driver (if any) has the capabilities the tool declares in TOOL_CAPABILITIES.
|
|
27
|
+
* Registering a tool without a capability declaration throws, so new tools
|
|
28
|
+
* cannot skip the check.
|
|
29
|
+
*/
|
|
30
|
+
export function withCapabilityGate(
|
|
31
|
+
server: McpServer,
|
|
32
|
+
getDriver: () => BrowserDriver | null
|
|
33
|
+
): McpServer {
|
|
34
|
+
return new Proxy(server, {
|
|
35
|
+
get(target, prop, receiver) {
|
|
36
|
+
if (prop !== 'tool') return Reflect.get(target, prop, receiver);
|
|
37
|
+
return (name: string, ...rest: unknown[]) => {
|
|
38
|
+
if (!isToolName(name)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Tool ${name} has no entry in TOOL_CAPABILITIES; declare the driver capabilities it needs.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const last = rest.length - 1;
|
|
44
|
+
const handler = rest[last];
|
|
45
|
+
if (typeof handler === 'function') {
|
|
46
|
+
rest[last] = async (...args: unknown[]) => {
|
|
47
|
+
const driver = getDriver();
|
|
48
|
+
if (driver) assertToolSupported(name, driver);
|
|
49
|
+
return handler(...args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return (target.tool as (...a: unknown[]) => unknown)(name, ...rest);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
package/src/mcp/tools/files.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { BrowserContext } from '../server.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { formatDialogResult } from '../../commands/dialog.js';
|
|
5
|
+
import { requireDriver } from './driver-context.js';
|
|
6
6
|
import {
|
|
7
7
|
executeDownloads,
|
|
8
8
|
formatDownloadsResult
|
|
@@ -49,15 +49,6 @@ const downloadsShape = {
|
|
|
49
49
|
.describe('Timeout for wait action in milliseconds (default: 30000)')
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
function requireContext(context: BrowserContext) {
|
|
53
|
-
const manager = context.manager;
|
|
54
|
-
if (!manager || !manager.isConnected()) {
|
|
55
|
-
throw new Error('Browser not started. Call browser_start first.');
|
|
56
|
-
}
|
|
57
|
-
const client = manager.getClient()!;
|
|
58
|
-
return { client };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
export function registerFilesTools(
|
|
62
53
|
server: McpServer,
|
|
63
54
|
context: BrowserContext
|
|
@@ -67,8 +58,7 @@ export function registerFilesTools(
|
|
|
67
58
|
'Handle browser dialogs (alert, confirm, prompt). Use "list" to see pending dialogs, "accept" to click OK/Yes, or "dismiss" to click Cancel/No. For prompt dialogs, provide promptText with accept.',
|
|
68
59
|
dialogShape,
|
|
69
60
|
async ({ action, promptText }) => {
|
|
70
|
-
const
|
|
71
|
-
const result = await executeDialog(client, context.dialogQueue, {
|
|
61
|
+
const result = await requireDriver(context).dialog({
|
|
72
62
|
action,
|
|
73
63
|
promptText
|
|
74
64
|
});
|
|
@@ -83,8 +73,7 @@ export function registerFilesTools(
|
|
|
83
73
|
'Upload files to a file input element by ref. Files must exist, be regular files, and be under allowed roots (PILOT_UPLOAD_ROOTS env, default: cwd). Use browser_snapshot to find file input refs.',
|
|
84
74
|
uploadShape,
|
|
85
75
|
async ({ ref, files }) => {
|
|
86
|
-
const
|
|
87
|
-
const result = await executeUpload(client, context.elementRefMap, {
|
|
76
|
+
const result = await requireDriver(context).upload({
|
|
88
77
|
ref,
|
|
89
78
|
files
|
|
90
79
|
});
|
|
@@ -104,7 +93,7 @@ export function registerFilesTools(
|
|
|
104
93
|
'Track file downloads. Actions: "list" shows tracked downloads with status/progress; "wait" blocks until a download (by GUID) completes; "clear" resets the tracker.',
|
|
105
94
|
downloadsShape,
|
|
106
95
|
async ({ action, guid, timeoutMs }) => {
|
|
107
|
-
|
|
96
|
+
requireDriver(context);
|
|
108
97
|
const result = await executeDownloads(context.downloadTracker, {
|
|
109
98
|
action,
|
|
110
99
|
guid,
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { BrowserContext } from '../server.js';
|
|
4
|
-
import {
|
|
5
|
-
import { executeHover } from '../../commands/hover.js';
|
|
6
|
-
import { executePressKey } from '../../commands/press_key.js';
|
|
7
|
-
import { executeSelect } from '../../commands/select.js';
|
|
8
|
-
import { executeCheck } from '../../commands/check.js';
|
|
9
|
-
import { executeScroll } from '../../commands/scroll.js';
|
|
10
|
-
|
|
11
|
-
function requireContext(context: BrowserContext) {
|
|
12
|
-
const manager = context.manager;
|
|
13
|
-
if (!manager || !manager.isConnected()) {
|
|
14
|
-
throw new Error('Browser not started. Call browser_start first.');
|
|
15
|
-
}
|
|
16
|
-
const client = manager.getClient()!;
|
|
17
|
-
return { client, refMap: context.elementRefMap };
|
|
18
|
-
}
|
|
4
|
+
import { requireDriver } from './driver-context.js';
|
|
19
5
|
|
|
20
6
|
const fillShape = {
|
|
21
7
|
ref: z.string().describe('Element ref from browser_snapshot (e.g. "e3")'),
|
|
@@ -81,8 +67,7 @@ export function registerInteractionTools(
|
|
|
81
67
|
'Fill a form field by element ref. Replaces the entire field value and fires input/change events compatible with React, Vue, and Svelte. Use browser_snapshot first to get refs.',
|
|
82
68
|
fillShape,
|
|
83
69
|
async ({ ref, value }) => {
|
|
84
|
-
const {
|
|
85
|
-
const result = await executeFill(client, refMap, { ref, value });
|
|
70
|
+
const result = await requireDriver(context).fill({ ref, value });
|
|
86
71
|
return {
|
|
87
72
|
content: [
|
|
88
73
|
{
|
|
@@ -113,8 +98,7 @@ export function registerInteractionTools(
|
|
|
113
98
|
isError: true
|
|
114
99
|
};
|
|
115
100
|
}
|
|
116
|
-
const
|
|
117
|
-
const result = await executeHover(client, refMap, params);
|
|
101
|
+
const result = await requireDriver(context).hover(params);
|
|
118
102
|
return {
|
|
119
103
|
content: [
|
|
120
104
|
{
|
|
@@ -131,8 +115,7 @@ export function registerInteractionTools(
|
|
|
131
115
|
'Press a key or key combination. Supports named keys (Enter, Tab, Escape, etc.) and modifier combos (Control+a, Meta+c, Shift+Enter).',
|
|
132
116
|
pressKeyShape,
|
|
133
117
|
async ({ key }) => {
|
|
134
|
-
const
|
|
135
|
-
const result = await executePressKey(client, { key });
|
|
118
|
+
const result = await requireDriver(context).pressKey({ key });
|
|
136
119
|
const modStr =
|
|
137
120
|
result.modifiers.length > 0
|
|
138
121
|
? ` with modifiers: ${result.modifiers.join('+')}`
|
|
@@ -165,8 +148,7 @@ export function registerInteractionTools(
|
|
|
165
148
|
isError: true
|
|
166
149
|
};
|
|
167
150
|
}
|
|
168
|
-
const
|
|
169
|
-
const result = await executeSelect(client, refMap, params);
|
|
151
|
+
const result = await requireDriver(context).select(params);
|
|
170
152
|
return {
|
|
171
153
|
content: [
|
|
172
154
|
{
|
|
@@ -183,8 +165,7 @@ export function registerInteractionTools(
|
|
|
183
165
|
'Toggle a checkbox or radio button by ref. Optionally set to a specific checked state.',
|
|
184
166
|
checkShape,
|
|
185
167
|
async ({ ref, checked }) => {
|
|
186
|
-
const {
|
|
187
|
-
const result = await executeCheck(client, refMap, { ref, checked });
|
|
168
|
+
const result = await requireDriver(context).check({ ref, checked });
|
|
188
169
|
return {
|
|
189
170
|
content: [
|
|
190
171
|
{
|
|
@@ -217,8 +198,7 @@ export function registerInteractionTools(
|
|
|
217
198
|
isError: true
|
|
218
199
|
};
|
|
219
200
|
}
|
|
220
|
-
const
|
|
221
|
-
const result = await executeScroll(client, refMap, params);
|
|
201
|
+
const result = await requireDriver(context).scroll(params);
|
|
222
202
|
return {
|
|
223
203
|
content: [
|
|
224
204
|
{
|
package/src/mcp/tools/names.ts
CHANGED
package/src/mcp/tools/network.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { BrowserContext } from '../server.js';
|
|
4
|
+
import { requireDriver } from './driver-context.js';
|
|
4
5
|
import {
|
|
5
|
-
executeNetwork,
|
|
6
6
|
formatNetworkList,
|
|
7
7
|
formatNetworkDetail
|
|
8
8
|
} from '../../commands/network.js';
|
|
@@ -48,15 +48,6 @@ const networkShape = {
|
|
|
48
48
|
.describe('Maximum number of results (list only)')
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
function requireContext(context: BrowserContext) {
|
|
52
|
-
const manager = context.manager;
|
|
53
|
-
if (!manager || !manager.isConnected()) {
|
|
54
|
-
throw new Error('Browser not started. Call browser_start first.');
|
|
55
|
-
}
|
|
56
|
-
const client = manager.getClient()!;
|
|
57
|
-
return { client, networkBuffer: context.networkBuffer };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
51
|
export function registerNetworkTools(
|
|
61
52
|
server: McpServer,
|
|
62
53
|
context: BrowserContext
|
|
@@ -66,8 +57,7 @@ export function registerNetworkTools(
|
|
|
66
57
|
'Inspect captured network requests. Actions: "list" shows request summaries with filters (url, method, status, resourceType, failed); "get" returns full detail for a request ID including headers and response body; "clear" resets the buffer.',
|
|
67
58
|
networkShape,
|
|
68
59
|
async (params) => {
|
|
69
|
-
const
|
|
70
|
-
const result = await executeNetwork(client, networkBuffer, params);
|
|
60
|
+
const result = await requireDriver(context).network(params);
|
|
71
61
|
|
|
72
62
|
let text: string;
|
|
73
63
|
if (result.action === 'list') {
|