@ejazullah/browser-mcp 0.0.56

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.
Files changed (66) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +860 -0
  3. package/cli.js +19 -0
  4. package/index.d.ts +23 -0
  5. package/index.js +1061 -0
  6. package/lib/auth.js +82 -0
  7. package/lib/browserContextFactory.js +205 -0
  8. package/lib/browserServerBackend.js +125 -0
  9. package/lib/config.js +266 -0
  10. package/lib/context.js +232 -0
  11. package/lib/databaseLogger.js +264 -0
  12. package/lib/extension/cdpRelay.js +346 -0
  13. package/lib/extension/extensionContextFactory.js +56 -0
  14. package/lib/extension/main.js +26 -0
  15. package/lib/fileUtils.js +32 -0
  16. package/lib/httpServer.js +39 -0
  17. package/lib/index.js +39 -0
  18. package/lib/javascript.js +49 -0
  19. package/lib/log.js +21 -0
  20. package/lib/loop/loop.js +69 -0
  21. package/lib/loop/loopClaude.js +152 -0
  22. package/lib/loop/loopOpenAI.js +143 -0
  23. package/lib/loop/main.js +60 -0
  24. package/lib/loopTools/context.js +66 -0
  25. package/lib/loopTools/main.js +49 -0
  26. package/lib/loopTools/perform.js +32 -0
  27. package/lib/loopTools/snapshot.js +29 -0
  28. package/lib/loopTools/tool.js +18 -0
  29. package/lib/manualPromise.js +111 -0
  30. package/lib/mcp/inProcessTransport.js +72 -0
  31. package/lib/mcp/server.js +93 -0
  32. package/lib/mcp/transport.js +217 -0
  33. package/lib/mongoDBLogger.js +252 -0
  34. package/lib/package.js +20 -0
  35. package/lib/program.js +113 -0
  36. package/lib/response.js +172 -0
  37. package/lib/sessionLog.js +156 -0
  38. package/lib/tab.js +266 -0
  39. package/lib/tools/cdp.js +169 -0
  40. package/lib/tools/common.js +55 -0
  41. package/lib/tools/console.js +33 -0
  42. package/lib/tools/dialogs.js +47 -0
  43. package/lib/tools/evaluate.js +53 -0
  44. package/lib/tools/extraction.js +217 -0
  45. package/lib/tools/files.js +44 -0
  46. package/lib/tools/forms.js +180 -0
  47. package/lib/tools/getext.js +99 -0
  48. package/lib/tools/install.js +53 -0
  49. package/lib/tools/interactions.js +191 -0
  50. package/lib/tools/keyboard.js +86 -0
  51. package/lib/tools/mouse.js +99 -0
  52. package/lib/tools/navigate.js +70 -0
  53. package/lib/tools/network.js +41 -0
  54. package/lib/tools/pdf.js +40 -0
  55. package/lib/tools/screenshot.js +75 -0
  56. package/lib/tools/selectors.js +233 -0
  57. package/lib/tools/snapshot.js +169 -0
  58. package/lib/tools/states.js +147 -0
  59. package/lib/tools/tabs.js +87 -0
  60. package/lib/tools/tool.js +33 -0
  61. package/lib/tools/utils.js +74 -0
  62. package/lib/tools/wait.js +56 -0
  63. package/lib/tools.js +64 -0
  64. package/lib/utils.js +26 -0
  65. package/openapi.json +683 -0
  66. package/package.json +92 -0
package/lib/tab.js ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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 { EventEmitter } from 'events';
17
+ import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js';
18
+ import { logUnhandledError } from './log.js';
19
+ import { ManualPromise } from './manualPromise.js';
20
+ export const TabEvents = {
21
+ modalState: 'modalState'
22
+ };
23
+ export class Tab extends EventEmitter {
24
+ context;
25
+ page;
26
+ _lastTitle = 'about:blank';
27
+ _consoleMessages = [];
28
+ _recentConsoleMessages = [];
29
+ _requests = new Map();
30
+ _onPageClose;
31
+ _modalStates = [];
32
+ _downloads = [];
33
+ constructor(context, page, onPageClose) {
34
+ super();
35
+ this.context = context;
36
+ this.page = page;
37
+ this._onPageClose = onPageClose;
38
+ page.on('console', event => this._handleConsoleMessage(messageToConsoleMessage(event)));
39
+ page.on('pageerror', error => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
40
+ page.on('request', request => this._requests.set(request, null));
41
+ page.on('response', response => this._requests.set(response.request(), response));
42
+ page.on('close', () => this._onClose());
43
+ page.on('filechooser', chooser => {
44
+ this.setModalState({
45
+ type: 'fileChooser',
46
+ description: 'File chooser',
47
+ fileChooser: chooser,
48
+ });
49
+ });
50
+ page.on('dialog', dialog => this._dialogShown(dialog));
51
+ page.on('download', download => {
52
+ void this._downloadStarted(download);
53
+ });
54
+ page.setDefaultNavigationTimeout(60000);
55
+ page.setDefaultTimeout(5000);
56
+ page[tabSymbol] = this;
57
+ }
58
+ static forPage(page) {
59
+ return page[tabSymbol];
60
+ }
61
+ modalStates() {
62
+ return this._modalStates;
63
+ }
64
+ setModalState(modalState) {
65
+ this._modalStates.push(modalState);
66
+ this.emit(TabEvents.modalState, modalState);
67
+ }
68
+ clearModalState(modalState) {
69
+ this._modalStates = this._modalStates.filter(state => state !== modalState);
70
+ }
71
+ modalStatesMarkdown() {
72
+ return renderModalStates(this.context, this.modalStates());
73
+ }
74
+ _dialogShown(dialog) {
75
+ this.setModalState({
76
+ type: 'dialog',
77
+ description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
78
+ dialog,
79
+ });
80
+ }
81
+ async _downloadStarted(download) {
82
+ const entry = {
83
+ download,
84
+ finished: false,
85
+ outputFile: await this.context.outputFile(download.suggestedFilename())
86
+ };
87
+ this._downloads.push(entry);
88
+ await download.saveAs(entry.outputFile);
89
+ entry.finished = true;
90
+ }
91
+ _clearCollectedArtifacts() {
92
+ this._consoleMessages.length = 0;
93
+ this._recentConsoleMessages.length = 0;
94
+ this._requests.clear();
95
+ }
96
+ _handleConsoleMessage(message) {
97
+ this._consoleMessages.push(message);
98
+ this._recentConsoleMessages.push(message);
99
+ }
100
+ _onClose() {
101
+ this._clearCollectedArtifacts();
102
+ this._onPageClose(this);
103
+ }
104
+ async updateTitle() {
105
+ await this._raceAgainstModalStates(async () => {
106
+ this._lastTitle = await callOnPageNoTrace(this.page, page => page.title());
107
+ });
108
+ }
109
+ lastTitle() {
110
+ return this._lastTitle;
111
+ }
112
+ isCurrentTab() {
113
+ return this === this.context.currentTab();
114
+ }
115
+ async waitForLoadState(state, options) {
116
+ await callOnPageNoTrace(this.page, page => page.waitForLoadState(state, options).catch(logUnhandledError));
117
+ }
118
+ async navigate(url) {
119
+ this._clearCollectedArtifacts();
120
+ const downloadEvent = callOnPageNoTrace(this.page, page => page.waitForEvent('download').catch(logUnhandledError));
121
+ try {
122
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
123
+ }
124
+ catch (_e) {
125
+ const e = _e;
126
+ const mightBeDownload = e.message.includes('net::ERR_ABORTED') // chromium
127
+ || e.message.includes('Download is starting'); // firefox + webkit
128
+ if (!mightBeDownload)
129
+ throw e;
130
+ // on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit
131
+ const download = await Promise.race([
132
+ downloadEvent,
133
+ new Promise(resolve => setTimeout(resolve, 3000)),
134
+ ]);
135
+ if (!download)
136
+ throw e;
137
+ // Make sure other "download" listeners are notified first.
138
+ await new Promise(resolve => setTimeout(resolve, 500));
139
+ return;
140
+ }
141
+ // Cap load event to 5 seconds, the page is operational at this point.
142
+ await this.waitForLoadState('load', { timeout: 5000 });
143
+ }
144
+ consoleMessages() {
145
+ return this._consoleMessages;
146
+ }
147
+ requests() {
148
+ return this._requests;
149
+ }
150
+ async captureSnapshot() {
151
+ let tabSnapshot;
152
+ const modalStates = await this._raceAgainstModalStates(async () => {
153
+ const snapshot = await this.page._snapshotForAI();
154
+ tabSnapshot = {
155
+ url: this.page.url(),
156
+ title: await this.page.title(),
157
+ ariaSnapshot: snapshot,
158
+ modalStates: [],
159
+ consoleMessages: [],
160
+ downloads: this._downloads,
161
+ };
162
+ });
163
+ if (tabSnapshot) {
164
+ // Assign console message late so that we did not lose any to modal state.
165
+ tabSnapshot.consoleMessages = this._recentConsoleMessages;
166
+ this._recentConsoleMessages = [];
167
+ }
168
+ return tabSnapshot ?? {
169
+ url: this.page.url(),
170
+ title: '',
171
+ ariaSnapshot: '',
172
+ modalStates,
173
+ consoleMessages: [],
174
+ downloads: [],
175
+ };
176
+ }
177
+ _javaScriptBlocked() {
178
+ return this._modalStates.some(state => state.type === 'dialog');
179
+ }
180
+ async _raceAgainstModalStates(action) {
181
+ if (this.modalStates().length)
182
+ return this.modalStates();
183
+ const promise = new ManualPromise();
184
+ const listener = (modalState) => promise.resolve([modalState]);
185
+ this.once(TabEvents.modalState, listener);
186
+ return await Promise.race([
187
+ action().then(() => {
188
+ this.off(TabEvents.modalState, listener);
189
+ return [];
190
+ }),
191
+ promise,
192
+ ]);
193
+ }
194
+ async waitForCompletion(callback) {
195
+ await this._raceAgainstModalStates(() => waitForCompletion(this, callback));
196
+ }
197
+ async refLocator(params) {
198
+ return (await this.refLocators([params]))[0];
199
+ }
200
+ async refLocators(params) {
201
+ const snapshot = await this.page._snapshotForAI();
202
+ return params.map(param => {
203
+ if (!snapshot.includes(`[ref=${param.ref}]`))
204
+ throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);
205
+ return this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
206
+ });
207
+ }
208
+ async refLocatorWithSelector(params) {
209
+ const snapshot = await this.page._snapshotForAI();
210
+ if (!snapshot.includes(`[ref=${params.ref}]`))
211
+ throw new Error(`Ref ${params.ref} not found in the current page snapshot. Try capturing new snapshot.`);
212
+ const locator = this.page.locator(`aria-ref=${params.ref}`).describe(params.element);
213
+ const selector = `aria-ref=${params.ref}`;
214
+ // Try to get the resolved selector (CSS/XPath)
215
+ let resolvedSelector;
216
+ try {
217
+ const selectorInfo = await locator._resolveSelector();
218
+ resolvedSelector = selectorInfo.resolvedSelector;
219
+ }
220
+ catch (e) {
221
+ // Ignore if we can't resolve the selector
222
+ }
223
+ return { locator, selector, resolvedSelector };
224
+ }
225
+ async waitForTimeout(time) {
226
+ if (this._javaScriptBlocked()) {
227
+ await new Promise(f => setTimeout(f, time));
228
+ return;
229
+ }
230
+ await callOnPageNoTrace(this.page, page => {
231
+ return page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
232
+ });
233
+ }
234
+ }
235
+ function messageToConsoleMessage(message) {
236
+ return {
237
+ type: message.type(),
238
+ text: message.text(),
239
+ toString: () => `[${message.type().toUpperCase()}] ${message.text()} @ ${message.location().url}:${message.location().lineNumber}`,
240
+ };
241
+ }
242
+ function pageErrorToConsoleMessage(errorOrValue) {
243
+ if (errorOrValue instanceof Error) {
244
+ return {
245
+ type: undefined,
246
+ text: errorOrValue.message,
247
+ toString: () => errorOrValue.stack || errorOrValue.message,
248
+ };
249
+ }
250
+ return {
251
+ type: undefined,
252
+ text: String(errorOrValue),
253
+ toString: () => String(errorOrValue),
254
+ };
255
+ }
256
+ export function renderModalStates(context, modalStates) {
257
+ const result = ['### Modal state'];
258
+ if (modalStates.length === 0)
259
+ result.push('- There is no modal state present');
260
+ for (const state of modalStates) {
261
+ const tool = context.tools.filter(tool => 'clearsModalState' in tool).find(tool => tool.clearsModalState === state.type);
262
+ result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
263
+ }
264
+ return result;
265
+ }
266
+ const tabSymbol = Symbol('tabSymbol');
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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
+ import * as playwright from 'playwright';
19
+ import { contextFactory } from '../browserContextFactory.js';
20
+ // Connect to a browser via CDP endpoint
21
+ const connectCDP = defineTool({
22
+ capability: 'core',
23
+ schema: {
24
+ name: 'browser_connect_cdp',
25
+ title: 'Connect to browser via CDP',
26
+ description: 'Connect to an existing browser instance using Chrome DevTools Protocol (CDP) endpoint',
27
+ inputSchema: z.object({
28
+ endpoint: z.string().describe('CDP endpoint URL (e.g., "ws://localhost:9222" or "wss://example.com/devtools/sessionid" or "http://localhost:9222")'),
29
+ timeout: z.number().optional().default(30000).describe('Connection timeout in milliseconds'),
30
+ }),
31
+ type: 'destructive',
32
+ },
33
+ handle: async (context, params, response) => {
34
+ try {
35
+ // Ensure endpoint format - don't modify if it already has a valid scheme
36
+ let cdpEndpoint = params.endpoint;
37
+ if (!cdpEndpoint.startsWith('ws://') &&
38
+ !cdpEndpoint.startsWith('wss://') &&
39
+ !cdpEndpoint.startsWith('http://') &&
40
+ !cdpEndpoint.startsWith('https://')) {
41
+ cdpEndpoint = `ws://${cdpEndpoint}`;
42
+ }
43
+ response.addResult(`Connecting to CDP endpoint: ${cdpEndpoint}`);
44
+ // Connect to the browser via CDP
45
+ const browser = await playwright.chromium.connectOverCDP(cdpEndpoint, {
46
+ timeout: params.timeout,
47
+ });
48
+ // Get browser info
49
+ const version = browser.version();
50
+ const contexts = browser.contexts();
51
+ response.addResult(`✅ Successfully connected to browser!`);
52
+ response.addResult(`Browser version: ${version}`);
53
+ response.addResult(`Active contexts: ${contexts.length}`);
54
+ // Update the context configuration to use this CDP connection
55
+ context.config.browser.cdpEndpoint = cdpEndpoint;
56
+ // Switch to CDP mode by updating the browser context factory
57
+ const newContextFactory = contextFactory(context.config);
58
+ await context.updateBrowserContextFactory(newContextFactory);
59
+ response.addCode(`// Browser connected via CDP
60
+ const browser = await playwright.chromium.connectOverCDP('${cdpEndpoint}');
61
+ console.log('Connected to:', browser.version());`);
62
+ // List available pages/tabs
63
+ if (contexts.length > 0) {
64
+ const pages = contexts[0].pages();
65
+ response.addResult(`Available pages: ${pages.length}`);
66
+ for (let i = 0; i < Math.min(pages.length, 5); i++) {
67
+ const page = pages[i];
68
+ const url = page.url();
69
+ const title = await page.title().catch(() => 'Unknown');
70
+ response.addResult(` [${i}] ${title} - ${url}`);
71
+ }
72
+ if (pages.length > 5) {
73
+ response.addResult(` ... and ${pages.length - 5} more pages`);
74
+ }
75
+ }
76
+ }
77
+ catch (error) {
78
+ const errorMessage = error instanceof Error ? error.message : String(error);
79
+ response.addError(`Failed to connect to CDP endpoint: ${errorMessage}`);
80
+ if (errorMessage.includes('ECONNREFUSED')) {
81
+ response.addResult(`\n💡 Troubleshooting tips:`);
82
+ response.addResult(`1. Make sure Chrome/Chromium is running with --remote-debugging-port=9222`);
83
+ response.addResult(`2. Start Chrome with: chrome --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0`);
84
+ response.addResult(`3. Check if the endpoint is accessible: curl ${params.endpoint}/json/version`);
85
+ }
86
+ throw error;
87
+ }
88
+ },
89
+ });
90
+ // Get CDP endpoints from running browsers
91
+ const getCDPEndpoints = defineTool({
92
+ capability: 'core',
93
+ schema: {
94
+ name: 'browser_get_cdp_endpoints',
95
+ title: 'Get available CDP endpoints',
96
+ description: 'Discover available Chrome DevTools Protocol endpoints from running browsers',
97
+ inputSchema: z.object({
98
+ port: z.number().optional().default(9222).describe('CDP port to check (default: 9222)'),
99
+ host: z.string().optional().default('localhost').describe('Host to check (default: localhost)'),
100
+ }),
101
+ type: 'readOnly',
102
+ },
103
+ handle: async (context, params, response) => {
104
+ try {
105
+ const baseUrl = `http://${params.host}:${params.port}`;
106
+ response.addResult(`Checking for CDP endpoints at ${baseUrl}`);
107
+ // Try to fetch the version info
108
+ const versionResponse = await fetch(`${baseUrl}/json/version`);
109
+ if (!versionResponse.ok) {
110
+ throw new Error(`No CDP server found at ${baseUrl}`);
111
+ }
112
+ const versionInfo = await versionResponse.json();
113
+ response.addResult(`✅ Found CDP server!`);
114
+ response.addResult(`Browser: ${versionInfo.Browser}`);
115
+ response.addResult(`User-Agent: ${versionInfo['User-Agent']}`);
116
+ response.addResult(`WebSocket URL: ${versionInfo.webSocketDebuggerUrl}`);
117
+ // Get list of pages/targets
118
+ const pagesResponse = await fetch(`${baseUrl}/json`);
119
+ const pages = await pagesResponse.json();
120
+ response.addResult(`\nAvailable targets: ${pages.length}`);
121
+ pages.forEach((page, index) => {
122
+ response.addResult(`[${index}] ${page.type}: ${page.title || 'Untitled'}`);
123
+ response.addResult(` URL: ${page.url}`);
124
+ response.addResult(` WebSocket: ${page.webSocketDebuggerUrl}`);
125
+ response.addResult('');
126
+ });
127
+ response.addCode(`// To connect to this CDP endpoint:
128
+ await client.callTool({
129
+ name: 'browser_connect_cdp',
130
+ arguments: { endpoint: '${baseUrl}' }
131
+ });`);
132
+ }
133
+ catch (error) {
134
+ const errorMessage = error instanceof Error ? error.message : String(error);
135
+ response.addError(`Failed to discover CDP endpoints: ${errorMessage}`);
136
+ response.addResult(`\n💡 To start a browser with CDP enabled:`);
137
+ response.addResult(`Chrome: google-chrome --remote-debugging-port=${params.port} --remote-debugging-address=0.0.0.0`);
138
+ response.addResult(`Chromium: chromium --remote-debugging-port=${params.port} --remote-debugging-address=0.0.0.0`);
139
+ response.addResult(`Edge: msedge --remote-debugging-port=${params.port} --remote-debugging-address=0.0.0.0`);
140
+ }
141
+ },
142
+ });
143
+ // Disconnect from CDP
144
+ const disconnectCDP = defineTool({
145
+ capability: 'core',
146
+ schema: {
147
+ name: 'browser_disconnect_cdp',
148
+ title: 'Disconnect from CDP',
149
+ description: 'Disconnect from the current CDP connection and return to normal browser mode',
150
+ inputSchema: z.object({}),
151
+ type: 'destructive',
152
+ },
153
+ handle: async (context, params, response) => {
154
+ if (!context.config.browser.cdpEndpoint) {
155
+ response.addResult('ℹ️ No active CDP connection to disconnect from');
156
+ return;
157
+ }
158
+ const previousEndpoint = context.config.browser.cdpEndpoint;
159
+ // Clear the CDP endpoint
160
+ context.config.browser.cdpEndpoint = undefined;
161
+ response.addResult(`✅ Disconnected from CDP endpoint: ${previousEndpoint}`);
162
+ response.addResult('Browser will now use normal launch mode for new connections');
163
+ },
164
+ });
165
+ export const cdpTools = [
166
+ connectCDP,
167
+ getCDPEndpoints,
168
+ disconnectCDP,
169
+ ];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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 { defineTabTool, defineTool } from './tool.js';
18
+ const close = defineTool({
19
+ capability: 'core',
20
+ schema: {
21
+ name: 'browser_close',
22
+ title: 'Close browser',
23
+ description: 'Close the page',
24
+ inputSchema: z.object({}),
25
+ type: 'readOnly',
26
+ },
27
+ handle: async (context, params, response) => {
28
+ await context.closeBrowserContext();
29
+ response.setIncludeTabs();
30
+ response.addCode(`await page.close()`);
31
+ },
32
+ });
33
+ const resize = defineTabTool({
34
+ capability: 'core',
35
+ schema: {
36
+ name: 'browser_resize',
37
+ title: 'Resize browser window',
38
+ description: 'Resize the browser window',
39
+ inputSchema: z.object({
40
+ width: z.number().describe('Width of the browser window'),
41
+ height: z.number().describe('Height of the browser window'),
42
+ }),
43
+ type: 'readOnly',
44
+ },
45
+ handle: async (tab, params, response) => {
46
+ response.addCode(`await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`);
47
+ await tab.waitForCompletion(async () => {
48
+ await tab.page.setViewportSize({ width: params.width, height: params.height });
49
+ });
50
+ },
51
+ });
52
+ export default [
53
+ close,
54
+ resize
55
+ ];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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 { defineTabTool } from './tool.js';
18
+ const console = defineTabTool({
19
+ capability: 'core',
20
+ schema: {
21
+ name: 'browser_console_messages',
22
+ title: 'Get console messages',
23
+ description: 'Returns all console messages',
24
+ inputSchema: z.object({}),
25
+ type: 'readOnly',
26
+ },
27
+ handle: async (tab, params, response) => {
28
+ tab.consoleMessages().map(message => response.addResult(message.toString()));
29
+ },
30
+ });
31
+ export default [
32
+ console,
33
+ ];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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 { defineTabTool } from './tool.js';
18
+ const handleDialog = defineTabTool({
19
+ capability: 'core',
20
+ schema: {
21
+ name: 'browser_handle_dialog',
22
+ title: 'Handle a dialog',
23
+ description: 'Handle a dialog',
24
+ inputSchema: z.object({
25
+ accept: z.boolean().describe('Whether to accept the dialog.'),
26
+ promptText: z.string().optional().describe('The text of the prompt in case of a prompt dialog.'),
27
+ }),
28
+ type: 'destructive',
29
+ },
30
+ handle: async (tab, params, response) => {
31
+ response.setIncludeSnapshot();
32
+ const dialogState = tab.modalStates().find(state => state.type === 'dialog');
33
+ if (!dialogState)
34
+ throw new Error('No dialog visible');
35
+ tab.clearModalState(dialogState);
36
+ await tab.waitForCompletion(async () => {
37
+ if (params.accept)
38
+ await dialogState.dialog.accept(params.promptText);
39
+ else
40
+ await dialogState.dialog.dismiss();
41
+ });
42
+ },
43
+ clearsModalState: 'dialog',
44
+ });
45
+ export default [
46
+ handleDialog,
47
+ ];
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
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 { defineTabTool } from './tool.js';
18
+ import * as javascript from '../javascript.js';
19
+ import { generateLocator } from './utils.js';
20
+ const evaluateSchema = z.object({
21
+ function: z.string().describe('() => { /* code */ } or (element) => { /* code */ } when element is provided'),
22
+ element: z.string().optional().describe('Human-readable element description used to obtain permission to interact with the element'),
23
+ ref: z.string().optional().describe('Exact target element reference from the page snapshot'),
24
+ });
25
+ const evaluate = defineTabTool({
26
+ capability: 'core',
27
+ schema: {
28
+ name: 'browser_evaluate',
29
+ title: 'Evaluate JavaScript',
30
+ description: 'Evaluate JavaScript expression on page or element',
31
+ inputSchema: evaluateSchema,
32
+ type: 'destructive',
33
+ },
34
+ handle: async (tab, params, response) => {
35
+ response.setIncludeSnapshot();
36
+ let locator;
37
+ if (params.ref && params.element) {
38
+ locator = await tab.refLocator({ ref: params.ref, element: params.element });
39
+ response.addCode(`await page.${await generateLocator(locator)}.evaluate(${javascript.quote(params.function)});`);
40
+ }
41
+ else {
42
+ response.addCode(`await page.evaluate(${javascript.quote(params.function)});`);
43
+ }
44
+ await tab.waitForCompletion(async () => {
45
+ const receiver = locator ?? tab.page;
46
+ const result = await receiver._evaluateFunction(params.function);
47
+ response.addResult(JSON.stringify(result, null, 2) || 'undefined');
48
+ });
49
+ },
50
+ });
51
+ export default [
52
+ evaluate,
53
+ ];