@ricardodeazambuja/browser-mcp-server 1.3.0 ā 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.4.0.md +8 -0
- package/README.md +87 -6
- package/package.json +3 -2
- package/src/browser.js +2 -0
- package/src/cdp.js +58 -0
- package/src/index.js +2 -2
- package/src/tools/docs.js +798 -0
- package/src/tools/index.js +5 -1
- package/src/tools/network.js +552 -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 +5 -1
- package/src/utils.js +12 -1
- 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/tests/test-mcp.js +3 -2
- 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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security Tools Test Suite
|
|
5
|
+
* Tests security headers, CSP monitoring, and mixed content detection
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
|
|
12
|
+
const serverPath = path.join(__dirname, '..', 'src', 'index.js');
|
|
13
|
+
const proc = spawn('node', [serverPath], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
14
|
+
|
|
15
|
+
proc.stderr.on('data', (data) => {
|
|
16
|
+
// Suppress stderr for cleaner test output
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
proc.on('error', (err) => {
|
|
20
|
+
console.error('Server error:', err);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const rl = readline.createInterface({ input: proc.stdout, terminal: false });
|
|
25
|
+
|
|
26
|
+
let messageId = 1;
|
|
27
|
+
let testStep = 0;
|
|
28
|
+
|
|
29
|
+
function sendRequest(method, params) {
|
|
30
|
+
const request = { jsonrpc: '2.0', id: messageId++, method, params };
|
|
31
|
+
proc.stdin.write(JSON.stringify(request) + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('š Security Tools Test Suite\n');
|
|
35
|
+
|
|
36
|
+
rl.on('line', (line) => {
|
|
37
|
+
try {
|
|
38
|
+
const response = JSON.parse(line);
|
|
39
|
+
if (response.method === 'notifications/resources/list_changed') return;
|
|
40
|
+
if (response.error) {
|
|
41
|
+
console.error('ā Error:', response.error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
if (response.result && response.id) {
|
|
45
|
+
handleResponse(response);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore non-JSON lines
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function handleResponse(response) {
|
|
53
|
+
const currentTest = steps[testStep];
|
|
54
|
+
if (!currentTest) return;
|
|
55
|
+
|
|
56
|
+
console.log(`ā”ļø ${currentTest.name}`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
currentTest.verify(response);
|
|
60
|
+
console.log(` ā
Passed\n`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(` ā Failed: ${err.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
testStep++;
|
|
67
|
+
if (testStep < steps.length) {
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
sendRequest(steps[testStep].method, steps[testStep].params());
|
|
70
|
+
}, 500);
|
|
71
|
+
} else {
|
|
72
|
+
console.log('š All security tests passed!');
|
|
73
|
+
proc.kill();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const steps = [
|
|
79
|
+
{
|
|
80
|
+
name: 'Initialize',
|
|
81
|
+
method: 'initialize',
|
|
82
|
+
params: () => ({
|
|
83
|
+
protocolVersion: '2024-11-05',
|
|
84
|
+
capabilities: {},
|
|
85
|
+
clientInfo: { name: 'security-test', version: '1.0.0' }
|
|
86
|
+
}),
|
|
87
|
+
verify: () => { }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Navigate to Security Test Page',
|
|
91
|
+
method: 'tools/call',
|
|
92
|
+
params: () => ({
|
|
93
|
+
name: 'browser_navigate',
|
|
94
|
+
arguments: { url: 'file://' + path.join(__dirname, 'fixtures', 'test-security.html') }
|
|
95
|
+
}),
|
|
96
|
+
verify: () => { }
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Wait for Page Load',
|
|
100
|
+
method: 'tools/call',
|
|
101
|
+
params: () => ({
|
|
102
|
+
name: 'browser_wait',
|
|
103
|
+
arguments: { ms: 1000 }
|
|
104
|
+
}),
|
|
105
|
+
verify: () => { }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'Get Security Headers',
|
|
109
|
+
method: 'tools/call',
|
|
110
|
+
params: () => ({
|
|
111
|
+
name: 'browser_sec_get_security_headers',
|
|
112
|
+
arguments: {}
|
|
113
|
+
}),
|
|
114
|
+
verify: (res) => {
|
|
115
|
+
const text = res.result.content[0].text;
|
|
116
|
+
if (res.result.isError) {
|
|
117
|
+
throw new Error('Get security headers failed: ' + text);
|
|
118
|
+
}
|
|
119
|
+
if (!text.includes('content-security-policy') && !text.includes('Security Headers')) {
|
|
120
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Start CSP Monitoring',
|
|
126
|
+
method: 'tools/call',
|
|
127
|
+
params: () => ({
|
|
128
|
+
name: 'browser_sec_start_csp_monitoring',
|
|
129
|
+
arguments: {}
|
|
130
|
+
}),
|
|
131
|
+
verify: (res) => {
|
|
132
|
+
const text = res.result.content[0].text;
|
|
133
|
+
// Accept success, already active, or error
|
|
134
|
+
if (!text.includes('monitoring started') && !text.includes('already active') && !text.includes('Error')) {
|
|
135
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'Wait for CSP Events',
|
|
141
|
+
method: 'tools/call',
|
|
142
|
+
params: () => ({
|
|
143
|
+
name: 'browser_wait',
|
|
144
|
+
arguments: { ms: 1000 }
|
|
145
|
+
}),
|
|
146
|
+
verify: () => { }
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Get CSP Violations',
|
|
150
|
+
method: 'tools/call',
|
|
151
|
+
params: () => ({
|
|
152
|
+
name: 'browser_sec_get_csp_violations',
|
|
153
|
+
arguments: {}
|
|
154
|
+
}),
|
|
155
|
+
verify: (res) => {
|
|
156
|
+
const text = res.result.content[0].text;
|
|
157
|
+
// May or may not have violations depending on browser behavior
|
|
158
|
+
if (!text.includes('violations') && !text.includes('No CSP') && !text.includes('not active') && !text.includes('Error')) {
|
|
159
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'Stop CSP Monitoring',
|
|
165
|
+
method: 'tools/call',
|
|
166
|
+
params: () => ({
|
|
167
|
+
name: 'browser_sec_stop_csp_monitoring',
|
|
168
|
+
arguments: {}
|
|
169
|
+
}),
|
|
170
|
+
verify: (res) => {
|
|
171
|
+
const text = res.result.content[0].text;
|
|
172
|
+
// Accept stopped, not active, or error
|
|
173
|
+
if (!text.includes('stopped') && !text.includes('not active') && !text.includes('Error')) {
|
|
174
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'Detect Mixed Content (HTTP page)',
|
|
180
|
+
method: 'tools/call',
|
|
181
|
+
params: () => ({
|
|
182
|
+
name: 'browser_sec_detect_mixed_content',
|
|
183
|
+
arguments: {}
|
|
184
|
+
}),
|
|
185
|
+
verify: (res) => {
|
|
186
|
+
const text = res.result.content[0].text;
|
|
187
|
+
// On file:// protocol, should indicate HTTPS-only
|
|
188
|
+
if (!text.includes('HTTPS') && !text.includes('No mixed content')) {
|
|
189
|
+
throw new Error('Invalid mixed content response');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
// Start tests
|
|
196
|
+
setTimeout(() => sendRequest(steps[0].method, steps[0].params()), 500);
|
|
197
|
+
|
|
198
|
+
// Timeout after 60 seconds
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
console.error('\nā Test timeout');
|
|
201
|
+
proc.kill();
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}, 60000);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage Tools Test Suite
|
|
5
|
+
* Tests IndexedDB, Cache Storage, and Service Worker inspection
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
|
|
12
|
+
const serverPath = path.join(__dirname, '..', 'src', 'index.js');
|
|
13
|
+
const proc = spawn('node', [serverPath], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
14
|
+
|
|
15
|
+
proc.stderr.on('data', (data) => {
|
|
16
|
+
// Suppress stderr for cleaner test output
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
proc.on('error', (err) => {
|
|
20
|
+
console.error('Server error:', err);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const rl = readline.createInterface({ input: proc.stdout, terminal: false });
|
|
25
|
+
|
|
26
|
+
let messageId = 1;
|
|
27
|
+
let testStep = 0;
|
|
28
|
+
|
|
29
|
+
function sendRequest(method, params) {
|
|
30
|
+
const request = { jsonrpc: '2.0', id: messageId++, method, params };
|
|
31
|
+
proc.stdin.write(JSON.stringify(request) + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('š¾ Storage Tools Test Suite\n');
|
|
35
|
+
|
|
36
|
+
rl.on('line', (line) => {
|
|
37
|
+
try {
|
|
38
|
+
const response = JSON.parse(line);
|
|
39
|
+
if (response.method === 'notifications/resources/list_changed') return;
|
|
40
|
+
if (response.error) {
|
|
41
|
+
console.error('ā Error:', response.error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
if (response.result && response.id) {
|
|
45
|
+
handleResponse(response);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore non-JSON lines
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function handleResponse(response) {
|
|
53
|
+
const currentTest = steps[testStep];
|
|
54
|
+
if (!currentTest) return;
|
|
55
|
+
|
|
56
|
+
console.log(`ā”ļø ${currentTest.name}`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
currentTest.verify(response);
|
|
60
|
+
console.log(` ā
Passed\n`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(` ā Failed: ${err.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
testStep++;
|
|
67
|
+
if (testStep < steps.length) {
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
sendRequest(steps[testStep].method, steps[testStep].params());
|
|
70
|
+
}, 500);
|
|
71
|
+
} else {
|
|
72
|
+
console.log('š All storage tests passed!');
|
|
73
|
+
proc.kill();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const steps = [
|
|
79
|
+
{
|
|
80
|
+
name: 'Initialize',
|
|
81
|
+
method: 'initialize',
|
|
82
|
+
params: () => ({
|
|
83
|
+
protocolVersion: '2024-11-05',
|
|
84
|
+
capabilities: {},
|
|
85
|
+
clientInfo: { name: 'storage-test', version: '1.0.0' }
|
|
86
|
+
}),
|
|
87
|
+
verify: () => { }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Navigate to Storage Test Page',
|
|
91
|
+
method: 'tools/call',
|
|
92
|
+
params: () => ({
|
|
93
|
+
name: 'browser_navigate',
|
|
94
|
+
arguments: { url: 'file://' + path.join(__dirname, 'fixtures', 'test-storage.html') }
|
|
95
|
+
}),
|
|
96
|
+
verify: () => { }
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Wait for Storage Setup',
|
|
100
|
+
method: 'tools/call',
|
|
101
|
+
params: () => ({
|
|
102
|
+
name: 'browser_wait',
|
|
103
|
+
arguments: { ms: 2000 }
|
|
104
|
+
}),
|
|
105
|
+
verify: () => { }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'List IndexedDB Databases',
|
|
109
|
+
method: 'tools/call',
|
|
110
|
+
params: () => ({
|
|
111
|
+
name: 'browser_storage_get_indexeddb',
|
|
112
|
+
arguments: {}
|
|
113
|
+
}),
|
|
114
|
+
verify: (res) => {
|
|
115
|
+
const text = res.result.content[0].text;
|
|
116
|
+
// Should list databases, indicate none found, or CDP error
|
|
117
|
+
if (!text.includes('databases') && !text.includes('No IndexedDB') && !text.includes('CDP Error') && !text.includes('Error')) {
|
|
118
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Inspect IndexedDB Database',
|
|
124
|
+
method: 'tools/call',
|
|
125
|
+
params: () => ({
|
|
126
|
+
name: 'browser_storage_get_indexeddb',
|
|
127
|
+
arguments: { databaseName: 'testDB' }
|
|
128
|
+
}),
|
|
129
|
+
verify: (res) => {
|
|
130
|
+
const text = res.result.content[0].text;
|
|
131
|
+
// Should show database structure or error if not found
|
|
132
|
+
if (!text.includes('objectStores') && !text.includes('not found') && !text.includes('error') && !text.includes('Error') && !text.includes('CDP Error')) {
|
|
133
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'List Cache Storage',
|
|
139
|
+
method: 'tools/call',
|
|
140
|
+
params: () => ({
|
|
141
|
+
name: 'browser_storage_get_cache_storage',
|
|
142
|
+
arguments: {}
|
|
143
|
+
}),
|
|
144
|
+
verify: (res) => {
|
|
145
|
+
const text = res.result.content[0].text;
|
|
146
|
+
// Should list caches, indicate none found, or error
|
|
147
|
+
if (!text.includes('caches') && !text.includes('No Cache') && !text.includes('CDP Error') && !text.includes('Error')) {
|
|
148
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Inspect Cache Entries',
|
|
154
|
+
method: 'tools/call',
|
|
155
|
+
params: () => ({
|
|
156
|
+
name: 'browser_storage_get_cache_storage',
|
|
157
|
+
arguments: { cacheName: 'test-cache-v1' }
|
|
158
|
+
}),
|
|
159
|
+
verify: (res) => {
|
|
160
|
+
const text = res.result.content[0].text;
|
|
161
|
+
// Should show cache entries, not found, or error
|
|
162
|
+
if (!text.includes('entries') && !text.includes('not found') && !text.includes('CDP Error') && !text.includes('Error')) {
|
|
163
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'Get Service Workers',
|
|
169
|
+
method: 'tools/call',
|
|
170
|
+
params: () => ({
|
|
171
|
+
name: 'browser_storage_get_service_workers',
|
|
172
|
+
arguments: {}
|
|
173
|
+
}),
|
|
174
|
+
verify: (res) => {
|
|
175
|
+
const text = res.result.content[0].text;
|
|
176
|
+
// Should list service workers, indicate none/not supported, or error
|
|
177
|
+
if (!text.includes('Service Worker') && !text.includes('supported') && !text.includes('No service') && !text.includes('Error') && !text.includes('CDP Error')) {
|
|
178
|
+
throw new Error('Unexpected response: ' + text.substring(0, 100));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// Start tests
|
|
185
|
+
setTimeout(() => sendRequest(steps[0].method, steps[0].params()), 500);
|
|
186
|
+
|
|
187
|
+
// Timeout after 60 seconds
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
console.error('\nā Test timeout');
|
|
190
|
+
proc.kill();
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}, 60000);
|