@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 +68 -150
- package/package.json +2 -1
- package/src/ensure.js +167 -92
- package/src/tools/actions/click.js +25 -12
- package/src/tools/actions/fill.js +25 -12
- package/src/tools/actions/open.js +25 -13
- package/src/tools/actions/snapshot.js +25 -11
- package/src/tools/actions/wait.js +18 -4
- package/src/tools/exec.js +15 -30
- package/test_input.txt +2 -0
- package/test_output.txt +15 -0
- package/test_results.txt +21 -0
package/README.md
CHANGED
|
@@ -1,80 +1,54 @@
|
|
|
1
1
|
# @bsbofmusic/agent-browser-mcp-opencode
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
8
|
+
[](https://img.shields.io/badge/MCP-blue)
|
|
9
|
+
[](https://img.shields.io/badge/License-Apache%202.0-blue)
|
|
10
|
+
[](https://img.shields.io/badge/node-%3E218.0.0-blue)
|
|
11
|
+
[](https://img.shields.io/npm/v/@bsbofmusic%2Fagent-browser-mcp-opencode)
|
|
10
12
|
|
|
11
|
-
|
|
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
|
-
|
|
17
|
+
<div align="center">
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
**Browser automation for AI agents via Model Context Protocol**
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
</div>
|
|
25
22
|
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
33
|
+
## Tools
|
|
60
34
|
|
|
61
35
|
### Core Tools
|
|
62
36
|
|
|
63
37
|
| Tool | Description |
|
|
64
38
|
|------|-------------|
|
|
65
|
-
| `browser_ensure` |
|
|
66
|
-
| `browser_doctor` | Diagnose issues with
|
|
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
|
-
###
|
|
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
|
|
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
|
-
##
|
|
62
|
+
## Usage Examples
|
|
89
63
|
|
|
90
|
-
###
|
|
64
|
+
### Basic Workflow
|
|
91
65
|
|
|
92
|
-
**Input:**
|
|
93
66
|
```json
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"arguments": {"url": "https://example.com"}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
67
|
+
// 1. Navigate
|
|
68
|
+
{"name": "browser_open", "arguments": {"url": "https://example.com"}}
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
### Semantic Locator
|
|
78
|
+
|
|
120
79
|
```json
|
|
121
80
|
{
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
|
|
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
|
|
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 |
|
|
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
|
|
126
|
+
### Run Diagnostics
|
|
186
127
|
|
|
187
128
|
```json
|
|
188
|
-
{
|
|
189
|
-
"name": "browser_doctor"
|
|
190
|
-
}
|
|
129
|
+
{"name": "browser_doctor"}
|
|
191
130
|
```
|
|
192
131
|
|
|
193
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
208
|
-
- Check npm global directory permissions
|
|
209
|
-
- Consider using npx instead
|
|
142
|
+
## Platform Compatibility
|
|
210
143
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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
|
|
160
|
+
**Expected**: Auto-installs dependencies without manual steps.
|
|
226
161
|
|
|
227
162
|
### 6.2 Capability Coverage
|
|
228
163
|
|
|
229
|
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
+
[](https://img.shields.io/badge/License-Apache%202.0-blue)
|
|
283
209
|
|
|
284
|
-
|
|
210
|
+
Apache-2.0 License
|
|
285
211
|
|
|
286
|
-
|
|
212
|
+
Copyright (c) 2024-2025 bsbofmusic
|
|
287
213
|
|
|
288
214
|
---
|
|
289
215
|
|
|
290
216
|
## Support
|
|
291
217
|
|
|
292
|
-
- MCP issues
|
|
293
|
-
- agent-browser upstream
|
|
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": "
|
|
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 = '
|
|
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 =
|
|
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
|
|
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.
|
|
90
|
-
|
|
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
|
|
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
|
-
|
|
129
|
-
log('
|
|
130
|
-
logs
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
logs.
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
311
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
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:
|
|
54
|
+
parsed = { success: result.ok };
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
return {
|
|
50
|
-
ok:
|
|
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
|
-
|
|
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:
|
|
83
|
+
stderr: error.stderr || errorMsg,
|
|
84
|
+
error: errorMsg,
|
|
72
85
|
selector,
|
|
73
86
|
duration: elapsed,
|
|
74
87
|
nextSteps,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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:
|
|
52
|
+
parsed = { success: result.ok };
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
return {
|
|
48
|
-
ok:
|
|
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
|
-
|
|
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:
|
|
82
|
+
stderr: error.stderr || errorMsg,
|
|
83
|
+
error: errorMsg,
|
|
71
84
|
selector,
|
|
72
85
|
value,
|
|
73
86
|
duration: elapsed,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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:
|
|
72
|
+
parsed = { success: result.ok, raw: result.stdout };
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
return {
|
|
69
|
-
ok:
|
|
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
|
-
|
|
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:
|
|
101
|
+
stderr: error.stderr || errorMsg,
|
|
102
|
+
error: errorMsg,
|
|
91
103
|
url,
|
|
92
104
|
duration: elapsed,
|
|
93
105
|
nextSteps,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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:
|
|
74
|
+
parsed = { success: result.ok, raw: result.stdout };
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
return {
|
|
69
|
-
ok:
|
|
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
|
-
|
|
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:
|
|
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)
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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 {
|
|
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
|
|
51
|
-
log('info', 'Executing command', { command: fullCommand, timeoutMs, cwd });
|
|
51
|
+
const cmd = json ? `${command} --json` : command;
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
encoding: 'utf8',
|
|
53
|
+
const result = await runAgentBrowserCommand(cmd, {
|
|
55
54
|
timeout: timeoutMs,
|
|
56
|
-
|
|
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:
|
|
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
package/test_output.txt
ADDED
|
@@ -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\"}}'"}
|
package/test_results.txt
ADDED
|
@@ -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
|
+
|