@ricardodeazambuja/browser-mcp-server 1.0.3 → 1.4.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/CHANGELOG-v1.3.0.md +42 -0
- package/CHANGELOG-v1.4.0.md +8 -0
- package/README.md +271 -45
- package/package.json +11 -10
- package/plugins/.gitkeep +0 -0
- package/src/.gitkeep +0 -0
- package/src/browser.js +152 -0
- package/src/cdp.js +58 -0
- package/src/index.js +126 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/console.js +139 -0
- package/src/tools/docs.js +1611 -0
- package/src/tools/index.js +60 -0
- package/src/tools/info.js +139 -0
- package/src/tools/interaction.js +126 -0
- package/src/tools/keyboard.js +27 -0
- package/src/tools/media.js +264 -0
- package/src/tools/mouse.js +104 -0
- package/src/tools/navigation.js +72 -0
- package/src/tools/network.js +552 -0
- package/src/tools/pages.js +149 -0
- package/src/tools/performance.js +517 -0
- package/src/tools/security.js +470 -0
- package/src/tools/storage.js +467 -0
- package/src/tools/system.js +196 -0
- package/src/utils.js +131 -0
- package/tests/.gitkeep +0 -0
- package/tests/fixtures/.gitkeep +0 -0
- package/tests/fixtures/test-media.html +35 -0
- package/tests/fixtures/test-network.html +48 -0
- package/tests/fixtures/test-performance.html +61 -0
- package/tests/fixtures/test-security.html +33 -0
- package/tests/fixtures/test-storage.html +76 -0
- package/tests/run-all.js +50 -0
- package/{test-browser-automation.js → tests/test-browser-automation.js} +44 -5
- package/{test-mcp.js → tests/test-mcp.js} +9 -4
- package/tests/test-media-tools.js +168 -0
- package/tests/test-network.js +212 -0
- package/tests/test-performance.js +254 -0
- package/tests/test-security.js +203 -0
- package/tests/test-storage.js +192 -0
- package/CHANGELOG-v1.0.2.md +0 -126
- package/browser-mcp-server-playwright.js +0 -792
package/src/browser.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser connection and state management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { debugLog, loadPlaywright, findChromeExecutable } = require('./utils');
|
|
7
|
+
const { resetCDPSession } = require('./cdp');
|
|
8
|
+
|
|
9
|
+
// Browser state
|
|
10
|
+
let browser = null;
|
|
11
|
+
let context = null;
|
|
12
|
+
let page = null;
|
|
13
|
+
let activePageIndex = 0;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Connect to existing Chrome or launch new instance (hybrid mode)
|
|
17
|
+
* @returns {Object} { browser, context, page }
|
|
18
|
+
*/
|
|
19
|
+
async function connectToBrowser() {
|
|
20
|
+
// Check if browser is disconnected or closed
|
|
21
|
+
if (browser && (!browser.isConnected || !browser.isConnected())) {
|
|
22
|
+
debugLog('Browser connection lost, resetting...');
|
|
23
|
+
browser = null;
|
|
24
|
+
context = null;
|
|
25
|
+
page = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!browser) {
|
|
29
|
+
try {
|
|
30
|
+
const pw = loadPlaywright();
|
|
31
|
+
|
|
32
|
+
// STRATEGY 1: Try to connect to existing Chrome (Antigravity mode)
|
|
33
|
+
try {
|
|
34
|
+
debugLog('Attempting to connect to Chrome on port 9222...');
|
|
35
|
+
browser = await pw.chromium.connectOverCDP('http://localhost:9222');
|
|
36
|
+
debugLog('✅ Connected to existing Chrome (Antigravity mode)');
|
|
37
|
+
|
|
38
|
+
const contexts = browser.contexts();
|
|
39
|
+
context = contexts.length > 0 ? contexts[0] : await browser.newContext();
|
|
40
|
+
} catch (connectError) {
|
|
41
|
+
debugLog(`Could not connect to existing Chrome: ${connectError.message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// STRATEGY 2: Launch our own Chrome (Standalone mode)
|
|
45
|
+
if (!browser) {
|
|
46
|
+
debugLog('No existing Chrome found. Launching new instance...');
|
|
47
|
+
|
|
48
|
+
const profileDir = process.env.MCP_BROWSER_PROFILE ||
|
|
49
|
+
`${os.tmpdir()}/chrome-mcp-profile`;
|
|
50
|
+
|
|
51
|
+
debugLog(`Browser profile: ${profileDir}`);
|
|
52
|
+
|
|
53
|
+
const chromeExecutable = findChromeExecutable();
|
|
54
|
+
const launchOptions = {
|
|
55
|
+
headless: false,
|
|
56
|
+
args: [
|
|
57
|
+
'--remote-debugging-port=9222',
|
|
58
|
+
'--no-first-run',
|
|
59
|
+
'--no-default-browser-check',
|
|
60
|
+
'--disable-fre',
|
|
61
|
+
'--disable-features=TranslateUI,OptGuideOnDeviceModel',
|
|
62
|
+
'--disable-sync',
|
|
63
|
+
'--disable-component-update',
|
|
64
|
+
'--disable-background-networking',
|
|
65
|
+
'--disable-breakpad',
|
|
66
|
+
'--disable-background-timer-throttling',
|
|
67
|
+
'--disable-backgrounding-occluded-windows',
|
|
68
|
+
'--disable-renderer-backgrounding'
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (chromeExecutable) {
|
|
73
|
+
debugLog(`Using system Chrome/Chromium: ${chromeExecutable}`);
|
|
74
|
+
launchOptions.executablePath = chromeExecutable;
|
|
75
|
+
} else {
|
|
76
|
+
debugLog('No system Chrome/Chromium found. Attempting to use Playwright Chromium...');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
context = await pw.chromium.launchPersistentContext(profileDir, launchOptions);
|
|
81
|
+
browser = context;
|
|
82
|
+
} catch (launchError) {
|
|
83
|
+
if (!chromeExecutable && launchError.message.includes("Executable doesn't exist")) {
|
|
84
|
+
debugLog('Playwright Chromium not installed and no system Chrome found');
|
|
85
|
+
throw new Error(
|
|
86
|
+
'❌ No Chrome/Chromium browser found!\n\n' +
|
|
87
|
+
'This MCP server needs a Chrome or Chromium browser to work.\n\n' +
|
|
88
|
+
'Option 1 - Install Chrome/Chromium on your system\n' +
|
|
89
|
+
'Option 2 - Install Playwright\'s Chromium: npx playwright install chromium\n' +
|
|
90
|
+
'Option 3 - Use with Antigravity: Open browser via Chrome logo\n'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
throw launchError;
|
|
94
|
+
}
|
|
95
|
+
debugLog('✅ Successfully launched new Chrome instance (Standalone mode)');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
debugLog(`Failed to connect/launch Chrome: ${error.message}`);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Ensure we have a context and page
|
|
105
|
+
if (!context) {
|
|
106
|
+
const contexts = browser.contexts();
|
|
107
|
+
context = contexts.length > 0 ? contexts[0] : await browser.newContext();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pages = context.pages();
|
|
111
|
+
if (pages.length === 0) {
|
|
112
|
+
page = await context.newPage();
|
|
113
|
+
activePageIndex = 0;
|
|
114
|
+
} else {
|
|
115
|
+
if (activePageIndex >= pages.length) activePageIndex = pages.length - 1;
|
|
116
|
+
page = pages[activePageIndex];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { browser, context, page };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get browser state
|
|
124
|
+
*/
|
|
125
|
+
function getBrowserState() {
|
|
126
|
+
return { browser, context, page, activePageIndex };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Set active page index
|
|
131
|
+
*/
|
|
132
|
+
function setActivePageIndex(index) {
|
|
133
|
+
activePageIndex = index;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reset browser state
|
|
138
|
+
*/
|
|
139
|
+
function resetBrowserState() {
|
|
140
|
+
browser = null;
|
|
141
|
+
context = null;
|
|
142
|
+
page = null;
|
|
143
|
+
activePageIndex = 0;
|
|
144
|
+
resetCDPSession();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
connectToBrowser,
|
|
149
|
+
getBrowserState,
|
|
150
|
+
setActivePageIndex,
|
|
151
|
+
resetBrowserState
|
|
152
|
+
};
|
package/src/cdp.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP (Chrome DevTools Protocol) Session Manager
|
|
3
|
+
* Manages CDP session lifecycle for advanced browser automation tools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { debugLog } = require('./utils');
|
|
7
|
+
|
|
8
|
+
// CDP session state
|
|
9
|
+
let cdpSession = null;
|
|
10
|
+
let currentPage = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get or create CDP session for current page
|
|
14
|
+
* Sessions are cached per page instance for efficiency
|
|
15
|
+
* @returns {CDPSession} Active CDP session
|
|
16
|
+
*/
|
|
17
|
+
async function getCDPSession() {
|
|
18
|
+
// Lazy-load to avoid circular dependency with browser.js
|
|
19
|
+
const { connectToBrowser } = require('./browser');
|
|
20
|
+
const { page } = await connectToBrowser();
|
|
21
|
+
|
|
22
|
+
// If page changed or session doesn't exist, create new session
|
|
23
|
+
if (!cdpSession || currentPage !== page) {
|
|
24
|
+
// Detach old session if it exists
|
|
25
|
+
if (cdpSession) {
|
|
26
|
+
try {
|
|
27
|
+
await cdpSession.detach();
|
|
28
|
+
debugLog('Detached old CDP session');
|
|
29
|
+
} catch (e) {
|
|
30
|
+
debugLog(`Failed to detach old CDP session: ${e.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create new session for current page
|
|
35
|
+
currentPage = page;
|
|
36
|
+
cdpSession = await page.context().newCDPSession(page);
|
|
37
|
+
debugLog('Created new CDP session');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return cdpSession;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Reset CDP session state
|
|
45
|
+
* Called when browser state is reset (connection lost, browser restart, etc.)
|
|
46
|
+
*/
|
|
47
|
+
function resetCDPSession() {
|
|
48
|
+
if (cdpSession) {
|
|
49
|
+
debugLog('Resetting CDP session state');
|
|
50
|
+
}
|
|
51
|
+
cdpSession = null;
|
|
52
|
+
currentPage = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
getCDPSession,
|
|
57
|
+
resetCDPSession
|
|
58
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Universal Browser Automation MCP Server (Playwright Edition)
|
|
5
|
+
* Main Entry Point
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const { debugLog, version } = require('./utils');
|
|
10
|
+
const { tools, handlers } = require('./tools');
|
|
11
|
+
const { getBrowserState } = require('./browser');
|
|
12
|
+
|
|
13
|
+
class BrowserMCPServer {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
terminal: false
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
this.init();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
debugLog('Server starting via src/index.js...');
|
|
26
|
+
|
|
27
|
+
// Process stdin lines as MCP requests
|
|
28
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
29
|
+
|
|
30
|
+
// Handle cleanup
|
|
31
|
+
process.on('SIGTERM', () => this.cleanup());
|
|
32
|
+
process.on('SIGINT', () => this.cleanup());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async handleLine(line) {
|
|
36
|
+
let request;
|
|
37
|
+
try {
|
|
38
|
+
debugLog(`Received: ${line.substring(0, 200)}`);
|
|
39
|
+
request = JSON.parse(line);
|
|
40
|
+
|
|
41
|
+
if (request.method === 'initialize') {
|
|
42
|
+
this.handleInitialize(request);
|
|
43
|
+
} else if (request.method === 'notifications/initialized') {
|
|
44
|
+
debugLog('Received initialized notification');
|
|
45
|
+
} else if (request.method === 'tools/list') {
|
|
46
|
+
this.handleToolsList(request);
|
|
47
|
+
} else if (request.method === 'tools/call') {
|
|
48
|
+
await this.handleToolCall(request);
|
|
49
|
+
} else {
|
|
50
|
+
debugLog(`Unknown method: ${request.method}`);
|
|
51
|
+
this.respond(request.id, null, { code: -32601, message: 'Method not found' });
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
debugLog(`Error processing request: ${error.message}`);
|
|
55
|
+
// Only log to stderr if it's not a JSON parse error (which would break MCP)
|
|
56
|
+
// Actually, stderr is fine, just don't write to stdout unless it's a JSON-RPC response
|
|
57
|
+
const id = request?.id || null;
|
|
58
|
+
this.respond(id, null, { code: -32603, message: error.message });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleInitialize(request) {
|
|
63
|
+
debugLog(`Initialize with protocol: ${request.params.protocolVersion}`);
|
|
64
|
+
this.respond(request.id, {
|
|
65
|
+
protocolVersion: request.params.protocolVersion || '2024-11-05',
|
|
66
|
+
capabilities: { tools: {} },
|
|
67
|
+
serverInfo: {
|
|
68
|
+
name: 'browser-automation-playwright',
|
|
69
|
+
version: version
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleToolsList(request) {
|
|
75
|
+
debugLog('Sending tools list');
|
|
76
|
+
this.respond(request.id, { tools });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async handleToolCall(request) {
|
|
80
|
+
debugLog(`Calling tool: ${request.params.name}`);
|
|
81
|
+
const result = await this.executeTool(request.params.name, request.params.arguments || {});
|
|
82
|
+
this.respond(request.id, result);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async executeTool(name, args) {
|
|
86
|
+
try {
|
|
87
|
+
const handler = handlers[name];
|
|
88
|
+
if (!handler) {
|
|
89
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
90
|
+
}
|
|
91
|
+
return await handler(args);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
debugLog(`Tool execution error (${name}): ${error.message}`);
|
|
94
|
+
return {
|
|
95
|
+
content: [{
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: `❌ Error executing ${name}: ${error.message}`
|
|
98
|
+
}],
|
|
99
|
+
isError: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
respond(id, result, error = null) {
|
|
105
|
+
const response = { jsonrpc: '2.0', id };
|
|
106
|
+
if (error) response.error = error;
|
|
107
|
+
else response.result = result;
|
|
108
|
+
console.log(JSON.stringify(response));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async cleanup() {
|
|
112
|
+
const { browser } = getBrowserState();
|
|
113
|
+
if (browser) {
|
|
114
|
+
debugLog('Closing browser on exit...');
|
|
115
|
+
await browser.close().catch(() => { });
|
|
116
|
+
}
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Start the server if this file is run directly
|
|
122
|
+
if (require.main === module) {
|
|
123
|
+
new BrowserMCPServer();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { BrowserMCPServer };
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const { connectToBrowser } = require('../browser');
|
|
2
|
+
const { debugLog } = require('../utils');
|
|
3
|
+
|
|
4
|
+
// Local state for console tool
|
|
5
|
+
let consoleLogs = [];
|
|
6
|
+
let consoleListening = false;
|
|
7
|
+
|
|
8
|
+
const definitions = [
|
|
9
|
+
{
|
|
10
|
+
name: 'browser_console_start',
|
|
11
|
+
description: 'Start capturing browser console logs (console.log, console.error, console.warn, etc.) (see browser_docs)',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
level: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Optional filter for log level: "log", "error", "warn", "info", "debug", or "all"',
|
|
18
|
+
enum: ['log', 'error', 'warn', 'info', 'debug', 'all']
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'browser_console_get',
|
|
27
|
+
description: 'Get all captured console logs since browser_console_start was called (see browser_docs)',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
filter: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Optional filter by log level: "log", "error", "warn", "info", "debug", or "all"',
|
|
34
|
+
enum: ['log', 'error', 'warn', 'info', 'debug', 'all']
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'browser_console_clear',
|
|
43
|
+
description: 'Clear all captured console logs and stop listening (see browser_docs)',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {},
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const handlers = {
|
|
54
|
+
browser_console_start: async (args) => {
|
|
55
|
+
const { page } = await connectToBrowser();
|
|
56
|
+
if (!consoleListening) {
|
|
57
|
+
page.on('console', msg => {
|
|
58
|
+
const logEntry = {
|
|
59
|
+
type: msg.type(),
|
|
60
|
+
text: msg.text(),
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
location: msg.location()
|
|
63
|
+
};
|
|
64
|
+
consoleLogs.push(logEntry);
|
|
65
|
+
debugLog(`Console [${logEntry.type}]: ${logEntry.text}`);
|
|
66
|
+
});
|
|
67
|
+
consoleListening = true;
|
|
68
|
+
debugLog('Console logging started');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `✅ Console logging started.\n\nCapturing: console.log, console.error, console.warn, console.info, console.debug\n\nUse browser_console_get to retrieve captured logs.`
|
|
74
|
+
}]
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
browser_console_get: async (args) => {
|
|
79
|
+
const filter = args.filter;
|
|
80
|
+
const filtered = filter && filter !== 'all'
|
|
81
|
+
? consoleLogs.filter(log => log.type === filter)
|
|
82
|
+
: consoleLogs;
|
|
83
|
+
|
|
84
|
+
if (filtered.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: consoleListening
|
|
89
|
+
? `No console logs captured yet.\n\n${filter && filter !== 'all' ? `Filter: ${filter}\n` : ''}Console logging is active - logs will appear as the page executes JavaScript.`
|
|
90
|
+
: `Console logging is not active.\n\nUse browser_console_start to begin capturing logs.`
|
|
91
|
+
}]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const logSummary = `📋 Captured ${filtered.length} console log${filtered.length === 1 ? '' : 's'}${filter && filter !== 'all' ? ` (filtered by: ${filter})` : ''}:\n\n`;
|
|
96
|
+
const formattedLogs = filtered.map((log, i) => {
|
|
97
|
+
const icon = {
|
|
98
|
+
'error': '❌',
|
|
99
|
+
'warn': '⚠️',
|
|
100
|
+
'log': '📝',
|
|
101
|
+
'info': 'ℹ️',
|
|
102
|
+
'debug': '🔍'
|
|
103
|
+
}[log.type] || '📄';
|
|
104
|
+
|
|
105
|
+
return `${i + 1}. ${icon} [${log.type.toUpperCase()}] ${log.timestamp}\n ${log.text}${log.location.url ? `\n Location: ${log.location.url}:${log.location.lineNumber}` : ''}`;
|
|
106
|
+
}).join('\n\n');
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: logSummary + formattedLogs
|
|
112
|
+
}]
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
browser_console_clear: async (args) => {
|
|
117
|
+
const { page } = await connectToBrowser();
|
|
118
|
+
const count = consoleLogs.length;
|
|
119
|
+
consoleLogs = [];
|
|
120
|
+
if (consoleListening) {
|
|
121
|
+
// Removing listeners is tricky if we don't store the reference to the specific function we passed
|
|
122
|
+
// But page.removeAllListeners('console') is cleaner if we are the only one using it.
|
|
123
|
+
// In this server context, we likely are.
|
|
124
|
+
if (page) {
|
|
125
|
+
page.removeAllListeners('console');
|
|
126
|
+
}
|
|
127
|
+
consoleListening = false;
|
|
128
|
+
}
|
|
129
|
+
debugLog(`Cleared ${count} console logs and stopped listening`);
|
|
130
|
+
return {
|
|
131
|
+
content: [{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `✅ Cleared ${count} console log${count === 1 ? '' : 's'} and stopped listening.\n\nUse browser_console_start to resume capturing.`
|
|
134
|
+
}]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
module.exports = { definitions, handlers };
|