@different-ai/opencode-browser 1.0.4 → 2.0.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/src/server.js DELETED
@@ -1,379 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MCP Server for Browser Automation
4
- *
5
- * This server exposes browser automation tools to OpenCode via MCP.
6
- * It connects to the native messaging host via Unix socket to execute commands.
7
- */
8
-
9
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
- import {
12
- CallToolRequestSchema,
13
- ListToolsRequestSchema,
14
- } from "@modelcontextprotocol/sdk/types.js";
15
- import { createConnection } from "net";
16
- import { homedir } from "os";
17
- import { join } from "path";
18
-
19
- const SOCKET_PATH = join(homedir(), ".opencode-browser", "browser.sock");
20
-
21
- // ============================================================================
22
- // Socket Connection to Native Host
23
- // ============================================================================
24
-
25
- let socket = null;
26
- let connected = false;
27
- let pendingRequests = new Map();
28
- let requestId = 0;
29
- let buffer = "";
30
-
31
- function connectToHost() {
32
- return new Promise((resolve, reject) => {
33
- socket = createConnection(SOCKET_PATH);
34
-
35
- socket.on("connect", () => {
36
- console.error("[browser-mcp] Connected to native host");
37
- connected = true;
38
- resolve();
39
- });
40
-
41
- socket.on("data", (data) => {
42
- buffer += data.toString();
43
-
44
- const lines = buffer.split("\n");
45
- buffer = lines.pop() || "";
46
-
47
- for (const line of lines) {
48
- if (line.trim()) {
49
- try {
50
- const message = JSON.parse(line);
51
- handleHostMessage(message);
52
- } catch (e) {
53
- console.error("[browser-mcp] Failed to parse:", e.message);
54
- }
55
- }
56
- }
57
- });
58
-
59
- socket.on("close", () => {
60
- console.error("[browser-mcp] Disconnected from native host");
61
- connected = false;
62
- // Reject all pending requests
63
- for (const [id, { reject }] of pendingRequests) {
64
- reject(new Error("Connection closed"));
65
- }
66
- pendingRequests.clear();
67
- });
68
-
69
- socket.on("error", (err) => {
70
- console.error("[browser-mcp] Socket error:", err.message);
71
- if (!connected) {
72
- reject(err);
73
- }
74
- });
75
- });
76
- }
77
-
78
- function handleHostMessage(message) {
79
- if (message.type === "tool_response") {
80
- const pending = pendingRequests.get(message.id);
81
- if (pending) {
82
- pendingRequests.delete(message.id);
83
- if (message.error) {
84
- pending.reject(new Error(message.error.content));
85
- } else {
86
- pending.resolve(message.result.content);
87
- }
88
- }
89
- }
90
- }
91
-
92
- async function executeTool(tool, args) {
93
- if (!connected) {
94
- // Try to reconnect
95
- try {
96
- await connectToHost();
97
- } catch {
98
- throw new Error("Not connected to browser extension. Make sure Chrome is running with the OpenCode extension installed.");
99
- }
100
- }
101
-
102
- const id = ++requestId;
103
-
104
- return new Promise((resolve, reject) => {
105
- pendingRequests.set(id, { resolve, reject });
106
-
107
- socket.write(JSON.stringify({
108
- type: "tool_request",
109
- id,
110
- tool,
111
- args
112
- }) + "\n");
113
-
114
- // Timeout after 60 seconds
115
- setTimeout(() => {
116
- if (pendingRequests.has(id)) {
117
- pendingRequests.delete(id);
118
- reject(new Error("Tool execution timed out"));
119
- }
120
- }, 60000);
121
- });
122
- }
123
-
124
- // ============================================================================
125
- // MCP Server
126
- // ============================================================================
127
-
128
- const server = new Server(
129
- {
130
- name: "browser-mcp",
131
- version: "1.0.0",
132
- },
133
- {
134
- capabilities: {
135
- tools: {},
136
- },
137
- }
138
- );
139
-
140
- // List available tools
141
- server.setRequestHandler(ListToolsRequestSchema, async () => {
142
- return {
143
- tools: [
144
- {
145
- name: "browser_navigate",
146
- description: "Navigate to a URL in the browser",
147
- inputSchema: {
148
- type: "object",
149
- properties: {
150
- url: {
151
- type: "string",
152
- description: "The URL to navigate to"
153
- },
154
- tabId: {
155
- type: "number",
156
- description: "Optional tab ID. Uses active tab if not specified."
157
- }
158
- },
159
- required: ["url"]
160
- }
161
- },
162
- {
163
- name: "browser_click",
164
- description: "Click an element on the page using a CSS selector",
165
- inputSchema: {
166
- type: "object",
167
- properties: {
168
- selector: {
169
- type: "string",
170
- description: "CSS selector for the element to click"
171
- },
172
- tabId: {
173
- type: "number",
174
- description: "Optional tab ID"
175
- }
176
- },
177
- required: ["selector"]
178
- }
179
- },
180
- {
181
- name: "browser_type",
182
- description: "Type text into an input element",
183
- inputSchema: {
184
- type: "object",
185
- properties: {
186
- selector: {
187
- type: "string",
188
- description: "CSS selector for the input element"
189
- },
190
- text: {
191
- type: "string",
192
- description: "Text to type"
193
- },
194
- clear: {
195
- type: "boolean",
196
- description: "Clear the field before typing"
197
- },
198
- tabId: {
199
- type: "number",
200
- description: "Optional tab ID"
201
- }
202
- },
203
- required: ["selector", "text"]
204
- }
205
- },
206
- {
207
- name: "browser_screenshot",
208
- description: "Take a screenshot of the current page",
209
- inputSchema: {
210
- type: "object",
211
- properties: {
212
- tabId: {
213
- type: "number",
214
- description: "Optional tab ID"
215
- },
216
- fullPage: {
217
- type: "boolean",
218
- description: "Capture full page (not yet implemented)"
219
- }
220
- }
221
- }
222
- },
223
- {
224
- name: "browser_snapshot",
225
- description: "Get an accessibility tree snapshot of the page. Returns interactive elements with selectors.",
226
- inputSchema: {
227
- type: "object",
228
- properties: {
229
- tabId: {
230
- type: "number",
231
- description: "Optional tab ID"
232
- }
233
- }
234
- }
235
- },
236
- {
237
- name: "browser_get_tabs",
238
- description: "List all open browser tabs",
239
- inputSchema: {
240
- type: "object",
241
- properties: {}
242
- }
243
- },
244
- {
245
- name: "browser_scroll",
246
- description: "Scroll the page or scroll an element into view",
247
- inputSchema: {
248
- type: "object",
249
- properties: {
250
- selector: {
251
- type: "string",
252
- description: "CSS selector to scroll into view"
253
- },
254
- x: {
255
- type: "number",
256
- description: "Horizontal scroll amount in pixels"
257
- },
258
- y: {
259
- type: "number",
260
- description: "Vertical scroll amount in pixels"
261
- },
262
- tabId: {
263
- type: "number",
264
- description: "Optional tab ID"
265
- }
266
- }
267
- }
268
- },
269
- {
270
- name: "browser_wait",
271
- description: "Wait for a specified duration",
272
- inputSchema: {
273
- type: "object",
274
- properties: {
275
- ms: {
276
- type: "number",
277
- description: "Milliseconds to wait (default: 1000)"
278
- }
279
- }
280
- }
281
- },
282
- {
283
- name: "browser_execute",
284
- description: "Execute JavaScript code in the page context",
285
- inputSchema: {
286
- type: "object",
287
- properties: {
288
- code: {
289
- type: "string",
290
- description: "JavaScript code to execute"
291
- },
292
- tabId: {
293
- type: "number",
294
- description: "Optional tab ID"
295
- }
296
- },
297
- required: ["code"]
298
- }
299
- }
300
- ]
301
- };
302
- });
303
-
304
- // Handle tool calls
305
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
306
- const { name, arguments: args } = request.params;
307
-
308
- // Map MCP tool names to internal tool names
309
- const toolMap = {
310
- browser_navigate: "navigate",
311
- browser_click: "click",
312
- browser_type: "type",
313
- browser_screenshot: "screenshot",
314
- browser_snapshot: "snapshot",
315
- browser_get_tabs: "get_tabs",
316
- browser_scroll: "scroll",
317
- browser_wait: "wait",
318
- browser_execute: "execute_script"
319
- };
320
-
321
- const internalTool = toolMap[name];
322
- if (!internalTool) {
323
- return {
324
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
325
- isError: true
326
- };
327
- }
328
-
329
- try {
330
- const result = await executeTool(internalTool, args || {});
331
-
332
- // Handle screenshot specially - return as image
333
- if (internalTool === "screenshot" && result.startsWith("data:image")) {
334
- const base64Data = result.replace(/^data:image\/\w+;base64,/, "");
335
- return {
336
- content: [
337
- {
338
- type: "image",
339
- data: base64Data,
340
- mimeType: "image/png"
341
- }
342
- ]
343
- };
344
- }
345
-
346
- return {
347
- content: [{ type: "text", text: result }]
348
- };
349
- } catch (error) {
350
- return {
351
- content: [{ type: "text", text: `Error: ${error.message}` }],
352
- isError: true
353
- };
354
- }
355
- });
356
-
357
- // ============================================================================
358
- // Main
359
- // ============================================================================
360
-
361
- async function main() {
362
- // Try to connect to native host
363
- try {
364
- await connectToHost();
365
- } catch (error) {
366
- console.error("[browser-mcp] Warning: Could not connect to native host:", error.message);
367
- console.error("[browser-mcp] Will retry on first tool call");
368
- }
369
-
370
- // Start MCP server
371
- const transport = new StdioServerTransport();
372
- await server.connect(transport);
373
- console.error("[browser-mcp] MCP server started");
374
- }
375
-
376
- main().catch((error) => {
377
- console.error("[browser-mcp] Fatal error:", error);
378
- process.exit(1);
379
- });