@bsbofmusic/agent-browser-mcp-opencode 1.0.2 → 2.0.1

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
@@ -2,210 +2,252 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- **MCP Server for agent-browser**
6
- <sub><sup>Browser automation for AI agents</sup></sub>
7
-
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)
5
+ **MCP Server for Vercel agent-browser**
6
+ <sub>Browser automation for AI agents via Model Context Protocol</sub>
7
+
8
+ [![MCP](https://img.shields.io/badge/MCP-v2-blue)](https://modelcontextprotocol.io/)
9
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://opensource.org/licenses/Apache-2.0)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-blue)](https://nodejs.org/)
11
+ [![NPM](https://img.shields.io/npm/v/@bsbofmusic%2Fagent-browser-mcp-opencode)](https://www.npmjs.com/package/@bsbofmusic/agent-browser-mcp-opencode)
12
+ [![Downloads](https://img.shields.io/npm/dw/@bsbofmusic%2Fagent-browser-mcp-opencode)](https://www.npmjs.com/package/@bsbofmusic/agent-browser-mcp-opencode)
13
+ [![Windows](https://img.shields.io/badge/Windows-✔️-blue)]()
14
+ [![macOS](https://img.shields.io/badge/macOS-✔️-blue)]()
15
+ [![Linux](https://img.shields.io/badge/Linux-✔️-blue)]()
12
16
 
13
17
  </div>
14
18
 
15
19
  ---
16
20
 
17
- <div align="center">
21
+ ## Overview
18
22
 
19
- **Browser automation for AI agents via Model Context Protocol**
23
+ This package is an **MCP (Model Context Protocol)** wrapper for [agent-browser](https://github.com/vercel-labs/agent-browser) by Vercel Labs. It provides browser automation capabilities to AI assistants like **OpenCode**, **Claude Code**, **Cursor**, and others through a standardized MCP interface.
20
24
 
21
- </div>
25
+ ### ✨ Key Features
26
+
27
+ | Feature | Description |
28
+ |---------|-------------|
29
+ | 🚀 **One-Click Enable** | Auto-installs all dependencies (agent-browser + Chromium) on first run |
30
+ | 🛠️ **Self-Healing** | Built-in `browser_ensure` and `browser_doctor` tools for auto-repair |
31
+ | 🔄 **Auto-Update** | `always-latest` mode with atomic update support |
32
+ | 📚 **Full Capability Coverage** | 15 MCP tools covering all core agent-browser functionality |
33
+ | ⚡ **Universal Passthrough** | `browser_exec` for executing any agent-browser CLI command |
34
+ | ♻️ **Idempotent** | Safe to run multiple times, no side effects |
35
+ | 🪟 **Windows Compatibility** | Auto-fallback to `npx` when global install fails |
36
+ | 📊 **Structured Output** | All tools return consistent `{ok, logs, stdout, stderr, nextSteps}` format |
22
37
 
23
38
  ---
24
39
 
25
- ## Quick Start
40
+ ## 🚀 Quick Start
41
+
42
+ ### Option 1: OpenCode Configuration (Recommended)
26
43
 
44
+ Add to your `opencode.json`:
45
+ ```json
46
+ {
47
+ "mcp": {
48
+ "agent-browser-mcp-opencode": {
49
+ "command": ["npx", "-y", "@bsbofmusic/agent-browser-mcp-opencode"],
50
+ "enabled": true,
51
+ "type": "local"
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Option 2: Direct Run
27
58
  ```bash
28
59
  npx @bsbofmusic/agent-browser-mcp-opencode
29
60
  ```
30
61
 
31
- ---
62
+ ### Option 3: Global Install
63
+ ```bash
64
+ npm install -g @bsbofmusic/agent-browser-mcp-opencode
65
+ agent-browser-mcp-opencode
66
+ ```
32
67
 
33
- ## Tools
68
+ ---
34
69
 
35
- ### Core Tools
70
+ ## 🛠️ MCP Tools
36
71
 
72
+ ### Core Management Tools
37
73
  | Tool | Description |
38
74
  |------|-------------|
39
- | `browser_ensure` | Install/repair agent-browser & Chromium |
40
- | `browser_doctor` | Diagnose issues with fix recommendations |
41
- | `browser_help` | List all available commands |
42
- | `browser_version` | Show version info |
43
- | `browser_exec` | Execute any agent-browser CLI command |
44
-
45
- ### Action Tools
75
+ | `browser_ensure` | Manually trigger full repair (install/upgrade agent-browser and Chromium) |
76
+ | `browser_doctor` | Diagnose environment issues with actionable nextSteps |
77
+ | `browser_help` | List all available commands and capabilities |
78
+ | `browser_version` | Show version information for MCP and agent-browser |
79
+ | `browser_exec` | Execute any agent-browser CLI command (full passthrough) |
46
80
 
81
+ ### Structured Action Tools
47
82
  | Tool | Description |
48
83
  |------|-------------|
49
- | `browser_open` | Navigate to URL |
50
- | `browser_snapshot` | Get accessibility tree with refs |
51
- | `browser_click` | Click element |
52
- | `browser_fill` | Fill form field |
53
- | `browser_screenshot` | Take screenshot |
54
- | `browser_close` | Close browser |
55
- | `browser_get_text` | Get element text |
56
- | `browser_find` | Semantic locator (role/text/label) |
57
- | `browser_wait` | Wait for element/text/URL/time |
58
- | `browser_tab` | Manage tabs |
84
+ | `browser_open` | Navigate to a URL |
85
+ | `browser_snapshot` | Get accessibility tree with element refs (recommended for AI interaction) |
86
+ | `browser_click` | Click an element (@e1 ref or CSS selector) |
87
+ | `browser_fill` | Clear and fill a form field |
88
+ | `browser_screenshot` | Take a screenshot of the current page |
89
+ | `browser_close` | Close the browser and end the session |
90
+ | `browser_get_text` | Get text content from an element |
91
+ | `browser_find` | Find element by semantic locator (role/text/label/alt etc.) |
92
+ | `browser_wait` | Wait for element/text/URL to appear or specified time |
93
+ | `browser_tab` | Manage browser tabs (list/create/switch/close) |
59
94
 
60
95
  ---
61
96
 
62
- ## Usage Examples
97
+ ## 💡 Usage Examples
63
98
 
64
99
  ### Basic Workflow
65
-
66
100
  ```json
67
- // 1. Navigate
101
+ // 1. Navigate to page
68
102
  {"name": "browser_open", "arguments": {"url": "https://example.com"}}
69
103
 
70
- // 2. Get interactive elements
71
- {"name": "browser_snapshot", "arguments": {"interactive": true}}
104
+ // 2. Get interactive elements with refs
105
+ {"name": "browser_snapshot", "arguments": {"interactive": true, "compact": true}}
72
106
 
73
- // 3. Click element
107
+ // 3. Click the first interactive element
74
108
  {"name": "browser_click", "arguments": {"selector": "@e1"}}
109
+
110
+ // 4. Fill a form field
111
+ {"name": "browser_fill", "arguments": {"selector": "@e2", "value": "test@example.com"}}
112
+
113
+ // 5. Take a screenshot
114
+ {"name": "browser_screenshot", "arguments": {"fullPage": true}}
75
115
  ```
76
116
 
77
117
  ### Semantic Locator
78
-
79
118
  ```json
80
- {
81
- "name": "browser_find",
82
- "arguments": {
83
- "type": "role",
84
- "value": "button",
85
- "action": "click",
86
- "name": "Submit"
87
- }
88
- }
119
+ {"name": "browser_find", "arguments": {
120
+ "type": "role",
121
+ "value": "button",
122
+ "action": "click",
123
+ "name": "Submit Form"
124
+ }}
89
125
  ```
90
126
 
91
- ### Using browser_exec
92
-
127
+ ### Using browser_exec (Advanced)
93
128
  ```json
94
- {
95
- "name": "browser_exec",
96
- "arguments": {
97
- "command": "open example.com && snapshot -i && click #submit"
98
- }
99
- }
129
+ {"name": "browser_exec", "arguments": {
130
+ "command": "open example.com && snapshot -i && find role button click --name \"Submit\""
131
+ }}
100
132
  ```
101
133
 
102
134
  ---
103
135
 
104
- ## Auto-Update
136
+ ## ⚙️ Configuration
105
137
 
138
+ ### Environment Variables
106
139
  | Variable | Default | Description |
107
140
  |----------|---------|-------------|
108
- | `ALWAYS_LATEST` | `1` | Check for updates before each call |
109
- | `UPDATE_STRATEGY` | `simple` | `atomic` or `simple` |
110
- | `LOG_LEVEL` | `info` | `error`, `warn`, `info`, `debug` |
141
+ | `ALWAYS_LATEST` | `1` | Check for agent-browser updates before each call |
142
+ | `UPDATE_STRATEGY` | `simple` | Update strategy: `atomic` (with rollback) or `simple` (direct overwrite) |
143
+ | `LOG_LEVEL` | `info` | Log level: `error`, `warn`, `info`, `debug` |
144
+ | `RUNTIME_DIR` | `~/.agent-browser/mcp` | Runtime directory for MCP state |
145
+
146
+ ### Update Strategies
147
+ | Strategy | Description |
148
+ |----------|-------------|
149
+ | **Simple** (default) | Direct overwrite installation, no rollback capability |
150
+ | **Atomic** | Uses dual `runtime_active/` + `runtime_staging/` directories, validates before switching, rolls back on failure |
111
151
 
112
152
  ---
113
153
 
114
- ## Runtime Directories
154
+ ## 📁 Runtime Directories
115
155
 
116
- | Directory | Location |
156
+ | Directory | Location | Purpose |
117
157
  |-----------|----------|---------|
118
- | Config | `~/.agent-browser/` | User configuration |
119
- | Sessions | `~/.agent-browser/sessions/` | Saved auth states |
120
- | Cache | System temp | Temporary files |
158
+ | Config | `~/.agent-browser/` | User configuration and saved sessions |
159
+ | MCP Runtime | `~/.agent-browser/mcp/` | MCP runtime state and update staging |
160
+ | Cache | System temp directory | Temporary files and screenshots |
161
+
162
+ All directories are auto-created on first run and are fully rebuildable.
121
163
 
122
164
  ---
123
165
 
124
- ## Troubleshooting
166
+ ## 🔧 Troubleshooting
125
167
 
126
168
  ### Run Diagnostics
127
-
128
169
  ```json
129
170
  {"name": "browser_doctor"}
130
171
  ```
172
+ Output includes:
173
+ - Issue categories: network/permissions/dependencies/system_libraries/executable/browser/auth
174
+ - Severity levels: critical/high/medium
175
+ - Actionable `nextSteps` for fixing issues
131
176
 
132
- **Common Issues**
133
-
177
+ ### Common Issues & Solutions
134
178
  | Issue | Solution |
135
179
  |-------|----------|
136
180
  | agent-browser not installed | Run `browser_ensure` or `npx agent-browser install` |
137
181
  | Chromium not installed | Run `npx agent-browser install` |
138
- | Permission errors | Check npm global directory permissions |
182
+ | Permission errors | Check npm global directory permissions, or use npx |
183
+ | Windows daemon issues | Auto-falls back to npx, no manual action needed |
184
+ | Network issues | Set npm mirror: `npm config set registry https://registry.npmmirror.com` |
139
185
 
140
186
  ---
141
187
 
142
- ## Platform Compatibility
188
+ ## 🖥️ Platform Compatibility
143
189
 
144
190
  | Platform | Status | Notes |
145
- |--------|--------|-------|
146
- | Windows | ⚠️ Partial | agent-browser daemon has socket issues. Use npx or Playwright as alternative |
191
+ |----------|--------|-------|
192
+ | Windows | Full | Auto-fallback to npx when PATH issues occur |
147
193
  | macOS | ✅ Full | Native binary works perfectly |
148
194
  | Linux | ✅ Full | Native binary works perfectly |
149
195
 
150
196
  ---
151
197
 
152
- ## Verification
198
+ ## Verification
153
199
 
154
200
  ### 6.1 One-Click Deployment
155
-
156
201
  ```bash
157
202
  npx @bsbofmusic/agent-browser-mcp-opencode
158
203
  ```
159
-
160
- **Expected**: Auto-installs dependencies without manual steps.
204
+ **Expected**: Auto-installs all dependencies without manual steps.
161
205
 
162
206
  ### 6.2 Capability Coverage
163
-
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)
207
+ All 15 tools are available:
208
+ - 5 core management tools
209
+ - 10 structured action tools
210
+ - Full CLI passthrough via `browser_exec`
167
211
 
168
212
  ### 6.3 Stability
169
-
170
213
  - `browser_doctor` outputs 7 issue categories
171
- - `browser_ensure` is idempotent
172
- - Errors include logs + nextSteps
214
+ - `browser_ensure` is fully idempotent
215
+ - All errors include structured logs and actionable nextSteps
173
216
 
174
217
  ### 6.4 Auto-Update
175
-
176
- - `browser_version` shows current/latest
177
- - `ALWAYS_LATEST=1` checks on each call
218
+ - `browser_version` shows current and latest available versions
219
+ - `ALWAYS_LATEST=1` checks for updates on every call
220
+ - Failed updates do not break existing working environment
178
221
 
179
222
  ### 6.5 Deployment Ready
180
-
181
- No manual config after first enable. Survives MCP/Agent restart.
223
+ No manual configuration required after first enable. Survives MCP and AI agent restarts.
182
224
 
183
225
  ### 6.6 Failure Scenarios
184
-
185
226
  | Scenario | Expected Output |
186
227
  |----------|-----------------|
187
- | No network | Network error + nextSteps |
188
- | No permission | Permission error + nextSteps |
189
- | Missing deps | Dep error + nextSteps |
228
+ | No network | Network error + repair steps |
229
+ | No write permissions | Permission error + npx fallback instructions |
230
+ | Missing dependencies | Dependency error + install instructions |
190
231
 
191
232
  ---
192
233
 
193
- ## Wrapper Declaration
234
+ ## 📜 Wrapper Declaration
194
235
 
195
- This project is a **wrapper** for **agent-browser** by Vercel Labs.
236
+ This project is a wrapper for **agent-browser** by Vercel Labs.
196
237
 
197
- - **Upstream**: https://github.com/vercel-labs/agent-browser
198
- - **License**: Apache-2.0
238
+ - **Upstream Project**: https://github.com/vercel-labs/agent-browser
239
+ - **Upstream License**: Apache-2.0
199
240
  - This is **NOT** an official upstream release
200
- - Upstream trademarks/copyrights belong to respective owners
241
+ - Upstream trademarks, names, and copyrights belong to their respective owners
242
+ - This package follows all upstream license requirements
201
243
 
202
- See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for license details.
244
+ See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for full license details.
203
245
 
204
246
  ---
205
247
 
206
- ## License
248
+ ## 📄 License
207
249
 
208
- [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://img.shields.io/badge/License-Apache%202.0-blue)
250
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](https://opensource.org/licenses/Apache-2.0)
209
251
 
210
252
  Apache-2.0 License
211
253
 
@@ -213,7 +255,7 @@ Copyright (c) 2024-2025 bsbofmusic
213
255
 
214
256
  ---
215
257
 
216
- ## Support
258
+ ## 🆘 Support
217
259
 
218
- - **MCP issues**: https://github.com/issues
219
- - **agent-browser upstream**: https://github.com/vercel-labs/agent-browser/issues
260
+ - **MCP Issues**: https://github.com/bsbofmusic/agent-browser-mcp-opencode/issues
261
+ - **Upstream agent-browser Issues**: 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.2",
3
+ "version": "2.0.1",
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
  };
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
  };
@@ -2,7 +2,7 @@ import { execSync } from 'child_process';
2
2
  import ensure from '../ensure.js';
3
3
  const { getAgentBrowserVersion, checkForUpdates } = ensure;
4
4
 
5
- const MCP_VERSION = '1.0.0';
5
+ const MCP_VERSION = '2.0.0';
6
6
 
7
7
  export const versionTool = {
8
8
  name: 'browser_version',
@@ -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,4 @@
1
+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}
2
+ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"browser_version","arguments":{}}}
3
+ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"browser_help","arguments":{}}}
4
+ {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"browser_doctor","arguments":{}}}