mcpbrowser 0.2.19 → 0.2.21
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/.github/workflows/test.yml +33 -0
- package/extension/package.json +1 -1
- package/package.json +4 -2
- package/server.json +1 -1
- package/src/mcp-browser.js +6 -8
- package/test-mcp.js +63 -0
- package/tests/mcp-server.test.js +154 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: MCP Server Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18.x, 20.x, 22.x]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: ${{ matrix.node-version }}
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Run MCP server tests
|
|
30
|
+
run: npm run test:mcp
|
|
31
|
+
|
|
32
|
+
- name: Run all tests
|
|
33
|
+
run: npm test
|
package/extension/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mcpbrowser",
|
|
3
3
|
"displayName": "MCP Browser",
|
|
4
4
|
"description": "Lightweight MCP server-extension that allows Copilot to fetch protected web pages you can authenticate to via browser - handles login, SSO, and anti-crawler restrictions. Should be used when standard fetch_webpage fails",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.21",
|
|
6
6
|
"publisher": "cherchyk",
|
|
7
7
|
"icon": "icon.png",
|
|
8
8
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{ "name": "mcpbrowser",
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.21",
|
|
3
3
|
"mcpName": "io.github.cherchyk/browser",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server that fetches protected web pages using Chrome DevTools Protocol",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"mcpbrowser": "src/mcp-browser.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"mcp": "node src/mcp-browser.js"
|
|
11
|
+
"mcp": "node src/mcp-browser.js",
|
|
12
|
+
"test": "node --test tests/*.test.js",
|
|
13
|
+
"test:mcp": "node --test tests/mcp-server.test.js"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"mcp",
|
package/server.json
CHANGED
package/src/mcp-browser.js
CHANGED
|
@@ -393,7 +393,7 @@ function prepareHtml(html, baseUrl) {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
async function main() {
|
|
396
|
-
const server = new Server({ name: "MCPBrowser", version: "0.2.
|
|
396
|
+
const server = new Server({ name: "MCPBrowser", version: "0.2.21" }, { capabilities: { tools: {} } });
|
|
397
397
|
|
|
398
398
|
const tools = [
|
|
399
399
|
{
|
|
@@ -455,10 +455,8 @@ async function main() {
|
|
|
455
455
|
// Export for testing
|
|
456
456
|
export { fetchPage, getBrowser, prepareHtml };
|
|
457
457
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
});
|
|
464
|
-
}
|
|
458
|
+
// Run the MCP server
|
|
459
|
+
main().catch((err) => {
|
|
460
|
+
console.error(err);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
});
|
package/test-mcp.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
|
|
4
|
+
const mcpProcess = spawn('node', ['src/mcp-browser.js'], {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
stdio: ['pipe', 'pipe', 'inherit']
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Send initialize request
|
|
10
|
+
const initRequest = {
|
|
11
|
+
jsonrpc: '2.0',
|
|
12
|
+
id: 1,
|
|
13
|
+
method: 'initialize',
|
|
14
|
+
params: {
|
|
15
|
+
protocolVersion: '2024-11-05',
|
|
16
|
+
capabilities: {},
|
|
17
|
+
clientInfo: { name: 'test', version: '1.0' }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
console.log('Sending initialize request...');
|
|
22
|
+
mcpProcess.stdin.write(JSON.stringify(initRequest) + '\n');
|
|
23
|
+
|
|
24
|
+
// Send list tools request
|
|
25
|
+
const listToolsRequest = {
|
|
26
|
+
jsonrpc: '2.0',
|
|
27
|
+
id: 2,
|
|
28
|
+
method: 'tools/list',
|
|
29
|
+
params: {}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
console.log('Sending tools/list request...');
|
|
34
|
+
mcpProcess.stdin.write(JSON.stringify(listToolsRequest) + '\n');
|
|
35
|
+
}, 1000);
|
|
36
|
+
|
|
37
|
+
let responseBuffer = '';
|
|
38
|
+
mcpProcess.stdout.on('data', (data) => {
|
|
39
|
+
responseBuffer += data.toString();
|
|
40
|
+
const lines = responseBuffer.split('\n');
|
|
41
|
+
responseBuffer = lines.pop() || '';
|
|
42
|
+
|
|
43
|
+
lines.forEach(line => {
|
|
44
|
+
if (line.trim()) {
|
|
45
|
+
try {
|
|
46
|
+
const response = JSON.parse(line);
|
|
47
|
+
console.log('Response:', JSON.stringify(response, null, 2));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.log('Raw output:', line);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
mcpProcess.on('error', (err) => {
|
|
56
|
+
console.error('Error:', err);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
console.log('Closing...');
|
|
61
|
+
mcpProcess.kill();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}, 3000);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
describe('MCP Server', () => {
|
|
11
|
+
it('should start and respond to initialize request', async () => {
|
|
12
|
+
const mcpProcess = spawn('node', [join(__dirname, '..', 'src', 'mcp-browser.js')], {
|
|
13
|
+
stdio: ['pipe', 'pipe', 'inherit']
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const initRequest = {
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id: 1,
|
|
20
|
+
method: 'initialize',
|
|
21
|
+
params: {
|
|
22
|
+
protocolVersion: '2024-11-05',
|
|
23
|
+
capabilities: {},
|
|
24
|
+
clientInfo: { name: 'test', version: '1.0' }
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const response = await new Promise((resolve, reject) => {
|
|
29
|
+
const timeout = setTimeout(() => {
|
|
30
|
+
reject(new Error('Server did not respond to initialize request within 5 seconds'));
|
|
31
|
+
}, 5000);
|
|
32
|
+
|
|
33
|
+
let buffer = '';
|
|
34
|
+
mcpProcess.stdout.on('data', (data) => {
|
|
35
|
+
buffer += data.toString();
|
|
36
|
+
const lines = buffer.split('\n');
|
|
37
|
+
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (line.trim()) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(line);
|
|
42
|
+
if (parsed.id === 1) {
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
resolve(parsed);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Not JSON, continue
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
mcpProcess.on('error', (err) => {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
reject(err);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
mcpProcess.on('exit', (code) => {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
reject(new Error(`Server exited with code ${code} before responding`));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
mcpProcess.stdin.write(JSON.stringify(initRequest) + '\n');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
assert.ok(response.result, 'Initialize response should have result');
|
|
68
|
+
assert.equal(response.result.protocolVersion, '2024-11-05', 'Protocol version should match');
|
|
69
|
+
assert.ok(response.result.serverInfo, 'Server info should be present');
|
|
70
|
+
assert.equal(response.result.serverInfo.name, 'MCPBrowser', 'Server name should be MCPBrowser');
|
|
71
|
+
} finally {
|
|
72
|
+
mcpProcess.kill();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should respond to tools/list request', async () => {
|
|
77
|
+
const mcpProcess = spawn('node', [join(__dirname, '..', 'src', 'mcp-browser.js')], {
|
|
78
|
+
stdio: ['pipe', 'pipe', 'inherit']
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// First initialize
|
|
83
|
+
const initRequest = {
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
id: 1,
|
|
86
|
+
method: 'initialize',
|
|
87
|
+
params: {
|
|
88
|
+
protocolVersion: '2024-11-05',
|
|
89
|
+
capabilities: {},
|
|
90
|
+
clientInfo: { name: 'test', version: '1.0' }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
mcpProcess.stdin.write(JSON.stringify(initRequest) + '\n');
|
|
95
|
+
|
|
96
|
+
// Wait a bit for initialization
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
98
|
+
|
|
99
|
+
// Then request tools list
|
|
100
|
+
const toolsRequest = {
|
|
101
|
+
jsonrpc: '2.0',
|
|
102
|
+
id: 2,
|
|
103
|
+
method: 'tools/list',
|
|
104
|
+
params: {}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const response = await new Promise((resolve, reject) => {
|
|
108
|
+
const timeout = setTimeout(() => {
|
|
109
|
+
reject(new Error('Server did not respond to tools/list request within 5 seconds'));
|
|
110
|
+
}, 5000);
|
|
111
|
+
|
|
112
|
+
let buffer = '';
|
|
113
|
+
mcpProcess.stdout.on('data', (data) => {
|
|
114
|
+
buffer += data.toString();
|
|
115
|
+
const lines = buffer.split('\n');
|
|
116
|
+
|
|
117
|
+
for (const line of lines) {
|
|
118
|
+
if (line.trim()) {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(line);
|
|
121
|
+
if (parsed.id === 2) {
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
resolve(parsed);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Not JSON, continue
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
mcpProcess.on('error', (err) => {
|
|
134
|
+
clearTimeout(timeout);
|
|
135
|
+
reject(err);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
mcpProcess.stdin.write(JSON.stringify(toolsRequest) + '\n');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
assert.ok(response.result, 'Tools/list response should have result');
|
|
142
|
+
assert.ok(response.result.tools, 'Result should have tools array');
|
|
143
|
+
assert.ok(Array.isArray(response.result.tools), 'Tools should be an array');
|
|
144
|
+
assert.ok(response.result.tools.length > 0, 'Should have at least one tool');
|
|
145
|
+
|
|
146
|
+
const fetchTool = response.result.tools.find(t => t.name === 'fetch_webpage_protected');
|
|
147
|
+
assert.ok(fetchTool, 'Should have fetch_webpage_protected tool');
|
|
148
|
+
assert.ok(fetchTool.description, 'Tool should have description');
|
|
149
|
+
assert.ok(fetchTool.inputSchema, 'Tool should have input schema');
|
|
150
|
+
} finally {
|
|
151
|
+
mcpProcess.kill();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|