@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.
- package/index.js +197 -0
- 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
|
+
}
|