@dwao/live-browser-mcp 1.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.
Files changed (2) hide show
  1. package/index.js +197 -0
  2. package/package.json +32 -0
package/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @dwao/live-browser-mcp
5
+ *
6
+ * Stdio MCP server that exposes Playwright-compatible browser_* tools,
7
+ * proxying each call through HTTP to a backend relay which forwards
8
+ * via WebSocket to a Chrome extension's CDP handler on the user's real tab.
9
+ *
10
+ * Environment variables:
11
+ * BACKEND_URL - Backend URL (e.g., https://evolve-api.dwao.com)
12
+ * TAB_ID - Chrome tab ID to target
13
+ * INTERNAL_KEY - Shared secret for internal API authentication
14
+ */
15
+
16
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
17
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
+ import {
19
+ CallToolRequestSchema,
20
+ ListToolsRequestSchema
21
+ } from '@modelcontextprotocol/sdk/types.js';
22
+
23
+ const BACKEND_URL = process.env.BACKEND_URL;
24
+ const TAB_ID = process.env.TAB_ID;
25
+ const INTERNAL_KEY = process.env.INTERNAL_KEY || '';
26
+ const ACTION_TIMEOUT = 30_000;
27
+
28
+ if (!BACKEND_URL || !TAB_ID) {
29
+ console.error('BACKEND_URL and TAB_ID environment variables are required');
30
+ process.exit(1);
31
+ }
32
+
33
+ // ============================================================================
34
+ // Tool definitions — mirror Playwright MCP's browser_* tools
35
+ // ============================================================================
36
+
37
+ const TOOLS = [
38
+ {
39
+ name: 'browser_navigate',
40
+ description: 'Navigate the browser to a URL',
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ url: { type: 'string', description: 'URL to navigate to' }
45
+ },
46
+ required: ['url']
47
+ }
48
+ },
49
+ {
50
+ name: 'browser_snapshot',
51
+ description: 'Capture accessibility snapshot of the current page. Returns a text representation of the page with element references like [ref=e1].',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {}
55
+ }
56
+ },
57
+ {
58
+ name: 'browser_evaluate',
59
+ description: 'Execute JavaScript in the browser console',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: {
63
+ expression: { type: 'string', description: 'JavaScript expression to evaluate' }
64
+ },
65
+ required: ['expression']
66
+ }
67
+ },
68
+ {
69
+ name: 'browser_screenshot',
70
+ description: 'Take a screenshot of the current page',
71
+ inputSchema: {
72
+ type: 'object',
73
+ properties: {}
74
+ }
75
+ },
76
+ {
77
+ name: 'browser_click',
78
+ description: 'Click on an element identified by reference from a snapshot',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {
82
+ element: { type: 'string', description: 'Human-readable element description' },
83
+ ref: { type: 'string', description: 'Element reference from snapshot (e.g., "e5")' }
84
+ },
85
+ required: ['element', 'ref']
86
+ }
87
+ },
88
+ {
89
+ name: 'browser_type',
90
+ description: 'Type text into an element identified by reference from a snapshot',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ element: { type: 'string', description: 'Human-readable element description' },
95
+ ref: { type: 'string', description: 'Element reference from snapshot (e.g., "e5")' },
96
+ text: { type: 'string', description: 'Text to type' },
97
+ submit: { type: 'boolean', description: 'Whether to press Enter after typing' }
98
+ },
99
+ required: ['element', 'ref', 'text']
100
+ }
101
+ },
102
+ {
103
+ name: 'browser_scroll',
104
+ description: 'Scroll the page up or down',
105
+ inputSchema: {
106
+ type: 'object',
107
+ properties: {
108
+ direction: { type: 'string', enum: ['up', 'down'], description: 'Scroll direction' },
109
+ amount: { type: 'number', description: 'Pixels to scroll (default 300)' }
110
+ },
111
+ required: ['direction']
112
+ }
113
+ }
114
+ ];
115
+
116
+ // ============================================================================
117
+ // HTTP proxy to backend browser-action endpoint
118
+ // ============================================================================
119
+
120
+ async function sendBrowserAction(action, params = {}) {
121
+ const controller = new AbortController();
122
+ const timeout = setTimeout(() => controller.abort(), ACTION_TIMEOUT);
123
+
124
+ try {
125
+ const res = await fetch(`${BACKEND_URL}/api/browser-action`, {
126
+ method: 'POST',
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ 'X-Internal-Key': INTERNAL_KEY
130
+ },
131
+ body: JSON.stringify({ tabId: TAB_ID, action, params }),
132
+ signal: controller.signal
133
+ });
134
+
135
+ if (!res.ok) {
136
+ const body = await res.text();
137
+ throw new Error(`Browser action failed (${res.status}): ${body}`);
138
+ }
139
+
140
+ return await res.json();
141
+ } finally {
142
+ clearTimeout(timeout);
143
+ }
144
+ }
145
+
146
+ // ============================================================================
147
+ // MCP Server setup
148
+ // ============================================================================
149
+
150
+ const server = new Server(
151
+ { name: '@dwao/live-browser-mcp', version: '1.0.0' },
152
+ { capabilities: { tools: {} } }
153
+ );
154
+
155
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
156
+ return { tools: TOOLS };
157
+ });
158
+
159
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
160
+ const { name, arguments: args } = request.params;
161
+
162
+ try {
163
+ const result = await sendBrowserAction(name, args || {});
164
+
165
+ // Screenshot returns base64 image
166
+ if (name === 'browser_screenshot' && result.screenshot) {
167
+ return {
168
+ content: [
169
+ {
170
+ type: 'image',
171
+ data: result.screenshot,
172
+ mimeType: 'image/png'
173
+ }
174
+ ]
175
+ };
176
+ }
177
+
178
+ // All other tools return text
179
+ const text = typeof result.result === 'string'
180
+ ? result.result
181
+ : JSON.stringify(result.result, null, 2);
182
+
183
+ return {
184
+ content: [{ type: 'text', text }]
185
+ };
186
+
187
+ } catch (error) {
188
+ return {
189
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
190
+ isError: true
191
+ };
192
+ }
193
+ });
194
+
195
+ // Start the server
196
+ const transport = new StdioServerTransport();
197
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@dwao/live-browser-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server that proxies browser tools to a real Chrome tab via CDP through a WebSocket relay",
5
+ "type": "module",
6
+ "bin": {
7
+ "live-browser-mcp": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js"
11
+ ],
12
+ "keywords": [
13
+ "mcp",
14
+ "mcp-server",
15
+ "chrome",
16
+ "cdp",
17
+ "browser",
18
+ "devtools",
19
+ "playwright-compatible"
20
+ ],
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/nicholasgma/live-browser-mcp"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.0"
31
+ }
32
+ }