@everydaydevopsio/pilot 0.6.3 → 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 +5 -0
- package/CHANGELOG.md +16 -0
- package/README.md +19 -2
- 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/types.d.ts +3 -0
- package/dist/browser/types.d.ts.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 +38 -72
- 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/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/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/types.ts +3 -0
- package/src/commands/connect.ts +3 -3
- package/src/mcp/server.ts +48 -91
- package/src/mcp/tools/browser.ts +39 -90
- 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/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/server.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import {
|
|
2
|
+
import { createDriver } from '../browser/driver/factory.js';
|
|
3
|
+
import type { BrowserDriver, DriverSession } from '../browser/driver/types.js';
|
|
3
4
|
import { ElementRefMap } from '../browser/inspect/element-ref.js';
|
|
4
|
-
import { resetCssState } from '../browser/inspect/styles.js';
|
|
5
5
|
import { DialogQueue } from '../browser/interaction/dialogs.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
DownloadTracker,
|
|
9
|
-
enableDownloads,
|
|
10
|
-
attachDownloadHandler
|
|
11
|
-
} from '../browser/interaction/downloads.js';
|
|
6
|
+
import { DownloadTracker } from '../browser/interaction/downloads.js';
|
|
12
7
|
import { NetworkBuffer } from '../browser/network/network-buffer.js';
|
|
13
|
-
import { attachNetworkMonitor } from '../browser/network/network-monitor.js';
|
|
14
8
|
import { loadConfig } from '../util/config.js';
|
|
15
|
-
import { ConsoleBuffer
|
|
9
|
+
import { ConsoleBuffer } from './console-buffer.js';
|
|
16
10
|
import { registerBrowserTools } from './tools/browser.js';
|
|
17
11
|
import { registerErrorTools } from './tools/errors.js';
|
|
18
12
|
import { registerSnapshotTools } from './tools/snapshot.js';
|
|
@@ -24,6 +18,7 @@ import { registerFilesTools } from './tools/files.js';
|
|
|
24
18
|
import { createTraceState } from '../browser/performance/trace.js';
|
|
25
19
|
import { registerPerformanceTools } from './tools/performance.js';
|
|
26
20
|
import { registerConnectTools } from './tools/connect.js';
|
|
21
|
+
import { withCapabilityGate } from './tools/driver-context.js';
|
|
27
22
|
|
|
28
23
|
export interface McpConfig {
|
|
29
24
|
bufferSize: number;
|
|
@@ -31,17 +26,35 @@ export interface McpConfig {
|
|
|
31
26
|
cdpHost?: string;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
export interface BrowserContext {
|
|
35
|
-
|
|
36
|
-
consoleBuffer: ConsoleBuffer;
|
|
37
|
-
elementRefMap: ElementRefMap;
|
|
38
|
-
networkBuffer: NetworkBuffer;
|
|
39
|
-
dialogQueue: DialogQueue;
|
|
40
|
-
downloadTracker: DownloadTracker;
|
|
41
|
-
traceState: ReturnType<typeof createTraceState>;
|
|
29
|
+
export interface BrowserContext extends DriverSession {
|
|
30
|
+
driver: BrowserDriver | null;
|
|
42
31
|
baseConfig: McpConfig;
|
|
43
32
|
}
|
|
44
33
|
|
|
34
|
+
export type DriverFactory = () => BrowserDriver;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register every Pilot tool behind the capability gate, so a tool the active
|
|
38
|
+
* driver cannot support fails before its handler runs.
|
|
39
|
+
*/
|
|
40
|
+
export function registerAllTools(
|
|
41
|
+
server: McpServer,
|
|
42
|
+
context: BrowserContext,
|
|
43
|
+
makeDriver: DriverFactory
|
|
44
|
+
): void {
|
|
45
|
+
const gated = withCapabilityGate(server, () => context.driver);
|
|
46
|
+
registerBrowserTools(gated, context, makeDriver);
|
|
47
|
+
registerErrorTools(gated, context.consoleBuffer);
|
|
48
|
+
registerSnapshotTools(gated, context);
|
|
49
|
+
registerInteractionTools(gated, context);
|
|
50
|
+
registerNetworkTools(gated, context);
|
|
51
|
+
registerRuntimeTools(gated, context.consoleBuffer);
|
|
52
|
+
registerStylesTools(gated, context);
|
|
53
|
+
registerFilesTools(gated, context);
|
|
54
|
+
registerPerformanceTools(gated, context);
|
|
55
|
+
registerConnectTools(gated, context, makeDriver);
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
export async function createMcpServer(config: McpConfig): Promise<{
|
|
46
59
|
server: McpServer;
|
|
47
60
|
cleanup: () => Promise<void>;
|
|
@@ -51,88 +64,32 @@ export async function createMcpServer(config: McpConfig): Promise<{
|
|
|
51
64
|
version: '0.1.0'
|
|
52
65
|
});
|
|
53
66
|
|
|
54
|
-
const consoleBuffer = new ConsoleBuffer(config.bufferSize);
|
|
55
|
-
const elementRefMap = new ElementRefMap();
|
|
56
|
-
const networkBuffer = new NetworkBuffer();
|
|
57
|
-
const dialogQueue = new DialogQueue();
|
|
58
|
-
const downloadTracker = new DownloadTracker();
|
|
59
|
-
const traceState = createTraceState();
|
|
60
|
-
|
|
61
67
|
const context: BrowserContext = {
|
|
62
|
-
|
|
63
|
-
consoleBuffer,
|
|
64
|
-
elementRefMap,
|
|
65
|
-
networkBuffer,
|
|
66
|
-
dialogQueue,
|
|
67
|
-
downloadTracker,
|
|
68
|
-
traceState,
|
|
68
|
+
driver: null,
|
|
69
|
+
consoleBuffer: new ConsoleBuffer(config.bufferSize),
|
|
70
|
+
elementRefMap: new ElementRefMap(),
|
|
71
|
+
networkBuffer: new NetworkBuffer(),
|
|
72
|
+
dialogQueue: new DialogQueue(),
|
|
73
|
+
downloadTracker: new DownloadTracker(),
|
|
74
|
+
traceState: createTraceState(),
|
|
69
75
|
baseConfig: config
|
|
70
76
|
};
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
attachNetworkMonitor(client, networkBuffer);
|
|
79
|
-
attachDialogHandler(client, dialogQueue);
|
|
80
|
-
attachDownloadHandler(client, downloadTracker);
|
|
81
|
-
void enableDownloads(client).catch(() => {
|
|
82
|
-
// Download behavior setup may fail in some environments
|
|
83
|
-
});
|
|
84
|
-
monitoredClient = client;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
manager.setEventCallback((event, data) => {
|
|
89
|
-
if (event === 'console_message') {
|
|
90
|
-
consoleBuffer.push(data as ConsoleMessage);
|
|
91
|
-
}
|
|
92
|
-
if (event === 'page_navigated') {
|
|
93
|
-
elementRefMap.invalidate();
|
|
94
|
-
}
|
|
95
|
-
if (event === 'browser_connected') {
|
|
96
|
-
ensureMonitors();
|
|
97
|
-
}
|
|
98
|
-
if (event === 'browser_disconnected') {
|
|
99
|
-
monitoredClient = null;
|
|
100
|
-
resetCssState();
|
|
101
|
-
dialogQueue.clear();
|
|
102
|
-
downloadTracker.clear();
|
|
103
|
-
}
|
|
104
|
-
// Reattach after tab switch (new client, no connect event)
|
|
105
|
-
ensureMonitors();
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function makeBrowserManager(): BrowserManager {
|
|
110
|
-
const browserConfig = loadConfig({
|
|
111
|
-
cdpPort: config.cdpPort,
|
|
112
|
-
cdpHost: config.cdpHost
|
|
113
|
-
});
|
|
114
|
-
const manager = new BrowserManager(browserConfig);
|
|
115
|
-
attachEventHandlers(manager);
|
|
116
|
-
return manager;
|
|
117
|
-
}
|
|
78
|
+
const makeDriver: DriverFactory = () =>
|
|
79
|
+
createDriver(
|
|
80
|
+
'chrome',
|
|
81
|
+
loadConfig({ cdpPort: config.cdpPort, cdpHost: config.cdpHost }),
|
|
82
|
+
context
|
|
83
|
+
);
|
|
118
84
|
|
|
119
|
-
|
|
120
|
-
registerErrorTools(server, consoleBuffer);
|
|
121
|
-
registerSnapshotTools(server, context);
|
|
122
|
-
registerInteractionTools(server, context);
|
|
123
|
-
registerNetworkTools(server, context);
|
|
124
|
-
registerRuntimeTools(server, consoleBuffer);
|
|
125
|
-
registerStylesTools(server, context);
|
|
126
|
-
registerFilesTools(server, context);
|
|
127
|
-
registerPerformanceTools(server, context);
|
|
128
|
-
registerConnectTools(server, context, makeBrowserManager);
|
|
85
|
+
registerAllTools(server, context, makeDriver);
|
|
129
86
|
|
|
130
87
|
return {
|
|
131
88
|
server,
|
|
132
89
|
cleanup: async () => {
|
|
133
|
-
if (context.
|
|
134
|
-
await context.
|
|
135
|
-
context.
|
|
90
|
+
if (context.driver) {
|
|
91
|
+
await context.driver.destroy();
|
|
92
|
+
context.driver = null;
|
|
136
93
|
}
|
|
137
94
|
}
|
|
138
95
|
};
|
package/src/mcp/tools/browser.ts
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import type {
|
|
4
|
-
import type { BrowserManager } from '../../browser.js';
|
|
5
|
-
import type { BrowserContext } from '../server.js';
|
|
3
|
+
import type { BrowserContext, DriverFactory } from '../server.js';
|
|
6
4
|
import { DEFAULT_STARTUP_TIMEOUT_MS } from '../../browser/chrome-launcher.js';
|
|
7
5
|
import {
|
|
8
6
|
listProfiles,
|
|
9
7
|
pilotDataDir,
|
|
10
8
|
stopPilotBrowsers
|
|
11
9
|
} from '../../browser/sessions.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { executeClick } from '../../commands/click.js';
|
|
15
|
-
import { executeType } from '../../commands/type.js';
|
|
16
|
-
import { executeEvaluate } from '../../commands/evaluate.js';
|
|
17
|
-
import { executeWait } from '../../commands/wait.js';
|
|
18
|
-
import { executePageInfo } from '../../commands/page_info.js';
|
|
19
|
-
import {
|
|
20
|
-
executeListTabs,
|
|
21
|
-
executeNewTab,
|
|
22
|
-
executeCloseTab,
|
|
23
|
-
executeSwitchTab
|
|
24
|
-
} from '../../commands/tabs.js';
|
|
25
|
-
import {
|
|
26
|
-
VIEWPORT_PRESETS,
|
|
27
|
-
applyViewport,
|
|
28
|
-
type ViewportConfig
|
|
29
|
-
} from '../../viewport.js';
|
|
10
|
+
import { VIEWPORT_PRESETS } from '../../viewport.js';
|
|
11
|
+
import { requireDriver } from './driver-context.js';
|
|
30
12
|
|
|
31
13
|
// Raw shapes for MCP SDK (expects ZodRawShape, not ZodObject)
|
|
32
14
|
|
|
@@ -248,22 +230,10 @@ const viewportResizeShape = {
|
|
|
248
230
|
)
|
|
249
231
|
};
|
|
250
232
|
|
|
251
|
-
function requireClient(context: BrowserContext): {
|
|
252
|
-
client: Client;
|
|
253
|
-
manager: BrowserManager;
|
|
254
|
-
} {
|
|
255
|
-
const manager = context.manager;
|
|
256
|
-
if (!manager || !manager.isConnected()) {
|
|
257
|
-
throw new Error('Browser not started. Call browser_start first.');
|
|
258
|
-
}
|
|
259
|
-
const client = manager.getClient()!;
|
|
260
|
-
return { client, manager };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
233
|
export function registerBrowserTools(
|
|
264
234
|
server: McpServer,
|
|
265
235
|
context: BrowserContext,
|
|
266
|
-
|
|
236
|
+
makeDriver: DriverFactory
|
|
267
237
|
): void {
|
|
268
238
|
server.tool(
|
|
269
239
|
'browser_start',
|
|
@@ -280,7 +250,7 @@ export function registerBrowserTools(
|
|
|
280
250
|
responsive,
|
|
281
251
|
startupTimeoutMs
|
|
282
252
|
}) => {
|
|
283
|
-
if (context.
|
|
253
|
+
if (context.driver?.isConnected()) {
|
|
284
254
|
return {
|
|
285
255
|
content: [
|
|
286
256
|
{
|
|
@@ -291,14 +261,14 @@ export function registerBrowserTools(
|
|
|
291
261
|
};
|
|
292
262
|
}
|
|
293
263
|
|
|
294
|
-
if (context.
|
|
295
|
-
await context.
|
|
296
|
-
context.
|
|
264
|
+
if (context.driver) {
|
|
265
|
+
await context.driver.destroy();
|
|
266
|
+
context.driver = null;
|
|
297
267
|
}
|
|
298
268
|
|
|
299
|
-
const
|
|
269
|
+
const driver = makeDriver();
|
|
300
270
|
const { headless: effectiveHeadless, viewport: effectiveViewport } =
|
|
301
|
-
await
|
|
271
|
+
await driver.launch({
|
|
302
272
|
headless,
|
|
303
273
|
chromePath,
|
|
304
274
|
profileName,
|
|
@@ -309,7 +279,7 @@ export function registerBrowserTools(
|
|
|
309
279
|
responsive,
|
|
310
280
|
startupTimeoutMs
|
|
311
281
|
});
|
|
312
|
-
context.
|
|
282
|
+
context.driver = driver;
|
|
313
283
|
|
|
314
284
|
return {
|
|
315
285
|
content: [
|
|
@@ -327,7 +297,7 @@ export function registerBrowserTools(
|
|
|
327
297
|
'Stop the Chrome browser and free all resources.',
|
|
328
298
|
{},
|
|
329
299
|
async () => {
|
|
330
|
-
if (!context.
|
|
300
|
+
if (!context.driver) {
|
|
331
301
|
return {
|
|
332
302
|
content: [
|
|
333
303
|
{
|
|
@@ -338,8 +308,8 @@ export function registerBrowserTools(
|
|
|
338
308
|
};
|
|
339
309
|
}
|
|
340
310
|
|
|
341
|
-
await context.
|
|
342
|
-
context.
|
|
311
|
+
await context.driver.destroy();
|
|
312
|
+
context.driver = null;
|
|
343
313
|
|
|
344
314
|
return {
|
|
345
315
|
content: [{ type: 'text' as const, text: 'Browser stopped.' }]
|
|
@@ -353,7 +323,7 @@ export function registerBrowserTools(
|
|
|
353
323
|
{},
|
|
354
324
|
async () => {
|
|
355
325
|
const status = {
|
|
356
|
-
session: context.
|
|
326
|
+
session: context.driver?.getSessionInfo() ?? {
|
|
357
327
|
mode: 'none',
|
|
358
328
|
connected: false
|
|
359
329
|
},
|
|
@@ -374,10 +344,10 @@ export function registerBrowserTools(
|
|
|
374
344
|
{},
|
|
375
345
|
async () => {
|
|
376
346
|
const lines: string[] = [];
|
|
377
|
-
if (context.
|
|
378
|
-
const info = context.
|
|
379
|
-
await context.
|
|
380
|
-
context.
|
|
347
|
+
if (context.driver) {
|
|
348
|
+
const info = context.driver.getSessionInfo();
|
|
349
|
+
await context.driver.destroy();
|
|
350
|
+
context.driver = null;
|
|
381
351
|
if (info.mode === 'launched') {
|
|
382
352
|
lines.push(
|
|
383
353
|
`this session: stopped Chrome pid ${info.pid} (profile "${info.profile}")`
|
|
@@ -410,8 +380,7 @@ export function registerBrowserTools(
|
|
|
410
380
|
'Capture a screenshot of the current browser viewport or full page',
|
|
411
381
|
screenshotShape,
|
|
412
382
|
async ({ format, quality, fullPage }) => {
|
|
413
|
-
const
|
|
414
|
-
const result = await executeScreenshot(client, {
|
|
383
|
+
const result = await requireDriver(context).screenshot({
|
|
415
384
|
format,
|
|
416
385
|
quality,
|
|
417
386
|
fullPage
|
|
@@ -446,8 +415,7 @@ export function registerBrowserTools(
|
|
|
446
415
|
'Navigate to a URL and wait for page load',
|
|
447
416
|
navigateShape,
|
|
448
417
|
async ({ url, waitUntil, timeoutMs }) => {
|
|
449
|
-
const
|
|
450
|
-
const result = await executeNavigate(client, manager, {
|
|
418
|
+
const result = await requireDriver(context).navigate({
|
|
451
419
|
url,
|
|
452
420
|
waitUntil,
|
|
453
421
|
timeoutMs
|
|
@@ -485,8 +453,7 @@ export function registerBrowserTools(
|
|
|
485
453
|
};
|
|
486
454
|
}
|
|
487
455
|
|
|
488
|
-
const
|
|
489
|
-
const result = await executeClick(client, params, context.elementRefMap);
|
|
456
|
+
const result = await requireDriver(context).click(params);
|
|
490
457
|
|
|
491
458
|
return {
|
|
492
459
|
content: [
|
|
@@ -504,8 +471,7 @@ export function registerBrowserTools(
|
|
|
504
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).',
|
|
505
472
|
typeShape,
|
|
506
473
|
async (params) => {
|
|
507
|
-
|
|
508
|
-
await executeType(client, params, context.elementRefMap);
|
|
474
|
+
await requireDriver(context).type(params);
|
|
509
475
|
|
|
510
476
|
const target = params.ref ?? params.selector;
|
|
511
477
|
return {
|
|
@@ -524,8 +490,7 @@ export function registerBrowserTools(
|
|
|
524
490
|
'Execute JavaScript in the page context and return the result',
|
|
525
491
|
evaluateShape,
|
|
526
492
|
async ({ expression, awaitPromise, timeoutMs }) => {
|
|
527
|
-
const
|
|
528
|
-
const result = await executeEvaluate(client, {
|
|
493
|
+
const result = await requireDriver(context).evaluate({
|
|
529
494
|
expression,
|
|
530
495
|
awaitPromise,
|
|
531
496
|
timeoutMs
|
|
@@ -568,8 +533,7 @@ export function registerBrowserTools(
|
|
|
568
533
|
};
|
|
569
534
|
}
|
|
570
535
|
|
|
571
|
-
const
|
|
572
|
-
const result = await executeWait(client, manager, params);
|
|
536
|
+
const result = await requireDriver(context).wait(params);
|
|
573
537
|
|
|
574
538
|
return {
|
|
575
539
|
content: [
|
|
@@ -587,8 +551,7 @@ export function registerBrowserTools(
|
|
|
587
551
|
'Get information about the current page (URL, title, ready state)',
|
|
588
552
|
{},
|
|
589
553
|
async () => {
|
|
590
|
-
const
|
|
591
|
-
const result = await executePageInfo(client);
|
|
554
|
+
const result = await requireDriver(context).pageInfo();
|
|
592
555
|
|
|
593
556
|
return {
|
|
594
557
|
content: [
|
|
@@ -606,8 +569,7 @@ export function registerBrowserTools(
|
|
|
606
569
|
'List all open browser tabs with their target IDs, URLs, titles, and active status',
|
|
607
570
|
{},
|
|
608
571
|
async () => {
|
|
609
|
-
const
|
|
610
|
-
const tabs = await executeListTabs(manager);
|
|
572
|
+
const tabs = await requireDriver(context).listTabs();
|
|
611
573
|
|
|
612
574
|
return {
|
|
613
575
|
content: [
|
|
@@ -625,8 +587,7 @@ export function registerBrowserTools(
|
|
|
625
587
|
'Open a new browser tab, optionally navigating to a URL',
|
|
626
588
|
newTabShape,
|
|
627
589
|
async ({ url }) => {
|
|
628
|
-
const
|
|
629
|
-
const result = await executeNewTab(manager, url);
|
|
590
|
+
const result = await requireDriver(context).newTab(url);
|
|
630
591
|
|
|
631
592
|
return {
|
|
632
593
|
content: [
|
|
@@ -644,8 +605,7 @@ export function registerBrowserTools(
|
|
|
644
605
|
'Close a browser tab by its target ID. Cannot close the currently active tab.',
|
|
645
606
|
closeTabShape,
|
|
646
607
|
async ({ targetId }) => {
|
|
647
|
-
|
|
648
|
-
await executeCloseTab(manager, targetId);
|
|
608
|
+
await requireDriver(context).closeTab(targetId);
|
|
649
609
|
|
|
650
610
|
return {
|
|
651
611
|
content: [
|
|
@@ -663,8 +623,7 @@ export function registerBrowserTools(
|
|
|
663
623
|
'Switch the active browser tab to the one with the given target ID. All subsequent commands will target this tab.',
|
|
664
624
|
switchTabShape,
|
|
665
625
|
async ({ targetId }) => {
|
|
666
|
-
|
|
667
|
-
await executeSwitchTab(manager, targetId);
|
|
626
|
+
await requireDriver(context).switchTab(targetId);
|
|
668
627
|
|
|
669
628
|
return {
|
|
670
629
|
content: [
|
|
@@ -682,22 +641,12 @@ export function registerBrowserTools(
|
|
|
682
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.',
|
|
683
642
|
viewportResizeShape,
|
|
684
643
|
async ({ width, height, deviceScaleFactor, mobile }) => {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
const current = manager.getViewportConfig();
|
|
688
|
-
const effectiveMobile = mobile ?? current?.mobile ?? false;
|
|
689
|
-
const newConfig: ViewportConfig = {
|
|
644
|
+
const newConfig = await requireDriver(context).resizeViewport({
|
|
690
645
|
width,
|
|
691
646
|
height,
|
|
692
|
-
deviceScaleFactor
|
|
693
|
-
mobile
|
|
694
|
-
|
|
695
|
-
...(effectiveMobile &&
|
|
696
|
-
current?.userAgent && { userAgent: current.userAgent })
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
await applyViewport(client, newConfig);
|
|
700
|
-
manager.setViewportConfig(newConfig);
|
|
647
|
+
deviceScaleFactor,
|
|
648
|
+
mobile
|
|
649
|
+
});
|
|
701
650
|
|
|
702
651
|
return {
|
|
703
652
|
content: [
|
|
@@ -715,11 +664,11 @@ export function registerBrowserTools(
|
|
|
715
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).',
|
|
716
665
|
tabsShape,
|
|
717
666
|
async ({ action, targetId, url }) => {
|
|
718
|
-
const
|
|
667
|
+
const driver = requireDriver(context);
|
|
719
668
|
|
|
720
669
|
switch (action) {
|
|
721
670
|
case 'list': {
|
|
722
|
-
const tabs = await
|
|
671
|
+
const tabs = await driver.listTabs();
|
|
723
672
|
return {
|
|
724
673
|
content: [
|
|
725
674
|
{ type: 'text' as const, text: JSON.stringify(tabs, null, 2) }
|
|
@@ -728,7 +677,7 @@ export function registerBrowserTools(
|
|
|
728
677
|
}
|
|
729
678
|
|
|
730
679
|
case 'new': {
|
|
731
|
-
const result = await
|
|
680
|
+
const result = await driver.newTab(url);
|
|
732
681
|
return {
|
|
733
682
|
content: [
|
|
734
683
|
{
|
|
@@ -751,7 +700,7 @@ export function registerBrowserTools(
|
|
|
751
700
|
isError: true
|
|
752
701
|
};
|
|
753
702
|
}
|
|
754
|
-
await
|
|
703
|
+
await driver.switchTab(targetId);
|
|
755
704
|
return {
|
|
756
705
|
content: [
|
|
757
706
|
{
|
|
@@ -774,7 +723,7 @@ export function registerBrowserTools(
|
|
|
774
723
|
isError: true
|
|
775
724
|
};
|
|
776
725
|
}
|
|
777
|
-
await
|
|
726
|
+
await driver.closeTab(targetId);
|
|
778
727
|
return {
|
|
779
728
|
content: [
|
|
780
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,
|