@aikeymouse/chromelink-mcp 1.2.2 → 1.2.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/bin/chromelink-mcp +4 -1
- package/index.js +566 -608
- package/package.json +4 -2
- package/scripts/prepare-publish.js +6 -2
package/bin/chromelink-mcp
CHANGED
package/index.js
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* Model Context Protocol (MCP) server that exposes Chrome browser automation
|
|
7
7
|
* capabilities to AI agents like Claude, GPT, etc.
|
|
8
8
|
*
|
|
9
|
-
* This server
|
|
10
|
-
*
|
|
9
|
+
* This server uses the official @modelcontextprotocol/sdk package for
|
|
10
|
+
* standards-compliant MCP protocol implementation.
|
|
11
11
|
*
|
|
12
12
|
* Architecture:
|
|
13
13
|
* AI Agent (Claude/GPT) <-> MCP Server (this file) <-> browser-link-server <-> Chrome Extension
|
|
14
14
|
*
|
|
15
15
|
* Communication:
|
|
16
|
-
* - AI Agent ↔ MCP Server: stdio (MCP protocol)
|
|
16
|
+
* - AI Agent ↔ MCP Server: stdio (MCP protocol via SDK)
|
|
17
17
|
* - MCP Server ↔ browser-link-server: WebSocket (ws://localhost:9000)
|
|
18
18
|
*
|
|
19
19
|
* Usage:
|
|
@@ -25,676 +25,634 @@
|
|
|
25
25
|
* "mcpServers": {
|
|
26
26
|
* "chrome-link": {
|
|
27
27
|
* "command": "node",
|
|
28
|
-
* "args": ["/path/to/chrome-driver-extension/
|
|
28
|
+
* "args": ["/path/to/chrome-driver-extension/mcp-server/index.js"]
|
|
29
29
|
* }
|
|
30
30
|
* }
|
|
31
31
|
* }
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
35
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
36
|
+
import {
|
|
37
|
+
CallToolRequestSchema,
|
|
38
|
+
ListToolsRequestSchema
|
|
39
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
40
|
+
import { createRequire } from 'module';
|
|
41
|
+
|
|
42
|
+
// Import CommonJS modules
|
|
43
|
+
const require = createRequire(import.meta.url);
|
|
34
44
|
const ChromeLinkClient = require('@aikeymouse/chromelink-client');
|
|
35
45
|
const packageJson = require('./package.json');
|
|
36
46
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
constructor() {
|
|
40
|
-
this.client = null;
|
|
41
|
-
this.requestId = 1;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Log to stderr (stdout is reserved for MCP protocol)
|
|
46
|
-
*/
|
|
47
|
-
log(message, ...args) {
|
|
48
|
-
console.error(`[MCP Server] ${message}`, ...args);
|
|
49
|
-
}
|
|
47
|
+
// Global ChromeLink client instance
|
|
48
|
+
let client = null;
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
50
|
+
/**
|
|
51
|
+
* Log to stderr (stdout is reserved for MCP protocol)
|
|
52
|
+
*/
|
|
53
|
+
function log(message, ...args) {
|
|
54
|
+
console.error(`[MCP Server] ${message}`, ...args);
|
|
55
|
+
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Initialize connection to browser-link-server
|
|
59
|
+
*/
|
|
60
|
+
async function initializeClient() {
|
|
61
|
+
try {
|
|
62
|
+
client = new ChromeLinkClient({ verbose: false });
|
|
63
|
+
await client.connect('ws://localhost:9000');
|
|
64
|
+
log('Connected to browser-link-server');
|
|
65
|
+
|
|
66
|
+
// Handle WebSocket disconnection - log but don't exit
|
|
67
|
+
// Server will attempt to reconnect on next tool call
|
|
68
|
+
client.ws.on('close', () => {
|
|
69
|
+
log('WebSocket connection closed (session may have expired)');
|
|
70
|
+
client = null;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
client.ws.on('error', (err) => {
|
|
74
|
+
log('WebSocket error:', err.message);
|
|
70
75
|
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
log('Failed to connect to browser-link-server:', error.message);
|
|
78
|
+
throw error;
|
|
71
79
|
}
|
|
80
|
+
}
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.log('Connected to browser-link-server');
|
|
81
|
-
|
|
82
|
-
// Handle WebSocket disconnection - log but don't exit
|
|
83
|
-
// Server will attempt to reconnect on next tool call
|
|
84
|
-
this.client.ws.on('close', () => {
|
|
85
|
-
this.log('WebSocket connection closed (session may have expired)');
|
|
86
|
-
this.client = null;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
this.client.ws.on('error', (err) => {
|
|
90
|
-
this.log('WebSocket error:', err.message);
|
|
91
|
-
});
|
|
92
|
-
} catch (error) {
|
|
93
|
-
this.log('Failed to connect to browser-link-server:', error.message);
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Ensure connection is active, reconnect if needed
|
|
100
|
-
*/
|
|
101
|
-
async ensureConnected() {
|
|
102
|
-
if (!this.client || !this.client.ws || this.client.ws.readyState !== 1) {
|
|
103
|
-
this.log('Reconnecting to browser-link-server...');
|
|
104
|
-
await this.initialize();
|
|
105
|
-
}
|
|
82
|
+
/**
|
|
83
|
+
* Ensure connection is active, reconnect if needed
|
|
84
|
+
*/
|
|
85
|
+
async function ensureConnected() {
|
|
86
|
+
if (!client || !client.ws || client.ws.readyState !== 1) {
|
|
87
|
+
log('Reconnecting to browser-link-server...');
|
|
88
|
+
await initializeClient();
|
|
106
89
|
}
|
|
90
|
+
}
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Get list of available MCP tools
|
|
94
|
+
*/
|
|
95
|
+
function getTools() {
|
|
96
|
+
return [
|
|
97
|
+
// Tab Management
|
|
98
|
+
{
|
|
99
|
+
name: 'chrome_list_tabs',
|
|
100
|
+
description: 'List all open tabs in the browser',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {},
|
|
104
|
+
required: []
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'chrome_open_tab',
|
|
109
|
+
description: 'Open a new tab with the specified URL',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
url: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
description: 'URL to open in the new tab'
|
|
116
|
+
},
|
|
117
|
+
focus: {
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
description: 'Whether to focus the new tab (default: true)',
|
|
120
|
+
default: true
|
|
122
121
|
}
|
|
123
122
|
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
required: ['url']
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'chrome_navigate_tab',
|
|
128
|
+
description: 'Navigate an existing tab to a new URL',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
tabId: {
|
|
133
|
+
type: 'number',
|
|
134
|
+
description: 'ID of the tab to navigate'
|
|
135
|
+
},
|
|
136
|
+
url: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'URL to navigate to'
|
|
139
|
+
},
|
|
140
|
+
focus: {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
description: 'Whether to focus the tab (default: true)',
|
|
143
|
+
default: true
|
|
141
144
|
}
|
|
142
145
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
description: 'URL to navigate to'
|
|
156
|
-
},
|
|
157
|
-
focus: {
|
|
158
|
-
type: 'boolean',
|
|
159
|
-
description: 'Whether to focus the tab (default: true)',
|
|
160
|
-
default: true
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
required: ['tabId', 'url']
|
|
146
|
+
required: ['tabId', 'url']
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'chrome_switch_tab',
|
|
151
|
+
description: 'Switch to (focus) a specific tab',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
tabId: {
|
|
156
|
+
type: 'number',
|
|
157
|
+
description: 'ID of the tab to switch to'
|
|
164
158
|
}
|
|
165
159
|
},
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
160
|
+
required: ['tabId']
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'chrome_close_tab',
|
|
165
|
+
description: 'Close a specific tab',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
tabId: {
|
|
170
|
+
type: 'number',
|
|
171
|
+
description: 'ID of the tab to close'
|
|
178
172
|
}
|
|
179
173
|
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
174
|
+
required: ['tabId']
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'chrome_get_active_tab',
|
|
179
|
+
description: 'Get information about the currently active tab',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {},
|
|
183
|
+
required: []
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Navigation History
|
|
188
|
+
{
|
|
189
|
+
name: 'chrome_go_back',
|
|
190
|
+
description: 'Navigate back in tab history (only works after user navigation, not programmatic)',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
tabId: {
|
|
195
|
+
type: 'number',
|
|
196
|
+
description: 'ID of the tab to navigate back'
|
|
192
197
|
}
|
|
193
198
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
required: ['tabId']
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'chrome_go_forward',
|
|
204
|
+
description: 'Navigate forward in tab history (only works after user navigation, not programmatic)',
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
tabId: {
|
|
209
|
+
type: 'number',
|
|
210
|
+
description: 'ID of the tab to navigate forward'
|
|
201
211
|
}
|
|
202
212
|
},
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
required: ['tabId']
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// DOM Interaction
|
|
218
|
+
{
|
|
219
|
+
name: 'chrome_wait_for_element',
|
|
220
|
+
description: 'Wait for an element matching the CSS selector to appear on the page',
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
selector: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
description: 'CSS selector for the element'
|
|
227
|
+
},
|
|
228
|
+
tabId: {
|
|
229
|
+
type: 'number',
|
|
230
|
+
description: 'ID of the tab'
|
|
231
|
+
},
|
|
232
|
+
timeout: {
|
|
233
|
+
type: 'number',
|
|
234
|
+
description: 'Maximum time to wait in milliseconds (default: 5000)',
|
|
235
|
+
default: 5000
|
|
217
236
|
}
|
|
218
237
|
},
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
required: ['selector', 'tabId']
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: 'chrome_get_text',
|
|
243
|
+
description: 'Get the text content of an element matching the CSS selector',
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
selector: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: 'CSS selector for the element'
|
|
250
|
+
},
|
|
251
|
+
tabId: {
|
|
252
|
+
type: 'number',
|
|
253
|
+
description: 'ID of the tab'
|
|
231
254
|
}
|
|
232
255
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
timeout: {
|
|
250
|
-
type: 'number',
|
|
251
|
-
description: 'Maximum time to wait in milliseconds (default: 5000)',
|
|
252
|
-
default: 5000
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
required: ['selector', 'tabId']
|
|
256
|
+
required: ['selector', 'tabId']
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'chrome_click',
|
|
261
|
+
description: 'Click an element matching the CSS selector',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
selector: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
description: 'CSS selector for the element to click'
|
|
268
|
+
},
|
|
269
|
+
tabId: {
|
|
270
|
+
type: 'number',
|
|
271
|
+
description: 'ID of the tab'
|
|
256
272
|
}
|
|
257
273
|
},
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
required: ['selector', 'tabId']
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'chrome_type',
|
|
279
|
+
description: 'Type text into an element matching the CSS selector',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
selector: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
description: 'CSS selector for the input element'
|
|
286
|
+
},
|
|
287
|
+
text: {
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: 'Text to type into the element'
|
|
290
|
+
},
|
|
291
|
+
tabId: {
|
|
292
|
+
type: 'number',
|
|
293
|
+
description: 'ID of the tab'
|
|
274
294
|
}
|
|
275
295
|
},
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
required: ['selector', 'text', 'tabId']
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// JavaScript Execution
|
|
301
|
+
{
|
|
302
|
+
name: 'chrome_execute_js',
|
|
303
|
+
description: 'Execute arbitrary JavaScript code in the page context',
|
|
304
|
+
inputSchema: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: {
|
|
307
|
+
code: {
|
|
308
|
+
type: 'string',
|
|
309
|
+
description: 'JavaScript code to execute'
|
|
310
|
+
},
|
|
311
|
+
tabId: {
|
|
312
|
+
type: 'number',
|
|
313
|
+
description: 'ID of the tab'
|
|
292
314
|
}
|
|
293
315
|
},
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
required: ['code', 'tabId']
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'chrome_call_helper',
|
|
321
|
+
description: 'Call a predefined DOM helper functions that works for CSP-restricted pages too. Available helpers: Element Interaction (clickElement, typeText, appendChar, clearContentEditable), Element Query (getText, getHTML, getLastHTML, elementExists, isVisible, waitForElement), Element Highlighting (highlightElement, removeHighlights), Element Positioning (getElementBounds, scrollElementIntoView), Element Inspection (inspectElement, getContainerElements, extractPageElements)',
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
functionName: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: 'Name of the helper function',
|
|
328
|
+
enum: [
|
|
329
|
+
'clickElement',
|
|
330
|
+
'typeText',
|
|
331
|
+
'appendChar',
|
|
332
|
+
'clearContentEditable',
|
|
333
|
+
'getText',
|
|
334
|
+
'getHTML',
|
|
335
|
+
'getLastHTML',
|
|
336
|
+
'elementExists',
|
|
337
|
+
'isVisible',
|
|
338
|
+
'waitForElement',
|
|
339
|
+
'highlightElement',
|
|
340
|
+
'removeHighlights',
|
|
341
|
+
'getElementBounds',
|
|
342
|
+
'scrollElementIntoView',
|
|
343
|
+
'inspectElement',
|
|
344
|
+
'getContainerElements',
|
|
345
|
+
'extractPageElements'
|
|
346
|
+
]
|
|
347
|
+
},
|
|
348
|
+
args: {
|
|
349
|
+
type: 'array',
|
|
350
|
+
description: 'Arguments to pass to the helper function (as individual parameters, not objects)',
|
|
351
|
+
items: {
|
|
352
|
+
oneOf: [
|
|
353
|
+
{ type: 'string' },
|
|
354
|
+
{ type: 'number' },
|
|
355
|
+
{ type: 'boolean' },
|
|
356
|
+
{ type: 'null' }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
tabId: {
|
|
361
|
+
type: 'number',
|
|
362
|
+
description: 'ID of the tab'
|
|
314
363
|
}
|
|
315
364
|
},
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
365
|
+
required: ['functionName', 'args', 'tabId']
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
// Screenshots
|
|
370
|
+
{
|
|
371
|
+
name: 'chrome_capture_screenshot',
|
|
372
|
+
description: 'Capture a screenshot of the current tab',
|
|
373
|
+
inputSchema: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
properties: {
|
|
376
|
+
format: {
|
|
377
|
+
type: 'string',
|
|
378
|
+
description: 'Image format (png or jpeg)',
|
|
379
|
+
enum: ['png', 'jpeg'],
|
|
380
|
+
default: 'png'
|
|
381
|
+
},
|
|
382
|
+
quality: {
|
|
383
|
+
type: 'number',
|
|
384
|
+
description: 'JPEG quality (0-100, only for jpeg format)',
|
|
385
|
+
minimum: 0,
|
|
386
|
+
maximum: 100,
|
|
387
|
+
default: 90
|
|
334
388
|
}
|
|
335
389
|
},
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
description: 'Arguments to pass to the helper function (as individual parameters, not objects)',
|
|
368
|
-
items: {
|
|
369
|
-
oneOf: [
|
|
370
|
-
{ type: 'string' },
|
|
371
|
-
{ type: 'number' },
|
|
372
|
-
{ type: 'boolean' },
|
|
373
|
-
{ type: 'null' }
|
|
374
|
-
]
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
tabId: {
|
|
378
|
-
type: 'number',
|
|
379
|
-
description: 'ID of the tab'
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
required: ['functionName', 'args', 'tabId']
|
|
390
|
+
required: []
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// Script Injection
|
|
395
|
+
{
|
|
396
|
+
name: 'chrome_register_injection',
|
|
397
|
+
description: 'Register a content script to be injected into matching pages',
|
|
398
|
+
inputSchema: {
|
|
399
|
+
type: 'object',
|
|
400
|
+
properties: {
|
|
401
|
+
id: {
|
|
402
|
+
type: 'string',
|
|
403
|
+
description: 'Unique identifier for this injection'
|
|
404
|
+
},
|
|
405
|
+
code: {
|
|
406
|
+
type: 'string',
|
|
407
|
+
description: 'JavaScript code to inject'
|
|
408
|
+
},
|
|
409
|
+
matches: {
|
|
410
|
+
type: 'array',
|
|
411
|
+
description: 'URL patterns to match (e.g., ["https://*.example.com/*"])',
|
|
412
|
+
items: {
|
|
413
|
+
type: 'string'
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
runAt: {
|
|
417
|
+
type: 'string',
|
|
418
|
+
description: 'When to inject the script',
|
|
419
|
+
enum: ['document_start', 'document_end', 'document_idle'],
|
|
420
|
+
default: 'document_idle'
|
|
383
421
|
}
|
|
384
422
|
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
default: 'png'
|
|
398
|
-
},
|
|
399
|
-
quality: {
|
|
400
|
-
type: 'number',
|
|
401
|
-
description: 'JPEG quality (0-100, only for jpeg format)',
|
|
402
|
-
minimum: 0,
|
|
403
|
-
maximum: 100,
|
|
404
|
-
default: 90
|
|
405
|
-
}
|
|
406
|
-
},
|
|
407
|
-
required: []
|
|
423
|
+
required: ['id', 'code', 'matches']
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'chrome_unregister_injection',
|
|
428
|
+
description: 'Unregister a previously registered content script',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
id: {
|
|
433
|
+
type: 'string',
|
|
434
|
+
description: 'ID of the injection to unregister'
|
|
408
435
|
}
|
|
409
436
|
},
|
|
437
|
+
required: ['id']
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
];
|
|
441
|
+
}
|
|
410
442
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Handle tool invocation
|
|
445
|
+
*/
|
|
446
|
+
async function handleToolCall(name, args) {
|
|
447
|
+
log(`Tool call: ${name}`, args);
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
// Ensure connection is active before executing tool
|
|
451
|
+
await ensureConnected();
|
|
452
|
+
|
|
453
|
+
let result;
|
|
454
|
+
|
|
455
|
+
switch (name) {
|
|
456
|
+
// Tab Management
|
|
457
|
+
case 'chrome_list_tabs':
|
|
458
|
+
result = await client.listTabs();
|
|
459
|
+
break;
|
|
460
|
+
|
|
461
|
+
case 'chrome_open_tab':
|
|
462
|
+
result = await client.openTab(args.url, args.focus);
|
|
463
|
+
break;
|
|
464
|
+
|
|
465
|
+
case 'chrome_navigate_tab':
|
|
466
|
+
result = await client.navigateTab(args.tabId, args.url, args.focus);
|
|
467
|
+
break;
|
|
468
|
+
|
|
469
|
+
case 'chrome_switch_tab':
|
|
470
|
+
result = await client.switchTab(args.tabId);
|
|
471
|
+
break;
|
|
472
|
+
|
|
473
|
+
case 'chrome_close_tab':
|
|
474
|
+
result = await client.closeTab(args.tabId);
|
|
475
|
+
break;
|
|
476
|
+
|
|
477
|
+
case 'chrome_get_active_tab':
|
|
478
|
+
result = await client.getActiveTab();
|
|
479
|
+
break;
|
|
480
|
+
|
|
481
|
+
// Navigation History
|
|
482
|
+
case 'chrome_go_back':
|
|
483
|
+
result = await client.goBack(args.tabId);
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
case 'chrome_go_forward':
|
|
487
|
+
result = await client.goForward(args.tabId);
|
|
488
|
+
break;
|
|
489
|
+
|
|
490
|
+
// DOM Interaction
|
|
491
|
+
case 'chrome_wait_for_element':
|
|
492
|
+
result = await client.waitForElement(args.selector, args.timeout || 5000, args.tabId);
|
|
493
|
+
break;
|
|
494
|
+
|
|
495
|
+
case 'chrome_get_text':
|
|
496
|
+
result = await client.getText(args.selector, args.tabId);
|
|
497
|
+
break;
|
|
498
|
+
|
|
499
|
+
case 'chrome_click':
|
|
500
|
+
result = await client.click(args.selector, args.tabId);
|
|
501
|
+
break;
|
|
502
|
+
|
|
503
|
+
case 'chrome_type':
|
|
504
|
+
result = await client.type(args.selector, args.text, args.tabId);
|
|
505
|
+
break;
|
|
506
|
+
|
|
507
|
+
// JavaScript Execution
|
|
508
|
+
case 'chrome_execute_js':
|
|
509
|
+
result = await client.executeJS(args.code, args.tabId);
|
|
510
|
+
break;
|
|
511
|
+
|
|
512
|
+
case 'chrome_call_helper':
|
|
513
|
+
// Validate that args array contains only primitives, not objects
|
|
514
|
+
if (args.args && Array.isArray(args.args)) {
|
|
515
|
+
for (let i = 0; i < args.args.length; i++) {
|
|
516
|
+
const arg = args.args[i];
|
|
517
|
+
if (arg !== null && typeof arg === 'object') {
|
|
518
|
+
throw new Error(`Invalid argument at index ${i}: expected primitive type (string, number, boolean, null), got object. Use individual parameters like ["form", false], not [{"containerSelector": "form"}]`);
|
|
519
|
+
}
|
|
455
520
|
}
|
|
456
521
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
522
|
+
result = await client.callHelper(args.functionName, args.args, args.tabId);
|
|
523
|
+
break;
|
|
524
|
+
|
|
525
|
+
// Screenshots
|
|
526
|
+
case 'chrome_capture_screenshot':
|
|
527
|
+
result = await client.captureScreenshot({
|
|
528
|
+
format: args.format || 'png',
|
|
529
|
+
quality: args.quality || 90
|
|
530
|
+
});
|
|
531
|
+
break;
|
|
532
|
+
|
|
533
|
+
// Script Injection
|
|
534
|
+
case 'chrome_register_injection':
|
|
535
|
+
result = await client.registerInjection(
|
|
536
|
+
args.id,
|
|
537
|
+
args.code,
|
|
538
|
+
args.matches,
|
|
539
|
+
args.runAt || 'document_idle'
|
|
540
|
+
);
|
|
541
|
+
break;
|
|
542
|
+
|
|
543
|
+
case 'chrome_unregister_injection':
|
|
544
|
+
result = await client.unregisterInjection(args.id);
|
|
545
|
+
break;
|
|
546
|
+
|
|
547
|
+
default:
|
|
548
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
549
|
+
}
|
|
460
550
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
*/
|
|
464
|
-
async handleToolCall(name, args) {
|
|
465
|
-
this.log(`Tool call: ${name}`, args);
|
|
551
|
+
log(`Tool result for ${name}:`, result);
|
|
552
|
+
return result;
|
|
466
553
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
switch (name) {
|
|
474
|
-
// Tab Management
|
|
475
|
-
case 'chrome_list_tabs':
|
|
476
|
-
result = await this.client.listTabs();
|
|
477
|
-
break;
|
|
478
|
-
|
|
479
|
-
case 'chrome_open_tab':
|
|
480
|
-
result = await this.client.openTab(args.url, args.focus);
|
|
481
|
-
break;
|
|
482
|
-
|
|
483
|
-
case 'chrome_navigate_tab':
|
|
484
|
-
result = await this.client.navigateTab(args.tabId, args.url, args.focus);
|
|
485
|
-
break;
|
|
486
|
-
|
|
487
|
-
case 'chrome_switch_tab':
|
|
488
|
-
result = await this.client.switchTab(args.tabId);
|
|
489
|
-
break;
|
|
490
|
-
|
|
491
|
-
case 'chrome_close_tab':
|
|
492
|
-
result = await this.client.closeTab(args.tabId);
|
|
493
|
-
break;
|
|
494
|
-
|
|
495
|
-
case 'chrome_get_active_tab':
|
|
496
|
-
result = await this.client.getActiveTab();
|
|
497
|
-
break;
|
|
498
|
-
|
|
499
|
-
// Navigation History
|
|
500
|
-
case 'chrome_go_back':
|
|
501
|
-
result = await this.client.goBack(args.tabId);
|
|
502
|
-
break;
|
|
503
|
-
|
|
504
|
-
case 'chrome_go_forward':
|
|
505
|
-
result = await this.client.goForward(args.tabId);
|
|
506
|
-
break;
|
|
507
|
-
|
|
508
|
-
// DOM Interaction
|
|
509
|
-
case 'chrome_wait_for_element':
|
|
510
|
-
result = await this.client.waitForElement(args.selector, args.timeout || 5000, args.tabId);
|
|
511
|
-
break;
|
|
512
|
-
|
|
513
|
-
case 'chrome_get_text':
|
|
514
|
-
result = await this.client.getText(args.selector, args.tabId);
|
|
515
|
-
break;
|
|
516
|
-
|
|
517
|
-
case 'chrome_click':
|
|
518
|
-
result = await this.client.click(args.selector, args.tabId);
|
|
519
|
-
break;
|
|
520
|
-
|
|
521
|
-
case 'chrome_type':
|
|
522
|
-
result = await this.client.type(args.selector, args.text, args.tabId);
|
|
523
|
-
break;
|
|
524
|
-
|
|
525
|
-
// JavaScript Execution
|
|
526
|
-
case 'chrome_execute_js':
|
|
527
|
-
result = await this.client.executeJS(args.code, args.tabId);
|
|
528
|
-
break;
|
|
529
|
-
|
|
530
|
-
case 'chrome_call_helper':
|
|
531
|
-
// Validate that args array contains only primitives, not objects
|
|
532
|
-
if (args.args && Array.isArray(args.args)) {
|
|
533
|
-
for (let i = 0; i < args.args.length; i++) {
|
|
534
|
-
const arg = args.args[i];
|
|
535
|
-
if (arg !== null && typeof arg === 'object') {
|
|
536
|
-
throw new Error(`Invalid argument at index ${i}: expected primitive type (string, number, boolean, null), got object. Use individual parameters like ["form", false], not [{"containerSelector": "form"}]`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
result = await this.client.callHelper(args.functionName, args.args, args.tabId);
|
|
541
|
-
break;
|
|
542
|
-
|
|
543
|
-
// Screenshots
|
|
544
|
-
case 'chrome_capture_screenshot':
|
|
545
|
-
result = await this.client.captureScreenshot({
|
|
546
|
-
format: args.format || 'png',
|
|
547
|
-
quality: args.quality || 90
|
|
548
|
-
});
|
|
549
|
-
break;
|
|
550
|
-
|
|
551
|
-
// Script Injection
|
|
552
|
-
case 'chrome_register_injection':
|
|
553
|
-
result = await this.client.registerInjection(
|
|
554
|
-
args.id,
|
|
555
|
-
args.code,
|
|
556
|
-
args.matches,
|
|
557
|
-
args.runAt || 'document_idle'
|
|
558
|
-
);
|
|
559
|
-
break;
|
|
560
|
-
|
|
561
|
-
case 'chrome_unregister_injection':
|
|
562
|
-
result = await this.client.unregisterInjection(args.id);
|
|
563
|
-
break;
|
|
564
|
-
|
|
565
|
-
default:
|
|
566
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
this.log(`Tool result for ${name}:`, result);
|
|
570
|
-
return result;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
log(`Tool error for ${name}:`, error.message);
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
571
559
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Main server initialization
|
|
562
|
+
*/
|
|
563
|
+
async function main() {
|
|
564
|
+
log('Starting MCP server with official SDK...');
|
|
565
|
+
|
|
566
|
+
// Connect to browser-link-server
|
|
567
|
+
await initializeClient();
|
|
568
|
+
|
|
569
|
+
// Create MCP Server instance
|
|
570
|
+
const server = new Server({
|
|
571
|
+
name: 'chrome-link',
|
|
572
|
+
version: packageJson.version
|
|
573
|
+
}, {
|
|
574
|
+
capabilities: {
|
|
575
|
+
tools: {}
|
|
575
576
|
}
|
|
576
|
-
}
|
|
577
|
+
});
|
|
577
578
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
579
|
+
// Register tools/list handler
|
|
580
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
581
|
+
return {
|
|
582
|
+
tools: getTools()
|
|
583
|
+
};
|
|
584
|
+
});
|
|
583
585
|
|
|
586
|
+
// Register tools/call handler
|
|
587
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
588
|
+
const { name, arguments: args } = request.params;
|
|
589
|
+
|
|
584
590
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
591
|
+
const result = await handleToolCall(name, args || {});
|
|
592
|
+
|
|
593
|
+
// Special handling for screenshots - return as image content
|
|
594
|
+
if (name === 'chrome_capture_screenshot' && result.dataUrl) {
|
|
595
|
+
// Extract base64 data and mime type from data URL
|
|
596
|
+
// Format: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
|
|
597
|
+
const match = result.dataUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
598
|
+
if (match) {
|
|
599
|
+
const [, mimeType, base64Data] = match;
|
|
600
|
+
return {
|
|
601
|
+
content: [
|
|
602
|
+
{
|
|
603
|
+
type: 'image',
|
|
604
|
+
data: base64Data,
|
|
605
|
+
mimeType: mimeType
|
|
598
606
|
}
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
case 'tools/list':
|
|
604
|
-
this.sendResponse({
|
|
605
|
-
jsonrpc: '2.0',
|
|
606
|
-
id,
|
|
607
|
-
result: this.getTools()
|
|
608
|
-
});
|
|
609
|
-
break;
|
|
610
|
-
|
|
611
|
-
case 'tools/call':
|
|
612
|
-
const result = await this.handleToolCall(params.name, params.arguments || {});
|
|
613
|
-
this.sendResponse({
|
|
614
|
-
jsonrpc: '2.0',
|
|
615
|
-
id,
|
|
616
|
-
result: {
|
|
617
|
-
content: [
|
|
618
|
-
{
|
|
619
|
-
type: 'text',
|
|
620
|
-
text: JSON.stringify(result, null, 2)
|
|
621
|
-
}
|
|
622
|
-
]
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
break;
|
|
626
|
-
|
|
627
|
-
default:
|
|
628
|
-
this.sendError(id, -32601, `Method not found: ${method}`);
|
|
607
|
+
]
|
|
608
|
+
};
|
|
609
|
+
}
|
|
629
610
|
}
|
|
630
|
-
} catch (error) {
|
|
631
|
-
this.sendError(id, -32603, error.message);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Start the MCP server
|
|
637
|
-
*/
|
|
638
|
-
async start() {
|
|
639
|
-
this.log('Starting MCP server...');
|
|
640
|
-
|
|
641
|
-
// Connect to browser-link-server
|
|
642
|
-
await this.initialize();
|
|
643
|
-
|
|
644
|
-
// Handle stdin for MCP protocol
|
|
645
|
-
let buffer = '';
|
|
646
|
-
process.stdin.on('data', (chunk) => {
|
|
647
|
-
buffer += chunk.toString();
|
|
648
611
|
|
|
649
|
-
//
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
const request = JSON.parse(line);
|
|
657
|
-
this.handleRequest(request);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
this.log('Failed to parse request:', error.message);
|
|
612
|
+
// For all other tools, return as text (without pretty-printing to reduce size)
|
|
613
|
+
return {
|
|
614
|
+
content: [
|
|
615
|
+
{
|
|
616
|
+
type: 'text',
|
|
617
|
+
text: JSON.stringify(result)
|
|
660
618
|
}
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
// Handle process signals
|
|
671
|
-
process.on('SIGINT', () => {
|
|
672
|
-
this.log('Received SIGINT, shutting down...');
|
|
673
|
-
this.shutdown();
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
process.on('SIGTERM', () => {
|
|
677
|
-
this.log('Received SIGTERM, shutting down...');
|
|
678
|
-
this.shutdown();
|
|
679
|
-
});
|
|
619
|
+
]
|
|
620
|
+
};
|
|
621
|
+
} catch (error) {
|
|
622
|
+
log(`Error handling tool call ${name}:`, error.message);
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
680
626
|
|
|
681
|
-
|
|
682
|
-
|
|
627
|
+
// Handle process signals for graceful shutdown
|
|
628
|
+
process.on('SIGINT', async () => {
|
|
629
|
+
log('Received SIGINT, shutting down...');
|
|
630
|
+
if (client) {
|
|
631
|
+
client.close();
|
|
632
|
+
}
|
|
633
|
+
await server.close();
|
|
634
|
+
process.exit(0);
|
|
635
|
+
});
|
|
683
636
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (this.client) {
|
|
689
|
-
this.client.close();
|
|
637
|
+
process.on('SIGTERM', async () => {
|
|
638
|
+
log('Received SIGTERM, shutting down...');
|
|
639
|
+
if (client) {
|
|
640
|
+
client.close();
|
|
690
641
|
}
|
|
642
|
+
await server.close();
|
|
691
643
|
process.exit(0);
|
|
692
|
-
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Connect transport and start server
|
|
647
|
+
const transport = new StdioServerTransport();
|
|
648
|
+
await server.connect(transport);
|
|
649
|
+
|
|
650
|
+
log('MCP server started successfully with SDK');
|
|
693
651
|
}
|
|
694
652
|
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
server.start().catch((error) => {
|
|
653
|
+
// Run the server
|
|
654
|
+
main().catch((error) => {
|
|
698
655
|
console.error('[MCP Server] Fatal error:', error);
|
|
699
656
|
process.exit(1);
|
|
700
657
|
});
|
|
658
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikeymouse/chromelink-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Chrome browser automation via AI agents",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"bin": {
|
|
7
8
|
"chromelink-mcp": "./bin/chromelink-mcp"
|
|
@@ -39,7 +40,8 @@
|
|
|
39
40
|
"node": ">=16.0.0"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@aikeymouse/chromelink-client": "^1.2.
|
|
43
|
+
"@aikeymouse/chromelink-client": "^1.2.3",
|
|
44
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
43
45
|
"ws": "^8.16.0"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {},
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* where VERSION comes from the VERSION file in the project root.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
17
21
|
|
|
18
22
|
const PACKAGE_JSON = path.join(__dirname, '..', 'package.json');
|
|
19
23
|
const BACKUP_FILE = PACKAGE_JSON + '.bak';
|