@darbotlabs/darbot-browser-mcp 0.2.0 → 1.3.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/LICENSE +1 -1
- package/README.md +222 -161
- package/cli.js +1 -1
- package/config.d.ts +77 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/ai/context.js +150 -0
- package/lib/ai/guardrails.js +382 -0
- package/lib/ai/integration.js +397 -0
- package/lib/ai/intent.js +237 -0
- package/lib/ai/manualPromise.js +111 -0
- package/lib/ai/memory.js +273 -0
- package/lib/ai/ml-scorer.js +265 -0
- package/lib/ai/orchestrator-tools.js +292 -0
- package/lib/ai/orchestrator.js +473 -0
- package/lib/ai/planner.js +300 -0
- package/lib/ai/reporter.js +493 -0
- package/lib/ai/workflow.js +407 -0
- package/lib/auth/apiKeyAuth.js +46 -0
- package/lib/auth/entraAuth.js +110 -0
- package/lib/auth/entraJwtVerifier.js +117 -0
- package/lib/auth/index.js +210 -0
- package/lib/auth/managedIdentityAuth.js +175 -0
- package/lib/auth/mcpOAuthProvider.js +186 -0
- package/lib/auth/tunnelAuth.js +120 -0
- package/lib/browserContextFactory.js +1 -1
- package/lib/browserServer.js +1 -1
- package/lib/cdpRelay.js +2 -2
- package/lib/common.js +68 -0
- package/lib/config.js +62 -3
- package/lib/connection.js +1 -1
- package/lib/context.js +1 -1
- package/lib/fileUtils.js +1 -1
- package/lib/guardrails.js +382 -0
- package/lib/health.js +178 -0
- package/lib/httpServer.js +1 -1
- package/lib/index.js +1 -1
- package/lib/javascript.js +1 -1
- package/lib/manualPromise.js +1 -1
- package/lib/memory.js +273 -0
- package/lib/openapi.js +373 -0
- package/lib/orchestrator.js +473 -0
- package/lib/package.js +1 -1
- package/lib/pageSnapshot.js +17 -2
- package/lib/planner.js +302 -0
- package/lib/program.js +17 -5
- package/lib/reporter.js +493 -0
- package/lib/resources/resource.js +1 -1
- package/lib/server.js +5 -3
- package/lib/tab.js +1 -1
- package/lib/tools/ai-native.js +298 -0
- package/lib/tools/autonomous.js +147 -0
- package/lib/tools/clock.js +183 -0
- package/lib/tools/common.js +1 -1
- package/lib/tools/console.js +1 -1
- package/lib/tools/diagnostics.js +132 -0
- package/lib/tools/dialogs.js +1 -1
- package/lib/tools/emulation.js +155 -0
- package/lib/tools/files.js +1 -1
- package/lib/tools/install.js +1 -1
- package/lib/tools/keyboard.js +1 -1
- package/lib/tools/navigate.js +1 -1
- package/lib/tools/network.js +1 -1
- package/lib/tools/pageSnapshot.js +58 -0
- package/lib/tools/pdf.js +1 -1
- package/lib/tools/profiles.js +76 -25
- package/lib/tools/screenshot.js +1 -1
- package/lib/tools/scroll.js +93 -0
- package/lib/tools/snapshot.js +1 -1
- package/lib/tools/storage.js +328 -0
- package/lib/tools/tab.js +16 -0
- package/lib/tools/tabs.js +1 -1
- package/lib/tools/testing.js +1 -1
- package/lib/tools/tool.js +1 -1
- package/lib/tools/utils.js +1 -1
- package/lib/tools/vision.js +1 -1
- package/lib/tools/wait.js +1 -1
- package/lib/tools.js +22 -1
- package/lib/transport.js +251 -31
- package/package.json +28 -22
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
/**
|
|
19
|
+
* Enhanced console with filtering - builds on existing console tool
|
|
20
|
+
*/
|
|
21
|
+
const consoleFiltered = defineTool({
|
|
22
|
+
capability: 'core',
|
|
23
|
+
schema: {
|
|
24
|
+
name: 'browser_console_filtered',
|
|
25
|
+
title: 'Autonomous filtered console',
|
|
26
|
+
description: 'Autonomously retrieve console messages filtered by type (log, error, warning, info, debug). Useful for focused debugging.',
|
|
27
|
+
inputSchema: z.object({
|
|
28
|
+
type: z.enum(['log', 'error', 'warning', 'info', 'debug', 'all']).optional().default('all').describe('Type of console messages to retrieve'),
|
|
29
|
+
limit: z.number().optional().default(100).describe('Maximum number of messages to return'),
|
|
30
|
+
}),
|
|
31
|
+
type: 'readOnly',
|
|
32
|
+
},
|
|
33
|
+
handle: async (context, params) => {
|
|
34
|
+
const tab = context.currentTabOrDie();
|
|
35
|
+
const messages = tab.consoleMessages();
|
|
36
|
+
let filtered = messages;
|
|
37
|
+
if (params.type && params.type !== 'all') {
|
|
38
|
+
filtered = messages.filter(msg => msg.type === params.type);
|
|
39
|
+
}
|
|
40
|
+
// Apply limit
|
|
41
|
+
filtered = filtered.slice(-params.limit);
|
|
42
|
+
const log = filtered.length > 0
|
|
43
|
+
? filtered.map(msg => `[${(msg.type || 'unknown').toUpperCase()}] ${msg.text}`).join('\n')
|
|
44
|
+
: `No ${params.type === 'all' ? '' : params.type + ' '}console messages found.`;
|
|
45
|
+
return {
|
|
46
|
+
code: [`// <internal code to get filtered console messages: type=${params.type}, limit=${params.limit}>`],
|
|
47
|
+
action: async () => {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: 'text', text: log }]
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
captureSnapshot: false,
|
|
53
|
+
waitForNetwork: false,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Performance metrics tool - get performance timing information
|
|
59
|
+
*/
|
|
60
|
+
const performanceMetrics = defineTool({
|
|
61
|
+
capability: 'core',
|
|
62
|
+
schema: {
|
|
63
|
+
name: 'browser_performance_metrics',
|
|
64
|
+
title: 'Autonomous performance analysis',
|
|
65
|
+
description: 'Autonomously retrieve performance metrics including page load times, DOM content loaded, and other timing data.',
|
|
66
|
+
inputSchema: z.object({}),
|
|
67
|
+
type: 'readOnly',
|
|
68
|
+
},
|
|
69
|
+
handle: async (context) => {
|
|
70
|
+
const tab = context.currentTabOrDie();
|
|
71
|
+
const code = [
|
|
72
|
+
`// Get performance metrics`,
|
|
73
|
+
`const metrics = await page.evaluate(() => JSON.stringify(performance.timing));`,
|
|
74
|
+
];
|
|
75
|
+
const action = async () => {
|
|
76
|
+
const metrics = await tab.page.evaluate(() => {
|
|
77
|
+
const timing = performance.timing;
|
|
78
|
+
const navigation = performance.getEntriesByType('navigation')[0];
|
|
79
|
+
return {
|
|
80
|
+
// Core Web Vitals related
|
|
81
|
+
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
|
|
82
|
+
loadComplete: timing.loadEventEnd - timing.navigationStart,
|
|
83
|
+
domInteractive: timing.domInteractive - timing.navigationStart,
|
|
84
|
+
// Network timing
|
|
85
|
+
dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
|
|
86
|
+
tcpConnection: timing.connectEnd - timing.connectStart,
|
|
87
|
+
serverResponse: timing.responseEnd - timing.requestStart,
|
|
88
|
+
// Additional metrics
|
|
89
|
+
firstByte: timing.responseStart - timing.navigationStart,
|
|
90
|
+
domParsing: timing.domComplete - timing.domLoading,
|
|
91
|
+
// Navigation type
|
|
92
|
+
navigationType: navigation?.type || 'unknown',
|
|
93
|
+
redirectCount: navigation?.redirectCount || 0,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const output = [
|
|
97
|
+
'=== Performance Metrics ===',
|
|
98
|
+
'',
|
|
99
|
+
'📊 Core Timings:',
|
|
100
|
+
` • DOM Content Loaded: ${metrics.domContentLoaded}ms`,
|
|
101
|
+
` • Load Complete: ${metrics.loadComplete}ms`,
|
|
102
|
+
` • DOM Interactive: ${metrics.domInteractive}ms`,
|
|
103
|
+
'',
|
|
104
|
+
'🌐 Network Timing:',
|
|
105
|
+
` • DNS Lookup: ${metrics.dnsLookup}ms`,
|
|
106
|
+
` • TCP Connection: ${metrics.tcpConnection}ms`,
|
|
107
|
+
` • Server Response: ${metrics.serverResponse}ms`,
|
|
108
|
+
` • Time to First Byte: ${metrics.firstByte}ms`,
|
|
109
|
+
'',
|
|
110
|
+
'📄 DOM Parsing:',
|
|
111
|
+
` • DOM Parsing Time: ${metrics.domParsing}ms`,
|
|
112
|
+
'',
|
|
113
|
+
'🔄 Navigation:',
|
|
114
|
+
` • Type: ${metrics.navigationType}`,
|
|
115
|
+
` • Redirect Count: ${metrics.redirectCount}`,
|
|
116
|
+
].join('\n');
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: 'text', text: output }]
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
code,
|
|
123
|
+
action,
|
|
124
|
+
captureSnapshot: false,
|
|
125
|
+
waitForNetwork: false,
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
export default [
|
|
130
|
+
consoleFiltered,
|
|
131
|
+
performanceMetrics,
|
|
132
|
+
];
|
package/lib/tools/dialogs.js
CHANGED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
/**
|
|
19
|
+
* Media emulation tools - uses Playwright emulateMedia API
|
|
20
|
+
* Extended support for:
|
|
21
|
+
* - Color scheme emulation (light/dark)
|
|
22
|
+
* - Reduced motion emulation
|
|
23
|
+
* - Contrast emulation (v1.51+)
|
|
24
|
+
* - Print/screen media
|
|
25
|
+
*/
|
|
26
|
+
const emulateMedia = captureSnapshot => defineTool({
|
|
27
|
+
capability: 'core',
|
|
28
|
+
schema: {
|
|
29
|
+
name: 'browser_emulate_media',
|
|
30
|
+
title: 'Autonomous media emulation',
|
|
31
|
+
description: 'Autonomously emulate media features like color scheme, reduced motion, contrast preference, and media type for accessibility and responsive testing.',
|
|
32
|
+
inputSchema: z.object({
|
|
33
|
+
colorScheme: z.enum(['light', 'dark', 'no-preference', 'null']).optional().describe('Emulate color scheme preference: light, dark, no-preference, or null to reset'),
|
|
34
|
+
reducedMotion: z.enum(['reduce', 'no-preference', 'null']).optional().describe('Emulate prefers-reduced-motion: reduce, no-preference, or null to reset'),
|
|
35
|
+
contrast: z.enum(['more', 'less', 'no-preference', 'null']).optional().describe('Emulate prefers-contrast: more, less, no-preference, or null to reset'),
|
|
36
|
+
media: z.enum(['screen', 'print', 'null']).optional().describe('Emulate media type: screen, print, or null to reset'),
|
|
37
|
+
forcedColors: z.enum(['active', 'none', 'null']).optional().describe('Emulate forced-colors: active, none, or null to reset'),
|
|
38
|
+
}),
|
|
39
|
+
type: 'readOnly',
|
|
40
|
+
},
|
|
41
|
+
handle: async (context, params) => {
|
|
42
|
+
const tab = context.currentTabOrDie();
|
|
43
|
+
const options = {};
|
|
44
|
+
if (params.colorScheme !== undefined)
|
|
45
|
+
options.colorScheme = params.colorScheme === 'null' ? null : params.colorScheme;
|
|
46
|
+
if (params.reducedMotion !== undefined)
|
|
47
|
+
options.reducedMotion = params.reducedMotion === 'null' ? null : params.reducedMotion;
|
|
48
|
+
if (params.contrast !== undefined)
|
|
49
|
+
options.contrast = params.contrast === 'null' ? null : params.contrast;
|
|
50
|
+
if (params.media !== undefined)
|
|
51
|
+
options.media = params.media === 'null' ? null : params.media;
|
|
52
|
+
if (params.forcedColors !== undefined)
|
|
53
|
+
options.forcedColors = params.forcedColors === 'null' ? null : params.forcedColors;
|
|
54
|
+
const optionsStr = Object.entries(options)
|
|
55
|
+
.filter(([_, v]) => v !== undefined)
|
|
56
|
+
.map(([k, v]) => `${k}: ${v === null ? 'null' : `'${v}'`}`)
|
|
57
|
+
.join(', ');
|
|
58
|
+
const code = [
|
|
59
|
+
`// Emulate media features: ${optionsStr}`,
|
|
60
|
+
`await page.emulateMedia({ ${optionsStr} });`,
|
|
61
|
+
];
|
|
62
|
+
const action = async () => {
|
|
63
|
+
await tab.page.emulateMedia(options);
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
code,
|
|
67
|
+
action,
|
|
68
|
+
captureSnapshot,
|
|
69
|
+
waitForNetwork: false,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
/**
|
|
74
|
+
* Geolocation emulation
|
|
75
|
+
*/
|
|
76
|
+
const emulateGeolocation = captureSnapshot => defineTool({
|
|
77
|
+
capability: 'core',
|
|
78
|
+
schema: {
|
|
79
|
+
name: 'browser_emulate_geolocation',
|
|
80
|
+
title: 'Autonomous geolocation emulation',
|
|
81
|
+
description: 'Autonomously emulate a geographic location for location-based testing.',
|
|
82
|
+
inputSchema: z.object({
|
|
83
|
+
latitude: z.number().min(-90).max(90).describe('Latitude between -90 and 90'),
|
|
84
|
+
longitude: z.number().min(-180).max(180).describe('Longitude between -180 and 180'),
|
|
85
|
+
accuracy: z.number().optional().describe('Accuracy in meters. Defaults to 0.'),
|
|
86
|
+
}),
|
|
87
|
+
type: 'destructive',
|
|
88
|
+
},
|
|
89
|
+
handle: async (context, params) => {
|
|
90
|
+
const tab = context.currentTabOrDie();
|
|
91
|
+
const browserContext = tab.page.context();
|
|
92
|
+
const code = [
|
|
93
|
+
`// Emulate geolocation: ${params.latitude}, ${params.longitude}`,
|
|
94
|
+
`await context.setGeolocation({ latitude: ${params.latitude}, longitude: ${params.longitude}${params.accuracy ? `, accuracy: ${params.accuracy}` : ''} });`,
|
|
95
|
+
];
|
|
96
|
+
const action = async () => {
|
|
97
|
+
await browserContext.setGeolocation({
|
|
98
|
+
latitude: params.latitude,
|
|
99
|
+
longitude: params.longitude,
|
|
100
|
+
accuracy: params.accuracy,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
code,
|
|
105
|
+
action,
|
|
106
|
+
captureSnapshot,
|
|
107
|
+
waitForNetwork: false,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Timezone emulation
|
|
113
|
+
*/
|
|
114
|
+
const emulateTimezone = captureSnapshot => defineTool({
|
|
115
|
+
capability: 'core',
|
|
116
|
+
schema: {
|
|
117
|
+
name: 'browser_emulate_timezone',
|
|
118
|
+
title: 'Autonomous timezone emulation',
|
|
119
|
+
description: 'Autonomously change the browser timezone for testing time-sensitive features.',
|
|
120
|
+
inputSchema: z.object({
|
|
121
|
+
timezoneId: z.string().describe('Timezone ID (e.g., "America/New_York", "Europe/London", "Asia/Tokyo")'),
|
|
122
|
+
}),
|
|
123
|
+
type: 'destructive',
|
|
124
|
+
},
|
|
125
|
+
handle: async (context, params) => {
|
|
126
|
+
const tab = context.currentTabOrDie();
|
|
127
|
+
const browserContext = tab.page.context();
|
|
128
|
+
const code = [
|
|
129
|
+
`// Emulate timezone: ${params.timezoneId}`,
|
|
130
|
+
`await context.setDefaultTimezone('${params.timezoneId}');`,
|
|
131
|
+
];
|
|
132
|
+
// Note: Playwright doesn't have setDefaultTimezone - we need to use emulateTimezone differently
|
|
133
|
+
// This would need to be set at context creation time, so we'll add JavaScript injection instead
|
|
134
|
+
const action = async () => {
|
|
135
|
+
await browserContext.addInitScript(`
|
|
136
|
+
const originalDateTimeFormat = Intl.DateTimeFormat;
|
|
137
|
+
Intl.DateTimeFormat = function(locale, options = {}) {
|
|
138
|
+
options.timeZone = '${params.timezoneId}';
|
|
139
|
+
return new originalDateTimeFormat(locale, options);
|
|
140
|
+
};
|
|
141
|
+
`);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
code,
|
|
145
|
+
action,
|
|
146
|
+
captureSnapshot,
|
|
147
|
+
waitForNetwork: false,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
export default (captureSnapshot) => [
|
|
152
|
+
emulateMedia(captureSnapshot),
|
|
153
|
+
emulateGeolocation(captureSnapshot),
|
|
154
|
+
emulateTimezone(captureSnapshot),
|
|
155
|
+
];
|
package/lib/tools/files.js
CHANGED
package/lib/tools/install.js
CHANGED
package/lib/tools/keyboard.js
CHANGED
package/lib/tools/navigate.js
CHANGED
package/lib/tools/network.js
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { callOnPageNoTrace } from './utils.js';
|
|
17
|
+
export class PageSnapshot {
|
|
18
|
+
_page;
|
|
19
|
+
_text;
|
|
20
|
+
constructor(page) {
|
|
21
|
+
this._page = page;
|
|
22
|
+
}
|
|
23
|
+
static async create(page) {
|
|
24
|
+
const snapshot = new PageSnapshot(page);
|
|
25
|
+
await snapshot._build();
|
|
26
|
+
return snapshot;
|
|
27
|
+
}
|
|
28
|
+
text() {
|
|
29
|
+
return this._text;
|
|
30
|
+
}
|
|
31
|
+
async _build() {
|
|
32
|
+
const snapshotResult = await callOnPageNoTrace(this._page, page => page._snapshotForAI());
|
|
33
|
+
// Handle both old (string) and new (object with full property) Playwright snapshot formats
|
|
34
|
+
let snapshot;
|
|
35
|
+
if (typeof snapshotResult === 'string') {
|
|
36
|
+
snapshot = snapshotResult;
|
|
37
|
+
}
|
|
38
|
+
else if (snapshotResult && typeof snapshotResult === 'object') {
|
|
39
|
+
// Try different known property names for the snapshot text
|
|
40
|
+
snapshot = snapshotResult.full
|
|
41
|
+
?? snapshotResult.text
|
|
42
|
+
?? snapshotResult.snapshot
|
|
43
|
+
?? JSON.stringify(snapshotResult, null, 2);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
snapshot = String(snapshotResult);
|
|
47
|
+
}
|
|
48
|
+
this._text = [
|
|
49
|
+
`- Page Snapshot`,
|
|
50
|
+
'```yaml',
|
|
51
|
+
snapshot,
|
|
52
|
+
'```',
|
|
53
|
+
].join('\n');
|
|
54
|
+
}
|
|
55
|
+
refLocator(params) {
|
|
56
|
+
return this._page.locator(`aria-ref=${params.ref}`).describe(params.element);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/lib/tools/pdf.js
CHANGED
package/lib/tools/profiles.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
3
|
*
|
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
* you may not use this file except in compliance with the License.
|
|
@@ -20,15 +20,15 @@ import { z } from 'zod';
|
|
|
20
20
|
import { defineTool } from './tool.js';
|
|
21
21
|
import { sanitizeForFilePath } from './utils.js';
|
|
22
22
|
const saveProfileSchema = z.object({
|
|
23
|
-
name: z.string().describe('Name for the
|
|
24
|
-
description: z.string().optional().describe('Optional description for the
|
|
23
|
+
name: z.string().describe('Name for the session state'),
|
|
24
|
+
description: z.string().optional().describe('Optional description for the session state'),
|
|
25
25
|
});
|
|
26
26
|
const switchProfileSchema = z.object({
|
|
27
|
-
name: z.string().describe('Name of the
|
|
27
|
+
name: z.string().describe('Name of the session state to restore'),
|
|
28
28
|
});
|
|
29
29
|
const listProfilesSchema = z.object({});
|
|
30
30
|
const deleteProfileSchema = z.object({
|
|
31
|
-
name: z.string().describe('Name of the
|
|
31
|
+
name: z.string().describe('Name of the session state to delete'),
|
|
32
32
|
});
|
|
33
33
|
async function getProfilesDir() {
|
|
34
34
|
let profilesDir;
|
|
@@ -40,7 +40,7 @@ async function getProfilesDir() {
|
|
|
40
40
|
profilesDir = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
41
41
|
else
|
|
42
42
|
throw new Error('Unsupported platform: ' + process.platform);
|
|
43
|
-
const result = path.join(profilesDir, 'darbot-browser-mcp', '
|
|
43
|
+
const result = path.join(profilesDir, 'darbot-browser-mcp', 'session-states');
|
|
44
44
|
await fs.promises.mkdir(result, { recursive: true });
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
@@ -53,8 +53,27 @@ async function saveCurrentProfile(context, profileName, description) {
|
|
|
53
53
|
const tab = context.currentTabOrDie();
|
|
54
54
|
const url = tab.page.url();
|
|
55
55
|
const title = await tab.title();
|
|
56
|
-
//
|
|
56
|
+
// Detect Edge profile info from environment/config
|
|
57
|
+
const edgeProfile = process.env.DARBOT_EDGE_PROFILE || 'default';
|
|
58
|
+
const edgeProfileEmail = process.env.DARBOT_EDGE_PROFILE_EMAIL || undefined;
|
|
59
|
+
const workspacePath = process.env.VSCODE_WORKSPACE_FOLDER || process.env.PWD || undefined;
|
|
60
|
+
const workspaceName = workspacePath ? path.basename(workspacePath) : undefined;
|
|
61
|
+
// Save session state metadata with unified header
|
|
57
62
|
const profileData = {
|
|
63
|
+
// Unified header
|
|
64
|
+
version: '2.0',
|
|
65
|
+
type: 'darbot-session-state',
|
|
66
|
+
// Edge profile context
|
|
67
|
+
edgeProfile: {
|
|
68
|
+
name: edgeProfile,
|
|
69
|
+
email: edgeProfileEmail,
|
|
70
|
+
},
|
|
71
|
+
// VS Code workspace context (if launched from VS Code)
|
|
72
|
+
workspace: workspacePath ? {
|
|
73
|
+
path: workspacePath,
|
|
74
|
+
name: workspaceName,
|
|
75
|
+
} : undefined,
|
|
76
|
+
// Session state details
|
|
58
77
|
name: profileName,
|
|
59
78
|
description: description || '',
|
|
60
79
|
created: new Date().toISOString(),
|
|
@@ -82,7 +101,7 @@ async function loadProfile(context, profileName) {
|
|
|
82
101
|
await fs.promises.access(profileDir);
|
|
83
102
|
}
|
|
84
103
|
catch {
|
|
85
|
-
throw new Error(`
|
|
104
|
+
throw new Error(`Session state "${profileName}" not found`);
|
|
86
105
|
}
|
|
87
106
|
// Load profile metadata
|
|
88
107
|
const profileDataPath = path.join(profileDir, 'profile.json');
|
|
@@ -95,9 +114,8 @@ async function loadProfile(context, profileName) {
|
|
|
95
114
|
// Create new context with the stored state
|
|
96
115
|
const tab = await context.ensureTab();
|
|
97
116
|
const currentContext = tab.page.context();
|
|
98
|
-
if (currentContext)
|
|
117
|
+
if (currentContext)
|
|
99
118
|
await currentContext.close();
|
|
100
|
-
}
|
|
101
119
|
const newContext = await tab.page.context().browser()?.newContext({
|
|
102
120
|
storageState,
|
|
103
121
|
viewport: null,
|
|
@@ -151,7 +169,7 @@ async function deleteProfile(profileName) {
|
|
|
151
169
|
await fs.promises.access(profileDir);
|
|
152
170
|
}
|
|
153
171
|
catch {
|
|
154
|
-
throw new Error(`
|
|
172
|
+
throw new Error(`Session state "${profileName}" not found`);
|
|
155
173
|
}
|
|
156
174
|
await fs.promises.rm(profileDir, { recursive: true, force: true });
|
|
157
175
|
}
|
|
@@ -159,13 +177,25 @@ export const browserSaveProfile = defineTool({
|
|
|
159
177
|
capability: 'core',
|
|
160
178
|
schema: {
|
|
161
179
|
name: 'browser_save_profile',
|
|
162
|
-
title: '
|
|
163
|
-
description: '
|
|
180
|
+
title: 'Save session state',
|
|
181
|
+
description: 'Save the current browser session state (cookies, localStorage, URL) for later restoration. Includes Edge profile and VS Code workspace context.',
|
|
164
182
|
inputSchema: saveProfileSchema,
|
|
165
183
|
type: 'destructive',
|
|
166
184
|
},
|
|
167
185
|
handle: async (context, { name, description }) => {
|
|
168
186
|
const profileData = await saveCurrentProfile(context, name, description);
|
|
187
|
+
let text = `Session state "${name}" saved successfully.\n\n`;
|
|
188
|
+
text += `### Session State Details\n`;
|
|
189
|
+
text += `- **Name:** ${profileData.name}\n`;
|
|
190
|
+
if (profileData.description)
|
|
191
|
+
text += `- **Description:** ${profileData.description}\n`;
|
|
192
|
+
text += `- **URL:** ${profileData.url}\n`;
|
|
193
|
+
text += `- **Title:** ${profileData.title}\n`;
|
|
194
|
+
text += `- **Created:** ${profileData.created}\n\n`;
|
|
195
|
+
text += `### Context\n`;
|
|
196
|
+
text += `- **Edge Profile:** ${profileData.edgeProfile.name}${profileData.edgeProfile.email ? ` (${profileData.edgeProfile.email})` : ''}\n`;
|
|
197
|
+
if (profileData.workspace)
|
|
198
|
+
text += `- **VS Code Workspace:** ${profileData.workspace.name} (${profileData.workspace.path})\n`;
|
|
169
199
|
return {
|
|
170
200
|
code: [`await browser_save_profile({ name: '${name}', description: '${description || ''}' })`],
|
|
171
201
|
action: async () => ({ content: [] }),
|
|
@@ -174,7 +204,7 @@ export const browserSaveProfile = defineTool({
|
|
|
174
204
|
resultOverride: {
|
|
175
205
|
content: [{
|
|
176
206
|
type: 'text',
|
|
177
|
-
text
|
|
207
|
+
text,
|
|
178
208
|
}],
|
|
179
209
|
},
|
|
180
210
|
};
|
|
@@ -184,13 +214,28 @@ export const browserSwitchProfile = defineTool({
|
|
|
184
214
|
capability: 'core',
|
|
185
215
|
schema: {
|
|
186
216
|
name: 'browser_switch_profile',
|
|
187
|
-
title: '
|
|
188
|
-
description: '
|
|
217
|
+
title: 'Restore session state',
|
|
218
|
+
description: 'Restore a previously saved session state, including cookies, localStorage, and navigate to the saved URL',
|
|
189
219
|
inputSchema: switchProfileSchema,
|
|
190
220
|
type: 'destructive',
|
|
191
221
|
},
|
|
192
222
|
handle: async (context, { name }) => {
|
|
193
223
|
const result = await loadProfile(context, name);
|
|
224
|
+
const pd = result.profileData;
|
|
225
|
+
let text = `Session state "${name}" restored.\n\n`;
|
|
226
|
+
text += `### Session State Details\n`;
|
|
227
|
+
text += `- **Name:** ${pd.name}\n`;
|
|
228
|
+
if (pd.description)
|
|
229
|
+
text += `- **Description:** ${pd.description}\n`;
|
|
230
|
+
text += `- **URL:** ${pd.url}\n`;
|
|
231
|
+
text += `- **Title:** ${pd.title}\n`;
|
|
232
|
+
text += `- **Storage:** ${result.restored ? 'Fully restored' : 'URL only (storage not available)'}\n\n`;
|
|
233
|
+
if (pd.edgeProfile) {
|
|
234
|
+
text += `### Original Context\n`;
|
|
235
|
+
text += `- **Edge Profile:** ${pd.edgeProfile.name}${pd.edgeProfile.email ? ` (${pd.edgeProfile.email})` : ''}\n`;
|
|
236
|
+
}
|
|
237
|
+
if (pd.workspace)
|
|
238
|
+
text += `- **VS Code Workspace:** ${pd.workspace.name}\n`;
|
|
194
239
|
return {
|
|
195
240
|
code: [`await browser_switch_profile({ name: '${name}' })`],
|
|
196
241
|
action: async () => ({ content: [] }),
|
|
@@ -199,7 +244,7 @@ export const browserSwitchProfile = defineTool({
|
|
|
199
244
|
resultOverride: {
|
|
200
245
|
content: [{
|
|
201
246
|
type: 'text',
|
|
202
|
-
text
|
|
247
|
+
text,
|
|
203
248
|
}],
|
|
204
249
|
},
|
|
205
250
|
};
|
|
@@ -209,16 +254,16 @@ export const browserListProfiles = defineTool({
|
|
|
209
254
|
capability: 'core',
|
|
210
255
|
schema: {
|
|
211
256
|
name: 'browser_list_profiles',
|
|
212
|
-
title: '
|
|
213
|
-
description: '
|
|
257
|
+
title: 'List session states',
|
|
258
|
+
description: 'List all saved Darbot session states with their Edge profile context and workspace information',
|
|
214
259
|
inputSchema: listProfilesSchema,
|
|
215
260
|
type: 'readOnly',
|
|
216
261
|
},
|
|
217
262
|
handle: async (context, {}) => {
|
|
218
263
|
const profiles = await listProfiles();
|
|
219
|
-
let text = '### Saved
|
|
264
|
+
let text = '### Saved Darbot Session States\n\n';
|
|
220
265
|
if (profiles.length === 0) {
|
|
221
|
-
text += 'No
|
|
266
|
+
text += 'No session states saved yet. Use the "browser_save_profile" tool to save your current browser session state.';
|
|
222
267
|
}
|
|
223
268
|
else {
|
|
224
269
|
for (const profile of profiles) {
|
|
@@ -227,7 +272,13 @@ export const browserListProfiles = defineTool({
|
|
|
227
272
|
text += `- Description: ${profile.description}\n`;
|
|
228
273
|
text += `- URL: ${profile.url}\n`;
|
|
229
274
|
text += `- Title: ${profile.title}\n`;
|
|
230
|
-
text += `- Created: ${new Date(profile.created).toLocaleString()}\n
|
|
275
|
+
text += `- Created: ${new Date(profile.created).toLocaleString()}\n`;
|
|
276
|
+
// Show Edge profile context if available (v2.0+ session states)
|
|
277
|
+
if (profile.edgeProfile)
|
|
278
|
+
text += `- Edge Profile: ${profile.edgeProfile.name}${profile.edgeProfile.email ? ` (${profile.edgeProfile.email})` : ''}\n`;
|
|
279
|
+
if (profile.workspace)
|
|
280
|
+
text += `- Workspace: ${profile.workspace.name}\n`;
|
|
281
|
+
text += '\n';
|
|
231
282
|
}
|
|
232
283
|
}
|
|
233
284
|
return {
|
|
@@ -248,8 +299,8 @@ export const browserDeleteProfile = defineTool({
|
|
|
248
299
|
capability: 'core',
|
|
249
300
|
schema: {
|
|
250
301
|
name: 'browser_delete_profile',
|
|
251
|
-
title: '
|
|
252
|
-
description: '
|
|
302
|
+
title: 'Delete session state',
|
|
303
|
+
description: 'Permanently delete a saved session state from storage',
|
|
253
304
|
inputSchema: deleteProfileSchema,
|
|
254
305
|
type: 'destructive',
|
|
255
306
|
},
|
|
@@ -263,7 +314,7 @@ export const browserDeleteProfile = defineTool({
|
|
|
263
314
|
resultOverride: {
|
|
264
315
|
content: [{
|
|
265
316
|
type: 'text',
|
|
266
|
-
text: `
|
|
317
|
+
text: `Session state "${name}" deleted successfully.`,
|
|
267
318
|
}],
|
|
268
319
|
},
|
|
269
320
|
};
|
package/lib/tools/screenshot.js
CHANGED