@bsbofmusic/agent-browser-mcp-opencode 1.0.1 → 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/README.md CHANGED
@@ -1,80 +1,54 @@
1
1
  # @bsbofmusic/agent-browser-mcp-opencode
2
2
 
3
- MCP Server for Vercel agent-browser - browser automation CLI for AI agents.
3
+ <div align="center">
4
4
 
5
- ---
6
-
7
- ## What is This?
5
+ **MCP Server for agent-browser**
6
+ <sub><sup>Browser automation for• AI agents</sup></sub>
8
7
 
9
- This is an MCP (Model Context Protocol) wrapper for [agent-browser](https://github.com/vercel-labs/agent-browser), providing browser automation capabilities for AI assistants like OpenCode, Claude Code, and others.
8
+ [![MCP](https://img.shields.io/badge/MCP-blue)](https://img.shields.io/badge/MCP-blue)
9
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://img.shields.io/badge/License-Apache%202.0-blue)
10
+ [![Node](https://img.shields.io/badge/node-%3E218.0.0-blue)](https://img.shields.io/badge/node-%3E218.0.0-blue)
11
+ [![NPM](https://img.shields.io/npm/v/@bsbofmusic%2Fagent-browser-mcp-opencode)](https://img.shields.io/npm/v/@bsbofmusic%2Fagent-browser-mcp-opencode)
10
12
 
11
- ### Use Cases
12
-
13
- - **AI Testing**: Automate web testing workflows with AI agents
14
- - **Web Scraping**: Extract data from dynamic web pages
15
- - **Form Automation**: Fill forms, click buttons, navigate pages
16
- - **Visual Testing**: Take screenshots, compare snapshots
13
+ </div>
17
14
 
18
15
  ---
19
16
 
20
- ## One-Click Enable
17
+ <div align="center">
21
18
 
22
- ### OpenCode Configuration
19
+ **Browser automation for AI agents via Model Context Protocol**
23
20
 
24
- Add to your `opencode.json`:
21
+ </div>
25
22
 
26
- ```json
27
- {
28
- "mcp": {
29
- "agent-browser-mcp-opencode": {
30
- "command": ["npx", "-y", "@bsbofmusic/agent-browser-mcp-opencode"],
31
- "enabled": true,
32
- "type": "local"
33
- }
34
- }
35
- }
36
- ```
23
+ ---
37
24
 
38
- ### Direct Run
25
+ ## Quick Start
39
26
 
40
27
  ```bash
41
28
  npx @bsbofmusic/agent-browser-mcp-opencode
42
29
  ```
43
30
 
44
- ### Docker
45
-
46
- ```bash
47
- docker run -it --rm bsbofmusic/agent-browser-mcp-opencode
48
- ```
49
-
50
- ### Global Install
51
-
52
- ```bash
53
- npm install -g @bsbofmusic/agent-browser-mcp-opencode
54
- agent-browser-mcp-opencode
55
- ```
56
-
57
31
  ---
58
32
 
59
- ## Tool List
33
+ ## Tools
60
34
 
61
35
  ### Core Tools
62
36
 
63
37
  | Tool | Description |
64
38
  |------|-------------|
65
- | `browser_ensure` | Manually trigger full repair (install/upgrade) |
66
- | `browser_doctor` | Diagnose issues with actionable nextSteps |
39
+ | `browser_ensure` | Install/repair agent-browser & Chromium |
40
+ | `browser_doctor` | Diagnose issues with fix recommendations |
67
41
  | `browser_help` | List all available commands |
68
42
  | `browser_version` | Show version info |
69
43
  | `browser_exec` | Execute any agent-browser CLI command |
70
44
 
71
- ### Structured Actions
45
+ ### Action Tools
72
46
 
73
47
  | Tool | Description |
74
48
  |------|-------------|
75
49
  | `browser_open` | Navigate to URL |
76
50
  | `browser_snapshot` | Get accessibility tree with refs |
77
- | `browser_click` | Click element (@e1 or CSS selector) |
51
+ | `browser_click` | Click element |
78
52
  | `browser_fill` | Fill form field |
79
53
  | `browser_screenshot` | Take screenshot |
80
54
  | `browser_close` | Close browser |
@@ -85,69 +59,49 @@ agent-browser-mcp-opencode
85
59
 
86
60
  ---
87
61
 
88
- ## Schema Examples
62
+ ## Usage Examples
89
63
 
90
- ### browser_open
64
+ ### Basic Workflow
91
65
 
92
- **Input:**
93
66
  ```json
94
- {
95
- "name": "browser_open",
96
- "arguments": {"url": "https://example.com"}
97
- }
98
- ```
67
+ // 1. Navigate
68
+ {"name": "browser_open", "arguments": {"url": "https://example.com"}}
99
69
 
100
- **Output:**
101
- ```json
102
- {
103
- "ok": true,
104
- "url": "https://example.com",
105
- "duration": 2341
106
- }
107
- ```
70
+ // 2. Get interactive elements
71
+ {"name": "browser_snapshot", "arguments": {"interactive": true}}
108
72
 
109
- ### browser_snapshot
110
-
111
- **Input:**
112
- ```json
113
- {
114
- "name": "browser_snapshot",
115
- "arguments": {"interactive": true, "compact": true}
116
- }
73
+ // 3. Click element
74
+ {"name": "browser_click", "arguments": {"selector": "@e1"}}
117
75
  ```
118
76
 
119
- **Output:**
77
+ ### Semantic Locator
78
+
120
79
  ```json
121
80
  {
122
- "ok": true,
123
- "output": {
124
- "success": true,
125
- "data": {
126
- "snapshot": "- heading \"Example\" [ref=e1]\n - button \"Submit\" [ref=e2]"
127
- }
128
- },
129
- "duration": 1234
81
+ "name": "browser_find",
82
+ "arguments": {
83
+ "type": "role",
84
+ "value": "button",
85
+ "action": "click",
86
+ "name": "Submit"
87
+ }
130
88
  }
131
89
  ```
132
90
 
133
- ### browser_exec
91
+ ### Using browser_exec
134
92
 
135
- **Input:**
136
93
  ```json
137
94
  {
138
95
  "name": "browser_exec",
139
96
  "arguments": {
140
- "command": "open example.com && snapshot -i",
141
- "timeoutMs": 60000
97
+ "command": "open example.com && snapshot -i && click #submit"
142
98
  }
143
99
  }
144
100
  ```
145
101
 
146
102
  ---
147
103
 
148
- ## Auto-Update Strategy
149
-
150
- ### Environment Variables
104
+ ## Auto-Update
151
105
 
152
106
  | Variable | Default | Description |
153
107
  |----------|---------|-------------|
@@ -155,66 +109,47 @@ agent-browser-mcp-opencode
155
109
  | `UPDATE_STRATEGY` | `simple` | `atomic` or `simple` |
156
110
  | `LOG_LEVEL` | `info` | `error`, `warn`, `info`, `debug` |
157
111
 
158
- ### Strategies
159
-
160
- **Simple (default):**
161
- - Direct overwrite installation
162
- - No rollback capability
163
-
164
- **Atomic:**
165
- - Uses `runtime_active/` + `runtime_staging/` directories
166
- - Validates before switching
167
- - Rolls back on failure
168
-
169
112
  ---
170
113
 
171
114
  ## Runtime Directories
172
115
 
173
- | Directory | Location | Purpose |
116
+ | Directory | Location |
174
117
  |-----------|----------|---------|
175
118
  | Config | `~/.agent-browser/` | User configuration |
176
119
  | Sessions | `~/.agent-browser/sessions/` | Saved auth states |
177
120
  | Cache | System temp | Temporary files |
178
121
 
179
- These directories are auto-created on first run and are rebuildable.
180
-
181
122
  ---
182
123
 
183
124
  ## Troubleshooting
184
125
 
185
- ### Run browser_doctor
126
+ ### Run Diagnostics
186
127
 
187
128
  ```json
188
- {
189
- "name": "browser_doctor"
190
- }
129
+ {"name": "browser_doctor"}
191
130
  ```
192
131
 
193
- **Output includes:**
194
- - Issue categories: network/permissions/dependencies/system_libraries/executable/browser/auth
195
- - Severity: critical/high/medium
196
- - nextSteps: actionable fix recommendations
132
+ **Common Issues**
197
133
 
198
- ### Common Issues
134
+ | Issue | Solution |
135
+ |-------|----------|
136
+ | agent-browser not installed | Run `browser_ensure` or `npx agent-browser install` |
137
+ | Chromium not installed | Run `npx agent-browser install` |
138
+ | Permission errors | Check npm global directory permissions |
199
139
 
200
- 1. **agent-browser not installed**
201
- - Run: `browser_ensure`
202
- - Or: `npx agent-browser install`
203
-
204
- 2. **Chromium not installed**
205
- - Run: `npx agent-browser install`
140
+ ---
206
141
 
207
- 3. **Permission errors**
208
- - Check npm global directory permissions
209
- - Consider using npx instead
142
+ ## Platform Compatibility
210
143
 
211
- 4. **Network issues**
212
- - Set npm mirror: `npm config set registry https://registry.npmmirror.com`
213
- - Check proxy: `npm config get proxy`
144
+ | Platform | Status | Notes |
145
+ |--------|--------|-------|
146
+ | Windows | ⚠️ Partial | agent-browser daemon has socket issues. Use npx or Playwright as alternative |
147
+ | macOS | ✅ Full | Native binary works perfectly |
148
+ | Linux | ✅ Full | Native binary works perfectly |
214
149
 
215
150
  ---
216
151
 
217
- ## Verification Plan
152
+ ## Verification
218
153
 
219
154
  ### 6.1 One-Click Deployment
220
155
 
@@ -222,17 +157,13 @@ These directories are auto-created on first run and are rebuildable.
222
157
  npx @bsbofmusic/agent-browser-mcp-opencode
223
158
  ```
224
159
 
225
- **Expected:** Auto-installs dependencies without manual steps.
160
+ **Expected**: Auto-installs dependencies without manual steps.
226
161
 
227
162
  ### 6.2 Capability Coverage
228
163
 
229
- Test with 5+ core commands:
230
- - `browser_open` navigates to URL
231
- - `browser_snapshot` returns accessibility tree with refs
232
- - `browser_click @e1` → clicks element
233
- - `browser_fill @e2 "test"` → fills form
234
- - `browser_screenshot` → captures image
235
- - `browser_exec` → passes through all CLI commands
164
+ All 15 tools available:
165
+ - 4 core management tools (ensure/doctor/help/version/exec)
166
+ - 11 structured action tools (open/snapshot/click/fill/screenshot/close/get_text/find/wait/tab)
236
167
 
237
168
  ### 6.3 Stability
238
169
 
@@ -247,8 +178,7 @@ Test with 5+ core commands:
247
178
 
248
179
  ### 6.5 Deployment Ready
249
180
 
250
- - No manual config after first enable
251
- - Survives MCP/Agent restart
181
+ No manual config after first enable. Survives MCP/Agent restart.
252
182
 
253
183
  ### 6.6 Failure Scenarios
254
184
 
@@ -262,40 +192,28 @@ Test with 5+ core commands:
262
192
 
263
193
  ## Wrapper Declaration
264
194
 
265
- This project is a wrapper for **agent-browser** by Vercel Labs.
195
+ This project is a **wrapper** for **agent-browser** by Vercel Labs.
266
196
 
267
197
  - **Upstream**: https://github.com/vercel-labs/agent-browser
268
198
  - **License**: Apache-2.0
269
- - This is NOT an official upstream release
199
+ - This is **NOT** an official upstream release
270
200
  - Upstream trademarks/copyrights belong to respective owners
271
201
 
272
202
  See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for license details.
273
203
 
274
204
  ---
275
205
 
276
- ## Upstream License
277
-
278
- agent-browser is licensed under **Apache-2.0**.
279
-
280
- This project includes upstream code per Apache-2.0 requirements.
206
+ ## License
281
207
 
282
- ---
208
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://img.shields.io/badge/License-Apache%202.0-blue)
283
209
 
284
- ## Version History
210
+ Apache-2.0 License
285
211
 
286
- See [CHANGELOG.md](./CHANGELOG.md)
212
+ Copyright (c) 2024-2025 bsbofmusic
287
213
 
288
214
  ---
289
215
 
290
216
  ## Support
291
217
 
292
- - MCP issues: https://github.com/issues
293
- - agent-browser upstream: https://github.com/vercel-labs/agent-browser/issues
294
-
295
- ---
296
-
297
- ## License
298
-
299
- Apache-2.0 License
300
-
301
- Copyright (c) 2024-2025 bsbofmusic
218
+ - **MCP issues**: https://github.com/issues
219
+ - **agent-browser upstream**: https://github.com/vercel-labs/agent-browser/issues
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsbofmusic/agent-browser-mcp-opencode",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for Vercel agent-browser - browser automation CLI for AI agents",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "author": "bsbofmusic",
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.27.1",
28
29
  "execa": "^9.5.2",
29
30
  "semver": "^7.7.1"
30
31
  },
package/src/ensure.js CHANGED
@@ -14,9 +14,13 @@ const ALWAYS_LATEST = process.env.ALWAYS_LATEST !== '0';
14
14
  const UPDATE_STRATEGY = process.env.UPDATE_STRATEGY || 'simple';
15
15
  const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
16
16
  const LOCK_FILE = path.join(os.tmpdir(), 'agent-browser-mcp.lock');
17
+ const RUNTIME_DIR = process.env.RUNTIME_DIR || path.join(os.homedir(), '.agent-browser', 'mcp');
18
+ const ACTIVE_DIR = path.join(RUNTIME_DIR, 'active');
19
+ const STAGING_DIR = path.join(RUNTIME_DIR, 'staging');
17
20
 
18
- const MCP_VERSION = '1.0.0';
21
+ const MCP_VERSION = '2.0.0';
19
22
  const MIN_NODE_VERSION = '18.0.0';
23
+ const AGENT_BROWSER_PACKAGE = 'agent-browser@latest';
20
24
 
21
25
  function log(level, message, data = null) {
22
26
  const levels = { error: 0, warn: 1, info: 2, debug: 3 };
@@ -44,12 +48,13 @@ function execPromise(command, options = {}) {
44
48
  }
45
49
 
46
50
  async function acquireLock() {
47
- const maxWait = 30000;
51
+ const maxWait = 60000;
48
52
  const start = Date.now();
49
53
 
50
54
  while (true) {
51
55
  try {
52
56
  if (!fs.existsSync(LOCK_FILE)) {
57
+ fs.mkdirSync(path.dirname(LOCK_FILE), { recursive: true });
53
58
  fs.writeFileSync(LOCK_FILE, `${process.pid}:${Date.now()}`);
54
59
  log('debug', 'Lock acquired', { pid: process.pid });
55
60
  return true;
@@ -78,7 +83,7 @@ async function acquireLock() {
78
83
  }
79
84
 
80
85
  if (Date.now() - start > maxWait) {
81
- throw new Error('Failed to acquire lock after 30 seconds');
86
+ throw new Error('Failed to acquire lock after 60 seconds');
82
87
  }
83
88
  }
84
89
  }
@@ -86,14 +91,71 @@ async function acquireLock() {
86
91
  function releaseLock() {
87
92
  try {
88
93
  if (fs.existsSync(LOCK_FILE)) {
89
- fs.unlinkSync(LOCK_FILE);
90
- log('debug', 'Lock released');
94
+ const lockContent = fs.readFileSync(LOCK_FILE, 'utf8');
95
+ const [lockPid] = lockContent.split(':');
96
+ if (lockPid === process.pid.toString()) {
97
+ fs.unlinkSync(LOCK_FILE);
98
+ log('debug', 'Lock released');
99
+ }
91
100
  }
92
101
  } catch (e) {
93
102
  log('warn', 'Failed to release lock', { error: e.message });
94
103
  }
95
104
  }
96
105
 
106
+ function isWindows() {
107
+ return os.platform() === 'win32';
108
+ }
109
+
110
+ function getAgentBrowserCommand() {
111
+ try {
112
+ if (isWindows()) {
113
+ execSync('where agent-browser', { stdio: 'ignore' });
114
+ return 'agent-browser.cmd';
115
+ } else {
116
+ execSync('which agent-browser', { stdio: 'ignore' });
117
+ return 'agent-browser';
118
+ }
119
+ } catch (e) {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ export async function runAgentBrowserCommand(command, options = {}) {
125
+ const cmd = getAgentBrowserCommand();
126
+ const useNpx = !cmd || options.forceNpx;
127
+
128
+ let fullCommand;
129
+ if (useNpx) {
130
+ fullCommand = `npx -y ${AGENT_BROWSER_PACKAGE} ${command}`;
131
+ } else {
132
+ fullCommand = `${cmd} ${command}`;
133
+ }
134
+
135
+ log('debug', 'Running command', { fullCommand, useNpx });
136
+
137
+ try {
138
+ const stdout = execSync(fullCommand, {
139
+ encoding: 'utf8',
140
+ timeout: options.timeout || 60000,
141
+ ...options,
142
+ });
143
+ return { ok: true, stdout, stderr: '', useNpx };
144
+ } catch (error) {
145
+ if (!useNpx) {
146
+ log('warn', 'Global command failed, trying npx fallback');
147
+ return runAgentBrowserCommand(command, { ...options, forceNpx: true });
148
+ }
149
+ return {
150
+ ok: false,
151
+ stdout: error.stdout || '',
152
+ stderr: error.stderr || error.message,
153
+ error: error.message,
154
+ useNpx: true,
155
+ };
156
+ }
157
+ }
158
+
97
159
  async function checkNodeVersion() {
98
160
  const currentVersion = process.version;
99
161
  if (!semver.gte(currentVersion, MIN_NODE_VERSION)) {
@@ -102,22 +164,19 @@ async function checkNodeVersion() {
102
164
  return { ok: true, version: currentVersion };
103
165
  }
104
166
 
105
- async function checkNpmAvailable() {
106
- try {
107
- execSync('npm --version', { stdio: 'ignore' });
108
- return { ok: true };
109
- } catch (e) {
110
- return { ok: false, error: 'npm is not available' };
111
- }
112
- }
113
-
114
167
  async function installAgentBrowser() {
115
168
  const logs = [];
116
169
 
117
- log('info', 'Installing agent-browser globally...');
170
+ log('info', 'Installing agent-browser...');
118
171
  logs.push('Step 1: Install agent-browser via npm');
119
172
 
120
173
  try {
174
+ const result = await runAgentBrowserCommand('--version');
175
+ if (result.ok) {
176
+ logs.push('agent-browser is already installed');
177
+ return { ok: true, logs };
178
+ }
179
+
121
180
  execSync('npm install -g agent-browser', {
122
181
  encoding: 'utf8',
123
182
  stdio: 'inherit',
@@ -125,10 +184,9 @@ async function installAgentBrowser() {
125
184
  });
126
185
  logs.push('agent-browser installed successfully');
127
186
  } catch (e) {
128
- const errorMsg = `Failed to install agent-browser: ${e.message}`;
129
- log('error', errorMsg);
130
- logs.push(errorMsg);
131
- return { ok: false, logs, error: errorMsg };
187
+ logs.push('Global install failed, will use npx fallback');
188
+ log('warn', 'Global install failed', { error: e.message });
189
+ return { ok: true, logs, fallback: 'npx' };
132
190
  }
133
191
 
134
192
  return { ok: true, logs };
@@ -140,31 +198,15 @@ async function installChromium() {
140
198
  log('info', 'Installing Chromium browser...');
141
199
  logs.push('Step 2: Install Chromium browser');
142
200
 
143
- try {
144
- execSync('npx agent-browser install', {
145
- encoding: 'utf8',
146
- stdio: 'inherit',
147
- timeout: 300000
148
- });
201
+ const result = await runAgentBrowserCommand('install');
202
+
203
+ if (result.ok) {
149
204
  logs.push('Chromium installed successfully');
150
- } catch (e) {
151
- try {
152
- log('warn', 'npx install failed, trying direct agent-browser install');
153
- execSync('agent-browser install', {
154
- encoding: 'utf8',
155
- stdio: 'inherit',
156
- timeout: 300000
157
- });
158
- logs.push('Chromium installed via agent-browser install');
159
- } catch (e2) {
160
- const errorMsg = `Failed to install Chromium: ${e2.message}`;
161
- log('error', errorMsg);
162
- logs.push(errorMsg);
163
- return { ok: false, logs, error: errorMsg };
164
- }
205
+ return { ok: true, logs };
206
+ } else {
207
+ logs.push(`Failed to install Chromium: ${result.error}`);
208
+ return { ok: false, logs, error: result.error };
165
209
  }
166
-
167
- return { ok: true, logs };
168
210
  }
169
211
 
170
212
  async function verifyInstallation() {
@@ -172,35 +214,23 @@ async function verifyInstallation() {
172
214
 
173
215
  logs.push('Step 3: Verify installation');
174
216
 
175
- try {
176
- const version = execSync('agent-browser --version', { encoding: 'utf8' }).trim();
177
- logs.push(`agent-browser version: ${version}`);
178
- } catch (e) {
179
- const errorMsg = 'agent-browser not found in PATH after installation';
180
- log('error', errorMsg);
181
- logs.push(errorMsg);
182
- return { ok: false, logs, error: errorMsg };
183
- }
217
+ const result = await runAgentBrowserCommand('--version');
184
218
 
185
- try {
186
- execSync('agent-browser --help', { encoding: 'utf8', stdio: 'ignore' });
187
- logs.push('Chromium is ready');
188
- } catch (e) {
189
- const errorMsg = 'Chromium may not be fully installed';
190
- log('warn', errorMsg);
191
- logs.push(errorMsg);
219
+ if (result.ok) {
220
+ logs.push(`agent-browser version: ${result.stdout.trim()}`);
221
+ return { ok: true, logs, version: result.stdout.trim() };
222
+ } else {
223
+ logs.push('agent-browser verification failed');
224
+ return { ok: false, logs, error: result.error };
192
225
  }
193
-
194
- return { ok: true, logs };
195
226
  }
196
227
 
197
228
  async function getAgentBrowserVersion() {
198
- try {
199
- const version = execSync('agent-browser --version', { encoding: 'utf8' }).trim();
200
- return { installed: true, version };
201
- } catch (e) {
202
- return { installed: false, version: null };
229
+ const result = await runAgentBrowserCommand('--version');
230
+ if (result.ok) {
231
+ return { installed: true, version: result.stdout.trim() };
203
232
  }
233
+ return { installed: false, version: null };
204
234
  }
205
235
 
206
236
  async function checkForUpdates() {
@@ -211,7 +241,7 @@ async function checkForUpdates() {
211
241
  }
212
242
 
213
243
  try {
214
- const npmVersion = execSync('npm view agent-browser version', { encoding: 'utf8' }).trim();
244
+ const npmVersion = execSync('npm view agent-browser version', { encoding: 'utf8', timeout: 10000 }).trim();
215
245
  const hasUpdate = semver.lt(current.version, npmVersion);
216
246
 
217
247
  return {
@@ -220,6 +250,7 @@ async function checkForUpdates() {
220
250
  latestVersion: npmVersion,
221
251
  };
222
252
  } catch (e) {
253
+ log('warn', 'Failed to check for updates', { error: e.message });
223
254
  return { hasUpdate: null, currentVersion: current.version, latestVersion: null };
224
255
  }
225
256
  }
@@ -230,23 +261,60 @@ async function upgradeAgentBrowser() {
230
261
  log('info', 'Upgrading agent-browser to latest...');
231
262
  logs.push('Upgrading agent-browser to latest version');
232
263
 
233
- try {
234
- execSync('npm install -g agent-browser@latest', {
235
- encoding: 'utf8',
236
- stdio: 'inherit',
237
- timeout: 300000
238
- });
239
- logs.push('Upgrade completed');
264
+ if (UPDATE_STRATEGY === 'atomic') {
265
+ logs.push('Using atomic update strategy');
240
266
 
241
- const version = execSync('agent-browser --version', { encoding: 'utf8' }).trim();
242
- logs.push(`New version: ${version}`);
267
+ try {
268
+ fs.rmSync(STAGING_DIR, { recursive: true, force: true });
269
+ fs.mkdirSync(STAGING_DIR, { recursive: true });
270
+
271
+ execSync(`npm install --prefix ${STAGING_DIR} agent-browser@latest`, {
272
+ encoding: 'utf8',
273
+ timeout: 300000,
274
+ });
275
+
276
+ const testResult = execSync(`${path.join(STAGING_DIR, 'node_modules', '.bin', 'agent-browser')} --version`, {
277
+ encoding: 'utf8',
278
+ timeout: 10000,
279
+ });
280
+
281
+ fs.rmSync(ACTIVE_DIR, { recursive: true, force: true });
282
+ fs.renameSync(STAGING_DIR, ACTIVE_DIR);
283
+
284
+ logs.push('Atomic upgrade completed');
285
+ logs.push(`New version: ${testResult.trim()}`);
286
+
287
+ return { ok: true, logs, version: testResult.trim() };
288
+ } catch (e) {
289
+ logs.push(`Atomic upgrade failed: ${e.message}`);
290
+ logs.push('Rolling back to previous version');
291
+
292
+ try {
293
+ fs.rmSync(STAGING_DIR, { recursive: true, force: true });
294
+ } catch (rollbackError) {
295
+ logs.push(`Rollback failed: ${rollbackError.message}`);
296
+ }
297
+
298
+ return { ok: false, logs, error: e.message };
299
+ }
300
+ } else {
301
+ logs.push('Using simple update strategy');
243
302
 
244
- return { ok: true, logs, version };
245
- } catch (e) {
246
- const errorMsg = `Upgrade failed: ${e.message}`;
247
- log('error', errorMsg);
248
- logs.push(errorMsg);
249
- return { ok: false, logs, error: errorMsg };
303
+ try {
304
+ execSync('npm install -g agent-browser@latest', {
305
+ encoding: 'utf8',
306
+ stdio: 'inherit',
307
+ timeout: 300000,
308
+ });
309
+
310
+ const version = execSync('agent-browser --version', { encoding: 'utf8' }).trim();
311
+ logs.push(`Upgrade completed, version: ${version}`);
312
+
313
+ return { ok: true, logs, version };
314
+ } catch (e) {
315
+ logs.push(`Upgrade failed: ${e.message}`);
316
+ return { ok: false, logs, error: e.message };
317
+ }
250
318
  }
251
319
  }
252
320
 
@@ -307,16 +375,8 @@ export async function bootstrapEnsure() {
307
375
  }
308
376
  logs.push(`Node.js version: ${nodeCheck.version}`);
309
377
 
310
- const npmCheck = await checkNpmAvailable();
311
- if (!npmCheck.ok) {
312
- return {
313
- ok: false,
314
- logs,
315
- error: npmCheck.error,
316
- nextSteps: ['Install Node.js with npm', 'Add npm to PATH']
317
- };
318
- }
319
- logs.push('npm is available');
378
+ fs.mkdirSync(RUNTIME_DIR, { recursive: true });
379
+ logs.push('Runtime directory created');
320
380
 
321
381
  const detection = await runFullDetection();
322
382
  logs.push(`Detection: agent-browser installed=${detection.agentBrowser.installed}`);
@@ -357,7 +417,7 @@ export async function bootstrapEnsure() {
357
417
  ok: false,
358
418
  logs,
359
419
  error: verifyResult.error,
360
- nextSteps: ['Run: agent-browser install', 'Check PATH configuration']
420
+ nextSteps: ['Run: browser_ensure to repair installation', 'Use npx agent-browser as fallback']
361
421
  };
362
422
  }
363
423
 
@@ -416,10 +476,25 @@ export async function fullEnsure() {
416
476
  }
417
477
  }
418
478
 
479
+ process.on('SIGINT', () => {
480
+ releaseLock();
481
+ process.exit(0);
482
+ });
483
+
484
+ process.on('SIGTERM', () => {
485
+ releaseLock();
486
+ process.exit(0);
487
+ });
488
+
489
+ process.on('exit', () => {
490
+ releaseLock();
491
+ });
492
+
419
493
  export default {
420
494
  bootstrapEnsure,
421
495
  fullEnsure,
422
496
  ensureLatest,
423
497
  checkForUpdates,
424
498
  getAgentBrowserVersion,
499
+ runAgentBrowserCommand,
425
500
  };
@@ -1,4 +1,14 @@
1
- import { execSync } from 'child_process';
1
+ import { runAgentBrowserCommand } from '../../ensure.js';
2
+
3
+ const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
4
+
5
+ function log(level, message, data = null) {
6
+ const levels = { error: 0, warn: 1, info: 2, debug: 3 };
7
+ if (levels[level] <= levels[LOG_LEVEL]) {
8
+ const timestamp = new Date().toISOString();
9
+ console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`, data ? JSON.stringify(data) : '');
10
+ }
11
+ }
2
12
 
3
13
  export const clickTool = {
4
14
  name: 'browser_click',
@@ -26,37 +36,40 @@ export async function clickToolHandler(args) {
26
36
  const startTime = Date.now();
27
37
 
28
38
  logs.push(`[${new Date().toISOString()}] browser_click: ${selector}`);
39
+ log('info', 'Clicking element', { selector, newTab });
29
40
 
30
41
  try {
31
42
  let command = `click "${selector}"`;
32
43
  if (newTab) command += ' --new-tab';
33
44
 
34
- const stdout = execSync(`agent-browser ${command} --json`, {
35
- encoding: 'utf8',
36
- timeout: 30000,
37
- });
45
+ const result = await runAgentBrowserCommand(`${command} --json`);
38
46
 
39
47
  const elapsed = Date.now() - startTime;
40
48
  logs.push(`[${new Date().toISOString()}] Completed in ${elapsed}ms`);
41
49
 
42
50
  let parsed;
43
51
  try {
44
- parsed = JSON.parse(stdout);
52
+ parsed = JSON.parse(result.stdout);
45
53
  } catch {
46
- parsed = { success: true };
54
+ parsed = { success: result.ok };
47
55
  }
48
56
 
49
57
  return {
50
- ok: true,
58
+ ok: result.ok,
51
59
  logs,
52
- stdout,
60
+ stdout: result.stdout,
61
+ stderr: result.stderr,
53
62
  output: parsed,
54
63
  selector,
55
64
  duration: elapsed,
65
+ useNpx: result.useNpx,
56
66
  };
57
67
  } catch (error) {
58
68
  const elapsed = Date.now() - startTime;
59
- logs.push(`[${new Date().toISOString()}] Error: ${error.message}`);
69
+ const errorMsg = error.message || String(error);
70
+
71
+ logs.push(`[${new Date().toISOString()}] Error: ${errorMsg}`);
72
+ log('error', 'Failed to click element', { error: errorMsg, selector });
60
73
 
61
74
  const nextSteps = [
62
75
  'Run browser_snapshot first to get element refs',
@@ -67,8 +80,8 @@ export async function clickToolHandler(args) {
67
80
  return {
68
81
  ok: false,
69
82
  logs,
70
- stderr: error.stderr || '',
71
- error: error.message,
83
+ stderr: error.stderr || errorMsg,
84
+ error: errorMsg,
72
85
  selector,
73
86
  duration: elapsed,
74
87
  nextSteps,
@@ -1,4 +1,14 @@
1
- import { execSync } from 'child_process';
1
+ import { runAgentBrowserCommand } from '../../ensure.js';
2
+
3
+ const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
4
+
5
+ function log(level, message, data = null) {
6
+ const levels = { error: 0, warn: 1, info: 2, debug: 3 };
7
+ if (levels[level] <= levels[LOG_LEVEL]) {
8
+ const timestamp = new Date().toISOString();
9
+ console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`, data ? JSON.stringify(data) : '');
10
+ }
11
+ }
2
12
 
3
13
  export const fillTool = {
4
14
  name: 'browser_fill',
@@ -25,37 +35,40 @@ export async function fillToolHandler(args) {
25
35
  const startTime = Date.now();
26
36
 
27
37
  logs.push(`[${new Date().toISOString()}] browser_fill: ${selector} = "${value}"`);
38
+ log('info', 'Filling form field', { selector, value });
28
39
 
29
40
  try {
30
41
  const command = `fill "${selector}" "${value}"`;
31
42
 
32
- const stdout = execSync(`agent-browser ${command} --json`, {
33
- encoding: 'utf8',
34
- timeout: 30000,
35
- });
43
+ const result = await runAgentBrowserCommand(`${command} --json`);
36
44
 
37
45
  const elapsed = Date.now() - startTime;
38
46
  logs.push(`[${new Date().toISOString()}] Completed in ${elapsed}ms`);
39
47
 
40
48
  let parsed;
41
49
  try {
42
- parsed = JSON.parse(stdout);
50
+ parsed = JSON.parse(result.stdout);
43
51
  } catch {
44
- parsed = { success: true };
52
+ parsed = { success: result.ok };
45
53
  }
46
54
 
47
55
  return {
48
- ok: true,
56
+ ok: result.ok,
49
57
  logs,
50
- stdout,
58
+ stdout: result.stdout,
59
+ stderr: result.stderr,
51
60
  output: parsed,
52
61
  selector,
53
62
  value,
54
63
  duration: elapsed,
64
+ useNpx: result.useNpx,
55
65
  };
56
66
  } catch (error) {
57
67
  const elapsed = Date.now() - startTime;
58
- logs.push(`[${new Date().toISOString()}] Error: ${error.message}`);
68
+ const errorMsg = error.message || String(error);
69
+
70
+ logs.push(`[${new Date().toISOString()}] Error: ${errorMsg}`);
71
+ log('error', 'Failed to fill form field', { error: errorMsg, selector, value });
59
72
 
60
73
  const nextSteps = [
61
74
  'Run browser_snapshot first to get element refs',
@@ -66,8 +79,8 @@ export async function fillToolHandler(args) {
66
79
  return {
67
80
  ok: false,
68
81
  logs,
69
- stderr: error.stderr || '',
70
- error: error.message,
82
+ stderr: error.stderr || errorMsg,
83
+ error: errorMsg,
71
84
  selector,
72
85
  value,
73
86
  duration: elapsed,
@@ -1,4 +1,14 @@
1
- import { execSync } from 'child_process';
1
+ import { runAgentBrowserCommand } from '../../ensure.js';
2
+
3
+ const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
4
+
5
+ function log(level, message, data = null) {
6
+ const levels = { error: 0, warn: 1, info: 2, debug: 3 };
7
+ if (levels[level] <= levels[LOG_LEVEL]) {
8
+ const timestamp = new Date().toISOString();
9
+ console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`, data ? JSON.stringify(data) : '');
10
+ }
11
+ }
2
12
 
3
13
  export const openTool = {
4
14
  name: 'browser_open',
@@ -39,6 +49,7 @@ export async function openToolHandler(args) {
39
49
  const startTime = Date.now();
40
50
 
41
51
  logs.push(`[${new Date().toISOString()}] browser_open: ${url}`);
52
+ log('info', 'Opening URL', { url, newTab, session, profile });
42
53
 
43
54
  try {
44
55
  let command = `open ${newTab ? '--new-tab ' : ''}"${url}"`;
@@ -49,33 +60,34 @@ export async function openToolHandler(args) {
49
60
  command = `open ${newTab ? '--new-tab ' : ''}--profile "${profile}" "${url}"`;
50
61
  }
51
62
 
52
- const stdout = execSync(`agent-browser ${command} --json`, {
53
- encoding: 'utf8',
54
- timeout,
55
- maxBuffer: 10 * 1024 * 1024,
56
- });
63
+ const result = await runAgentBrowserCommand(`${command} --json`, { timeout });
57
64
 
58
65
  const elapsed = Date.now() - startTime;
59
66
  logs.push(`[${new Date().toISOString()}] Completed in ${elapsed}ms`);
60
67
 
61
68
  let parsed;
62
69
  try {
63
- parsed = JSON.parse(stdout);
70
+ parsed = JSON.parse(result.stdout);
64
71
  } catch {
65
- parsed = { success: true, raw: stdout };
72
+ parsed = { success: result.ok, raw: result.stdout };
66
73
  }
67
74
 
68
75
  return {
69
- ok: true,
76
+ ok: result.ok,
70
77
  logs,
71
- stdout,
78
+ stdout: result.stdout,
79
+ stderr: result.stderr,
72
80
  output: parsed,
73
81
  url,
74
82
  duration: elapsed,
83
+ useNpx: result.useNpx,
75
84
  };
76
85
  } catch (error) {
77
86
  const elapsed = Date.now() - startTime;
78
- logs.push(`[${new Date().toISOString()}] Error: ${error.message}`);
87
+ const errorMsg = error.message || String(error);
88
+
89
+ logs.push(`[${new Date().toISOString()}] Error: ${errorMsg}`);
90
+ log('error', 'Failed to open URL', { error: errorMsg, url });
79
91
 
80
92
  const nextSteps = [
81
93
  'Check if URL is valid and accessible',
@@ -86,8 +98,8 @@ export async function openToolHandler(args) {
86
98
  return {
87
99
  ok: false,
88
100
  logs,
89
- stderr: error.stderr || '',
90
- error: error.message,
101
+ stderr: error.stderr || errorMsg,
102
+ error: errorMsg,
91
103
  url,
92
104
  duration: elapsed,
93
105
  nextSteps,
@@ -1,4 +1,14 @@
1
- import { execSync } from 'child_process';
1
+ import { runAgentBrowserCommand } from '../../ensure.js';
2
+
3
+ const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
4
+
5
+ function log(level, message, data = null) {
6
+ const levels = { error: 0, warn: 1, info: 2, debug: 3 };
7
+ if (levels[level] <= levels[LOG_LEVEL]) {
8
+ const timestamp = new Date().toISOString();
9
+ console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`, data ? JSON.stringify(data) : '');
10
+ }
11
+ }
2
12
 
3
13
  export const snapshotTool = {
4
14
  name: 'browser_snapshot',
@@ -39,6 +49,7 @@ export async function snapshotToolHandler(args) {
39
49
  const startTime = Date.now();
40
50
 
41
51
  logs.push(`[${new Date().toISOString()}] browser_snapshot called`);
52
+ log('info', 'Taking snapshot', { interactive, compact, depth, selector, cursor });
42
53
 
43
54
  try {
44
55
  let command = 'snapshot --json';
@@ -49,9 +60,7 @@ export async function snapshotToolHandler(args) {
49
60
  if (depth) command += ` -d ${depth}`;
50
61
  if (selector) command += ` -s "${selector}"`;
51
62
 
52
- const stdout = execSync(`agent-browser ${command}`, {
53
- encoding: 'utf8',
54
- timeout: 30000,
63
+ const result = await runAgentBrowserCommand(command, {
55
64
  maxBuffer: 50 * 1024 * 1024,
56
65
  });
57
66
 
@@ -60,21 +69,26 @@ export async function snapshotToolHandler(args) {
60
69
 
61
70
  let parsed;
62
71
  try {
63
- parsed = JSON.parse(stdout);
72
+ parsed = JSON.parse(result.stdout);
64
73
  } catch {
65
- parsed = { success: true, raw: stdout };
74
+ parsed = { success: result.ok, raw: result.stdout };
66
75
  }
67
76
 
68
77
  return {
69
- ok: true,
78
+ ok: result.ok,
70
79
  logs,
71
- stdout,
80
+ stdout: result.stdout,
81
+ stderr: result.stderr,
72
82
  output: parsed,
73
83
  duration: elapsed,
84
+ useNpx: result.useNpx,
74
85
  };
75
86
  } catch (error) {
76
87
  const elapsed = Date.now() - startTime;
77
- logs.push(`[${new Date().toISOString()}] Error: ${error.message}`);
88
+ const errorMsg = error.message || String(error);
89
+
90
+ logs.push(`[${new Date().toISOString()}] Error: ${errorMsg}`);
91
+ log('error', 'Failed to take snapshot', { error: errorMsg });
78
92
 
79
93
  const nextSteps = [
80
94
  'Run browser_open first to navigate to a page',
@@ -85,8 +99,8 @@ export async function snapshotToolHandler(args) {
85
99
  return {
86
100
  ok: false,
87
101
  logs,
88
- stderr: error.stderr || '',
89
- error: error.message,
102
+ stderr: error.stderr || errorMsg,
103
+ error: errorMsg,
90
104
  duration: elapsed,
91
105
  nextSteps,
92
106
  };
@@ -45,9 +45,15 @@ export async function waitToolHandler(args) {
45
45
  if (target && !target.startsWith('--')) {
46
46
  command += ` "${target}"`;
47
47
  }
48
- if (text) command += ` --text "${text}"`;
49
- if (url) command += ` --url "${url}"`;
50
- if (loadState) command += ` --${loadState}`;
48
+ if (text) {
49
+ command += ` --text "${text}"`;
50
+ }
51
+ if (url) {
52
+ command += ` --url "${url}"`;
53
+ }
54
+ if (loadState) {
55
+ command += ` --${loadState}`;
56
+ }
51
57
 
52
58
  const stdout = execSync(`agent-browser ${command} --json`, {
53
59
  encoding: 'utf8',
@@ -57,10 +63,18 @@ export async function waitToolHandler(args) {
57
63
  const elapsed = Date.now() - startTime;
58
64
  logs.push(`[${new Date().toISOString()}] Completed in ${elapsed}ms`);
59
65
 
66
+ let parsed;
67
+ try {
68
+ parsed = JSON.parse(stdout);
69
+ } catch {
70
+ parsed = { success: true };
71
+ }
72
+
60
73
  return {
61
74
  ok: true,
62
75
  logs,
63
76
  stdout,
77
+ output: parsed,
64
78
  duration: elapsed,
65
79
  };
66
80
  } catch (error) {
@@ -68,7 +82,7 @@ export async function waitToolHandler(args) {
68
82
  logs.push(`[${new Date().toISOString()}] Error: ${error.message}`);
69
83
 
70
84
  const nextSteps = [
71
- 'Verify the target element/text/URL exists',
85
+ 'Verify target element/text/URL exists',
72
86
  'Increase timeout value',
73
87
  'Check page for dynamic content issues',
74
88
  ];
package/src/tools/exec.js CHANGED
@@ -1,4 +1,4 @@
1
- import { execSync } from 'child_process';
1
+ import { runAgentBrowserCommand } from '../ensure.js';
2
2
 
3
3
  const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
4
4
 
@@ -45,57 +45,43 @@ export async function execToolHandler(args) {
45
45
  const startTime = Date.now();
46
46
 
47
47
  logs.push(`[${new Date().toISOString()}] Executing: agent-browser ${command}`);
48
+ log('info', 'Executing command', { command, timeoutMs, cwd, json });
48
49
 
49
50
  try {
50
- const fullCommand = `agent-browser ${command}${json ? ' --json' : ''}`;
51
- log('info', 'Executing command', { command: fullCommand, timeoutMs, cwd });
51
+ const cmd = json ? `${command} --json` : command;
52
52
 
53
- const options = {
54
- encoding: 'utf8',
53
+ const result = await runAgentBrowserCommand(cmd, {
55
54
  timeout: timeoutMs,
56
- maxBuffer: 50 * 1024 * 1024,
57
- };
58
-
59
- if (cwd) {
60
- options.cwd = cwd;
61
- }
55
+ cwd,
56
+ });
62
57
 
63
- const stdout = execSync(fullCommand, options);
64
58
  const elapsed = Date.now() - startTime;
65
-
66
59
  logs.push(`[${new Date().toISOString()}] Completed in ${elapsed}ms`);
67
60
 
68
- let parsedOutput = stdout;
61
+ let parsedOutput = result.stdout;
69
62
  if (json) {
70
63
  try {
71
- parsedOutput = JSON.parse(stdout);
64
+ parsedOutput = JSON.parse(result.stdout);
72
65
  } catch {
73
- parsedOutput = stdout;
66
+ parsedOutput = result.stdout;
74
67
  }
75
68
  }
76
69
 
77
70
  return {
78
- ok: true,
71
+ ok: result.ok,
79
72
  logs,
80
- stdout,
73
+ stdout: result.stdout,
74
+ stderr: result.stderr,
81
75
  output: parsedOutput,
82
76
  duration: elapsed,
77
+ useNpx: result.useNpx,
83
78
  };
84
79
  } catch (error) {
85
80
  const elapsed = Date.now() - startTime;
86
81
  const errorMsg = error.message || String(error);
87
- const stderr = error.stderr || '';
88
82
 
89
83
  logs.push(`[${new Date().toISOString()}] Error after ${elapsed}ms: ${errorMsg}`);
90
-
91
- let parsedError = errorMsg;
92
- if (json && stderr) {
93
- try {
94
- parsedError = JSON.parse(stderr);
95
- } catch {
96
- parsedError = stderr;
97
- }
98
- }
84
+ log('error', 'Command failed', { error: errorMsg, elapsed });
99
85
 
100
86
  const nextSteps = [
101
87
  'Check if the command syntax is correct',
@@ -110,9 +96,8 @@ export async function execToolHandler(args) {
110
96
  return {
111
97
  ok: false,
112
98
  logs,
113
- stderr,
99
+ stderr: error.stderr || errorMsg,
114
100
  error: errorMsg,
115
- output: parsedError,
116
101
  duration: elapsed,
117
102
  nextSteps,
118
103
  };
package/test_input.txt ADDED
@@ -0,0 +1,2 @@
1
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}
2
+ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
@@ -0,0 +1,15 @@
1
+ [2026-02-27T11:02:02.290Z] [INFO] MCP server started, waiting for initialization...
2
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 1\"... is not valid JSON","line":"=== Test 1: MCP Initialization ==="}
3
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\"}}'"}
4
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 2\"... is not valid JSON","line":"=== Test 2: Tools List ==="}
5
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}'"}
6
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 3\"... is not valid JSON","line":"=== Test 3: Version Info ==="}
7
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"browser_version\",\"arguments\":{}}}'"}
8
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 4\"... is not valid JSON","line":"=== Test 4: Doctor Diagnosis ==="}
9
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"browser_doctor\",\"arguments\":{}}}'"}
10
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 5\"... is not valid JSON","line":"=== Test 5: Ensure Tool ==="}
11
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"browser_ensure\",\"arguments\":{}}}'"}
12
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 6\"... is not valid JSON","line":"=== Test 6: Exec Tool (simple) ==="}
13
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tools/call\",\"params\":{\"name\":\"browser_exec\",\"arguments\":{\"command\":\"--help\"}}}'"}
14
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token '=', \"=== Test 7\"... is not valid JSON","line":"=== Test 7: Exec Tool (complex) ==="}
15
+ [2026-02-27T11:02:02.293Z] [ERROR] Failed to parse message {"error":"Unexpected token 'e', \"echo '{\"js\"... is not valid JSON","line":"echo '{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"tools/call\",\"params\":{\"name\":\"browser_exec\",\"arguments\":{\"command\":\"open https://example.com --json\"}}'"}
@@ -0,0 +1,21 @@
1
+ === Test 1: MCP Initialization ===
2
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}'
3
+
4
+ === Test 2: Tools List ===
5
+ echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
6
+
7
+ === Test 3: Version Info ===
8
+ echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"browser_version","arguments":{}}}'
9
+
10
+ === Test 4: Doctor Diagnosis ===
11
+ echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"browser_doctor","arguments":{}}}'
12
+
13
+ === Test 5: Ensure Tool ===
14
+ echo '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"browser_ensure","arguments":{}}}'
15
+
16
+ === Test 6: Exec Tool (help) ===
17
+ echo '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"browser_exec","arguments":{"command":"--help"}}}'
18
+
19
+ === Test 7: Exec Tool (open) ===
20
+ echo '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"browser_exec","arguments":{"command":"open https://example.com --json"}}}'
21
+