@ricardodeazambuja/browser-mcp-server 1.0.3
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.0.2.md +126 -0
- package/LICENSE +21 -0
- package/README.md +596 -0
- package/browser-mcp-server-playwright.js +792 -0
- package/package.json +50 -0
- package/test-browser-automation.js +189 -0
- package/test-mcp.js +150 -0
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ricardodeazambuja/browser-mcp-server",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Universal browser automation MCP server using Playwright. Works with Antigravity, Claude Desktop, and any MCP client.",
|
|
5
|
+
"main": "browser-mcp-server-playwright.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"browser-mcp-server": "./browser-mcp-server-playwright.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node browser-mcp-server-playwright.js",
|
|
11
|
+
"install-browsers": "npx playwright install chromium",
|
|
12
|
+
"test": "echo 'Testing MCP server...' && echo '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}' | node browser-mcp-server-playwright.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"mcp-server",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"browser-automation",
|
|
19
|
+
"playwright",
|
|
20
|
+
"claude",
|
|
21
|
+
"antigravity",
|
|
22
|
+
"web-automation",
|
|
23
|
+
"testing",
|
|
24
|
+
"scraping"
|
|
25
|
+
],
|
|
26
|
+
"author": "Ricardo de Azambuja (https://ricardodeazambuja.com)",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/ricardodeazambuja/browser-mcp-server.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/ricardodeazambuja/browser-mcp-server/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/ricardodeazambuja/browser-mcp-server#readme",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=16.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"playwright": "^1.57.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"browser-mcp-server-playwright.js",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"CHANGELOG-v1.0.2.md",
|
|
47
|
+
"test-mcp.js",
|
|
48
|
+
"test-browser-automation.js"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive browser automation test
|
|
5
|
+
* Tests actual browser operations: navigate, evaluate JS, screenshot
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
let requestId = 0;
|
|
13
|
+
let proc;
|
|
14
|
+
|
|
15
|
+
function sendRequest(method, params = {}) {
|
|
16
|
+
const id = ++requestId;
|
|
17
|
+
const request = {
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id,
|
|
20
|
+
method,
|
|
21
|
+
params
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
console.log(`\nโก๏ธ ${method}${params.name ? ` (${params.name})` : ''}`);
|
|
25
|
+
proc.stdin.write(JSON.stringify(request) + '\n');
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function runTests() {
|
|
30
|
+
console.log('๐ Browser Automation Test Suite\n');
|
|
31
|
+
console.log('=' .repeat(60));
|
|
32
|
+
|
|
33
|
+
proc = spawn('node', ['browser-mcp-server-playwright.js'], {
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const rl = readline.createInterface({
|
|
38
|
+
input: proc.stdout,
|
|
39
|
+
crlfDelay: Infinity
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let testStep = 0;
|
|
43
|
+
const tests = [
|
|
44
|
+
'Initialize',
|
|
45
|
+
'List Tools',
|
|
46
|
+
'Health Check',
|
|
47
|
+
'Navigate to Example.com',
|
|
48
|
+
'Evaluate JavaScript',
|
|
49
|
+
'Take Screenshot',
|
|
50
|
+
'Cleanup'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
rl.on('line', async (line) => {
|
|
54
|
+
try {
|
|
55
|
+
const response = JSON.parse(line);
|
|
56
|
+
|
|
57
|
+
if (response.id === undefined) return; // Skip notifications
|
|
58
|
+
|
|
59
|
+
const currentTest = tests[testStep];
|
|
60
|
+
|
|
61
|
+
if (response.error) {
|
|
62
|
+
console.log(` โ ${currentTest} failed: ${response.error.message}`);
|
|
63
|
+
proc.kill();
|
|
64
|
+
process.exit(1);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (response.id) {
|
|
69
|
+
case 1: // Initialize
|
|
70
|
+
console.log(` โ
${currentTest}`);
|
|
71
|
+
testStep++;
|
|
72
|
+
setTimeout(() => sendRequest('tools/list'), 100);
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case 2: // Tools list
|
|
76
|
+
console.log(` โ
${currentTest} (${response.result.tools.length} tools)`);
|
|
77
|
+
testStep++;
|
|
78
|
+
setTimeout(() => sendRequest('tools/call', {
|
|
79
|
+
name: 'browser_health_check',
|
|
80
|
+
arguments: {}
|
|
81
|
+
}), 100);
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 3: // Health check
|
|
85
|
+
const healthText = response.result.content[0].text;
|
|
86
|
+
const mode = healthText.includes('Launched standalone')
|
|
87
|
+
? 'Standalone Mode'
|
|
88
|
+
: 'Antigravity Mode';
|
|
89
|
+
console.log(` โ
${currentTest} (${mode})`);
|
|
90
|
+
testStep++;
|
|
91
|
+
|
|
92
|
+
// Navigate to a real page
|
|
93
|
+
setTimeout(() => sendRequest('tools/call', {
|
|
94
|
+
name: 'browser_navigate',
|
|
95
|
+
arguments: { url: 'https://example.com' }
|
|
96
|
+
}), 100);
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case 4: // Navigate
|
|
100
|
+
console.log(` โ
${currentTest}`);
|
|
101
|
+
testStep++;
|
|
102
|
+
|
|
103
|
+
// Evaluate some JavaScript
|
|
104
|
+
setTimeout(() => sendRequest('tools/call', {
|
|
105
|
+
name: 'browser_evaluate',
|
|
106
|
+
arguments: {
|
|
107
|
+
code: 'document.title + " - " + window.location.href'
|
|
108
|
+
}
|
|
109
|
+
}), 500);
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 5: // Evaluate JS
|
|
113
|
+
const evalResult = JSON.parse(response.result.content[0].text);
|
|
114
|
+
console.log(` โ
${currentTest}`);
|
|
115
|
+
console.log(` Result: ${evalResult}`);
|
|
116
|
+
testStep++;
|
|
117
|
+
|
|
118
|
+
// Take a screenshot
|
|
119
|
+
setTimeout(() => sendRequest('tools/call', {
|
|
120
|
+
name: 'browser_screenshot',
|
|
121
|
+
arguments: { fullPage: false }
|
|
122
|
+
}), 500);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 6: // Screenshot
|
|
126
|
+
const imgData = response.result.content[0].data;
|
|
127
|
+
const imgSize = Buffer.from(imgData, 'base64').length;
|
|
128
|
+
console.log(` โ
${currentTest} (${(imgSize / 1024).toFixed(1)} KB)`);
|
|
129
|
+
testStep++;
|
|
130
|
+
|
|
131
|
+
// All tests complete
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
console.log('\n' + '='.repeat(60));
|
|
134
|
+
console.log('\n๐ All browser automation tests passed!\n');
|
|
135
|
+
console.log('โ
Test Results:');
|
|
136
|
+
console.log(' โข MCP protocol communication');
|
|
137
|
+
console.log(' โข Browser launch (standalone mode)');
|
|
138
|
+
console.log(' โข Page navigation (example.com)');
|
|
139
|
+
console.log(' โข JavaScript evaluation');
|
|
140
|
+
console.log(' โข Screenshot capture');
|
|
141
|
+
console.log('\nโจ The MCP server is fully functional!\n');
|
|
142
|
+
|
|
143
|
+
proc.kill();
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}, 500);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`\nโ Error: ${error.message}`);
|
|
150
|
+
proc.kill();
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
proc.stderr.on('data', (data) => {
|
|
156
|
+
const msg = data.toString();
|
|
157
|
+
if (msg.toLowerCase().includes('error')) {
|
|
158
|
+
console.error(`โ ๏ธ ${msg}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
proc.on('close', (code) => {
|
|
163
|
+
if (code !== 0 && testStep < tests.length) {
|
|
164
|
+
console.error(`\nโ Process exited unexpectedly (code ${code})`);
|
|
165
|
+
process.exit(code);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Start test sequence
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
sendRequest('initialize', {
|
|
172
|
+
protocolVersion: '2024-11-05',
|
|
173
|
+
capabilities: {},
|
|
174
|
+
clientInfo: { name: 'automation-test', version: '1.0.0' }
|
|
175
|
+
});
|
|
176
|
+
}, 100);
|
|
177
|
+
|
|
178
|
+
// Timeout after 60 seconds
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
console.error('\nโ Test timeout');
|
|
181
|
+
proc.kill();
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}, 60000);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
runTests().catch(error => {
|
|
187
|
+
console.error('โ Fatal error:', error);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
package/test-mcp.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive test script for the browser MCP server
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
|
|
10
|
+
let requestId = 0;
|
|
11
|
+
|
|
12
|
+
function sendRequest(proc, method, params = {}) {
|
|
13
|
+
const id = ++requestId;
|
|
14
|
+
const request = {
|
|
15
|
+
jsonrpc: '2.0',
|
|
16
|
+
id,
|
|
17
|
+
method,
|
|
18
|
+
params
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
console.log(`\nโก๏ธ Sending: ${method}`);
|
|
22
|
+
proc.stdin.write(JSON.stringify(request) + '\n');
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sendNotification(proc, method, params = {}) {
|
|
27
|
+
const notification = {
|
|
28
|
+
jsonrpc: '2.0',
|
|
29
|
+
method,
|
|
30
|
+
params
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
console.log(`\nโก๏ธ Sending notification: ${method}`);
|
|
34
|
+
proc.stdin.write(JSON.stringify(notification) + '\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function runTests() {
|
|
38
|
+
console.log('๐งช Starting MCP Server Tests\n');
|
|
39
|
+
console.log('=' .repeat(50));
|
|
40
|
+
|
|
41
|
+
const proc = spawn('node', ['browser-mcp-server-playwright.js'], {
|
|
42
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const rl = readline.createInterface({
|
|
46
|
+
input: proc.stdout,
|
|
47
|
+
crlfDelay: Infinity
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const responses = new Map();
|
|
51
|
+
let testsCompleted = 0;
|
|
52
|
+
const totalTests = 3;
|
|
53
|
+
|
|
54
|
+
rl.on('line', (line) => {
|
|
55
|
+
try {
|
|
56
|
+
const response = JSON.parse(line);
|
|
57
|
+
console.log(`\nโฌ
๏ธ Response (id=${response.id}):`);
|
|
58
|
+
|
|
59
|
+
if (response.error) {
|
|
60
|
+
console.log(` โ Error: ${response.error.message}`);
|
|
61
|
+
} else if (response.result) {
|
|
62
|
+
if (response.id === 1) {
|
|
63
|
+
// Initialize response
|
|
64
|
+
console.log(` โ
Protocol: ${response.result.protocolVersion}`);
|
|
65
|
+
console.log(` โ
Server: ${response.result.serverInfo.name} v${response.result.serverInfo.version}`);
|
|
66
|
+
testsCompleted++;
|
|
67
|
+
|
|
68
|
+
// Send initialized notification
|
|
69
|
+
sendNotification(proc, 'notifications/initialized');
|
|
70
|
+
|
|
71
|
+
// Request tools list
|
|
72
|
+
setTimeout(() => sendRequest(proc, 'tools/list'), 100);
|
|
73
|
+
|
|
74
|
+
} else if (response.id === 2) {
|
|
75
|
+
// Tools list response
|
|
76
|
+
const toolCount = response.result.tools.length;
|
|
77
|
+
console.log(` โ
Received ${toolCount} tools:`);
|
|
78
|
+
response.result.tools.forEach(tool => {
|
|
79
|
+
console.log(` - ${tool.name}`);
|
|
80
|
+
});
|
|
81
|
+
testsCompleted++;
|
|
82
|
+
|
|
83
|
+
// Test health check (actual browser operation)
|
|
84
|
+
setTimeout(() => sendRequest(proc, 'tools/call', {
|
|
85
|
+
name: 'browser_health_check',
|
|
86
|
+
arguments: {}
|
|
87
|
+
}), 100);
|
|
88
|
+
|
|
89
|
+
} else if (response.id === 3) {
|
|
90
|
+
// Health check response
|
|
91
|
+
if (response.result.content && response.result.content[0]) {
|
|
92
|
+
console.log(` โ
Health Check Result:`);
|
|
93
|
+
console.log(response.result.content[0].text.split('\n').map(l => ` ${l}`).join('\n'));
|
|
94
|
+
}
|
|
95
|
+
testsCompleted++;
|
|
96
|
+
|
|
97
|
+
// All tests done
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
console.log('\n' + '='.repeat(50));
|
|
100
|
+
console.log(`\nโ
All tests passed! (${testsCompleted}/${totalTests})`);
|
|
101
|
+
console.log('\n๐ Test Summary:');
|
|
102
|
+
console.log(' โ
MCP Protocol initialization');
|
|
103
|
+
console.log(' โ
Tools listing (16 tools)');
|
|
104
|
+
console.log(' โ
Browser automation (health check)');
|
|
105
|
+
console.log('\n๐ MCP Server is fully functional!\n');
|
|
106
|
+
proc.kill();
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}, 500);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('โ Error parsing response:', error.message);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
proc.stderr.on('data', (data) => {
|
|
117
|
+
const msg = data.toString();
|
|
118
|
+
if (msg.includes('Error') || msg.includes('error')) {
|
|
119
|
+
console.error(`\nโ ๏ธ stderr: ${msg}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
proc.on('close', (code) => {
|
|
124
|
+
if (code !== 0 && testsCompleted < totalTests) {
|
|
125
|
+
console.error(`\nโ Process exited with code ${code}`);
|
|
126
|
+
process.exit(code);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Start the test sequence
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
sendRequest(proc, 'initialize', {
|
|
133
|
+
protocolVersion: '2024-11-05',
|
|
134
|
+
capabilities: {},
|
|
135
|
+
clientInfo: { name: 'test-client', version: '1.0.0' }
|
|
136
|
+
});
|
|
137
|
+
}, 100);
|
|
138
|
+
|
|
139
|
+
// Safety timeout
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
console.error('\nโ Test timeout - tests did not complete in time');
|
|
142
|
+
proc.kill();
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}, 30000); // 30 second timeout
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
runTests().catch(error => {
|
|
148
|
+
console.error('โ Test failed:', error);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|