@xbrowser/cli 1.23.0 → 1.24.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-FBQZ3PVD.js";
3
+ } from "./chunk-PGKV24QN.js";
4
4
  import {
5
5
  buildViewerUrl
6
6
  } from "./chunk-TZPKFUBT.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-LPW6QS4M.js";
3
+ } from "./chunk-FNXA6FDM.js";
4
4
  import {
5
5
  buildViewerUrl
6
6
  } from "./chunk-3OD76SUE.js";
@@ -3,7 +3,8 @@ import {
3
3
  } from "./chunk-3OD76SUE.js";
4
4
  import {
5
5
  HumanInteractionManager
6
- } from "./chunk-XTUAUFL3.js";
6
+ } from "./chunk-SOPN4XDK.js";
7
+ import "./chunk-ZTHE5RBZ.js";
7
8
  import "./chunk-Q2DFJQGS.js";
8
9
  import "./chunk-KFQGP6VL.js";
9
10
 
@@ -0,0 +1,243 @@
1
+ import {
2
+ executeChain,
3
+ executeCommand
4
+ } from "./chunk-BROEUPL3.js";
5
+ import "./chunk-2Q3GQRUP.js";
6
+ import "./chunk-JKVUFP3G.js";
7
+ import "./chunk-3OD76SUE.js";
8
+ import "./chunk-ZTHE5RBZ.js";
9
+ import "./chunk-Q2DFJQGS.js";
10
+ import "./chunk-7OP2ISEY.js";
11
+ import "./chunk-GYB5BOSP.js";
12
+ import "./chunk-MYH6FIBZ.js";
13
+ import "./chunk-GDKLH7ZY.js";
14
+ import "./chunk-WYETQ5C7.js";
15
+ import "./chunk-75DNUI6M.js";
16
+ import "./chunk-KFQGP6VL.js";
17
+
18
+ // src/mcp/server.ts
19
+ var TOOLS = [
20
+ {
21
+ name: "browser_navigate",
22
+ description: "Navigate to a URL and optionally run follow-up commands as a chain. Use this to open pages. Session auto-creates; pass session to isolate state.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ url: { type: "string", description: "URL to navigate to" },
27
+ session: { type: "string", description: "Session name (default: mcp)" },
28
+ chain: {
29
+ type: "string",
30
+ description: 'Optional follow-up command chain joined by && (e.g. `click "#btn" && text .result`). Full command list: xbrowser --help'
31
+ }
32
+ },
33
+ required: ["url"]
34
+ }
35
+ },
36
+ {
37
+ name: "browser_act",
38
+ description: "Run an interaction command on the current page: click / fill / type / press / select / check / hover / scroll / wait / eval. Selectors support CSS and text= prefix.",
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: {
42
+ command: {
43
+ type: "string",
44
+ description: "Command word, e.g. click / fill / press / wait / eval"
45
+ },
46
+ args: {
47
+ type: "object",
48
+ description: 'Command parameters, e.g. { selector: "#input", value: "hello" }'
49
+ },
50
+ session: { type: "string", description: "Session name (default: mcp)" }
51
+ },
52
+ required: ["command"]
53
+ }
54
+ },
55
+ {
56
+ name: "browser_read",
57
+ description: "Read page content: text / html / title / url. Cheaper than snapshot for plain content.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ kind: { type: "string", enum: ["text", "html", "title", "url"] },
62
+ selector: { type: "string", description: "Optional CSS selector to scope the read" },
63
+ session: { type: "string", description: "Session name (default: mcp)" }
64
+ },
65
+ required: ["kind"]
66
+ }
67
+ },
68
+ {
69
+ name: "browser_snapshot",
70
+ description: "Accessibility-tree snapshot of interactive elements \u2014 the token-efficient way for an agent to see the page. Returns refs usable with browser_act selectors.",
71
+ inputSchema: {
72
+ type: "object",
73
+ properties: {
74
+ interactiveOnly: { type: "boolean", description: "Only interactive elements (default true)" },
75
+ session: { type: "string", description: "Session name (default: mcp)" }
76
+ }
77
+ }
78
+ },
79
+ {
80
+ name: "browser_screenshot",
81
+ description: "Take a screenshot. Returns the file path (use screenshot --base64 chain for inline images).",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ output: { type: "string", description: "Output PNG path" },
86
+ fullPage: { type: "boolean" },
87
+ session: { type: "string", description: "Session name (default: mcp)" }
88
+ }
89
+ }
90
+ },
91
+ {
92
+ name: "browser_network",
93
+ description: "Inspect captured network requests: list / top / analyze.",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ action: { type: "string", enum: ["list", "top", "analyze"] },
98
+ session: { type: "string", description: "Session name (default: mcp)" }
99
+ },
100
+ required: ["action"]
101
+ }
102
+ },
103
+ {
104
+ name: "browser_replay",
105
+ description: "Replay a recorded flow with the self-healing engine \u2014 survives site redesigns via selector cascade (semantic candidates \u2192 fingerprints \u2192 coordinates \u2192 knowledge base). Deterministic, no LLM cost.",
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ file: { type: "string", description: "Path to recorded YAML/JSON" },
110
+ slowMo: { type: "number", description: "ms delay between steps" },
111
+ session: { type: "string", description: "Session name (default: mcp)" }
112
+ },
113
+ required: ["file"]
114
+ }
115
+ }
116
+ ];
117
+ var DEFAULT_SESSION = "mcp";
118
+ async function runTool(name, args) {
119
+ const session = args.session || DEFAULT_SESSION;
120
+ const sessionOpts = { sessionName: session };
121
+ const text = (v) => ({ content: [{ type: "text", text: JSON.stringify(v, null, 2) }] });
122
+ switch (name) {
123
+ case "browser_navigate": {
124
+ const url = args.url;
125
+ let chain = `goto ${url}`;
126
+ if (args.chain) chain += ` && ${args.chain}`;
127
+ const result = await executeChain(chain, sessionOpts);
128
+ return text({ ok: result.success, steps: result.steps?.map((s) => ({ command: s.command, success: s.success })) });
129
+ }
130
+ case "browser_act": {
131
+ const command = args.command;
132
+ const cmdArgs = args.args || {};
133
+ const result = await executeCommand(command, cmdArgs, session, {});
134
+ return text(result);
135
+ }
136
+ case "browser_read": {
137
+ const kind = args.kind;
138
+ const params = {};
139
+ if (args.selector) params.selector = args.selector;
140
+ const result = await executeCommand(kind, params, session, {});
141
+ return text(result);
142
+ }
143
+ case "browser_snapshot": {
144
+ const params = { interactiveOnly: args.interactiveOnly !== false };
145
+ const result = await executeCommand("snapshot", params, session, {});
146
+ return text(result);
147
+ }
148
+ case "browser_screenshot": {
149
+ const params = {
150
+ output: args.output || `output/mcp-${Date.now()}.png`,
151
+ ...args.fullPage ? { fullPage: true } : {}
152
+ };
153
+ const result = await executeCommand("screenshot", params, session, {});
154
+ return text(result);
155
+ }
156
+ case "browser_network": {
157
+ const result = await executeCommand(`network:${args.action}`, {}, session, {});
158
+ return text(result);
159
+ }
160
+ case "browser_replay": {
161
+ const params = {
162
+ file: args.file,
163
+ ...typeof args.slowMo === "number" ? { slowMo: args.slowMo } : {}
164
+ };
165
+ const result = await executeCommand("replay", params, session, {});
166
+ return text(result);
167
+ }
168
+ default:
169
+ throw new Error(`unknown tool: ${name}`);
170
+ }
171
+ }
172
+ function reply(id, result) {
173
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
174
+ }
175
+ function replyError(id, code, message) {
176
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
177
+ }
178
+ async function handleRequest(req) {
179
+ if (req.id === void 0) return;
180
+ switch (req.method) {
181
+ case "initialize":
182
+ reply(req.id, {
183
+ protocolVersion: "2024-11-05",
184
+ capabilities: { tools: {} },
185
+ serverInfo: { name: "xbrowser", version: process.env.npm_package_version || "1.23.0" }
186
+ });
187
+ break;
188
+ case "notifications/initialized":
189
+ break;
190
+ case "tools/list":
191
+ reply(req.id, { tools: TOOLS });
192
+ break;
193
+ case "tools/call": {
194
+ const toolName = req.params.name;
195
+ const toolArgs = req.params.arguments || {};
196
+ try {
197
+ const result = await runTool(toolName, toolArgs);
198
+ reply(req.id, { content: result.content });
199
+ } catch (err) {
200
+ reply(req.id, {
201
+ content: [{ type: "text", text: `Error: ${err.message}` }],
202
+ isError: true
203
+ });
204
+ }
205
+ break;
206
+ }
207
+ default:
208
+ replyError(req.id, -32601, `method not found: ${req.method}`);
209
+ }
210
+ }
211
+ var inflight = 0;
212
+ function startMcpStdio() {
213
+ let buffer = "";
214
+ process.stdin.setEncoding("utf8");
215
+ process.stdin.on("data", (chunk) => {
216
+ buffer += chunk;
217
+ let idx;
218
+ while ((idx = buffer.indexOf("\n")) >= 0) {
219
+ const line = buffer.slice(0, idx).trim();
220
+ buffer = buffer.slice(idx + 1);
221
+ if (!line) continue;
222
+ try {
223
+ const req = JSON.parse(line);
224
+ inflight++;
225
+ handleRequest(req).catch((e) => {
226
+ if (req.id !== void 0) replyError(req.id, -32603, e.message);
227
+ }).finally(() => {
228
+ inflight--;
229
+ if (stdinClosed && inflight === 0) process.exit(0);
230
+ });
231
+ } catch {
232
+ }
233
+ }
234
+ });
235
+ let stdinClosed = false;
236
+ process.stdin.on("end", () => {
237
+ stdinClosed = true;
238
+ if (inflight === 0) process.exit(0);
239
+ });
240
+ }
241
+ export {
242
+ startMcpStdio
243
+ };
@@ -0,0 +1,240 @@
1
+ import {
2
+ executeChain,
3
+ executeCommand
4
+ } from "./chunk-A7RZYSWM.js";
5
+ import "./chunk-JKVUFP3G.js";
6
+ import "./chunk-3OD76SUE.js";
7
+ import "./chunk-ZTHE5RBZ.js";
8
+ import "./chunk-Q2DFJQGS.js";
9
+ import "./chunk-IFKJO2UR.js";
10
+ import "./chunk-GYB5BOSP.js";
11
+ import "./chunk-MYH6FIBZ.js";
12
+ import "./chunk-GDKLH7ZY.js";
13
+ import "./chunk-KFQGP6VL.js";
14
+
15
+ // src/mcp/server.ts
16
+ var TOOLS = [
17
+ {
18
+ name: "browser_navigate",
19
+ description: "Navigate to a URL and optionally run follow-up commands as a chain. Use this to open pages. Session auto-creates; pass session to isolate state.",
20
+ inputSchema: {
21
+ type: "object",
22
+ properties: {
23
+ url: { type: "string", description: "URL to navigate to" },
24
+ session: { type: "string", description: "Session name (default: mcp)" },
25
+ chain: {
26
+ type: "string",
27
+ description: 'Optional follow-up command chain joined by && (e.g. `click "#btn" && text .result`). Full command list: xbrowser --help'
28
+ }
29
+ },
30
+ required: ["url"]
31
+ }
32
+ },
33
+ {
34
+ name: "browser_act",
35
+ description: "Run an interaction command on the current page: click / fill / type / press / select / check / hover / scroll / wait / eval. Selectors support CSS and text= prefix.",
36
+ inputSchema: {
37
+ type: "object",
38
+ properties: {
39
+ command: {
40
+ type: "string",
41
+ description: "Command word, e.g. click / fill / press / wait / eval"
42
+ },
43
+ args: {
44
+ type: "object",
45
+ description: 'Command parameters, e.g. { selector: "#input", value: "hello" }'
46
+ },
47
+ session: { type: "string", description: "Session name (default: mcp)" }
48
+ },
49
+ required: ["command"]
50
+ }
51
+ },
52
+ {
53
+ name: "browser_read",
54
+ description: "Read page content: text / html / title / url. Cheaper than snapshot for plain content.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ kind: { type: "string", enum: ["text", "html", "title", "url"] },
59
+ selector: { type: "string", description: "Optional CSS selector to scope the read" },
60
+ session: { type: "string", description: "Session name (default: mcp)" }
61
+ },
62
+ required: ["kind"]
63
+ }
64
+ },
65
+ {
66
+ name: "browser_snapshot",
67
+ description: "Accessibility-tree snapshot of interactive elements \u2014 the token-efficient way for an agent to see the page. Returns refs usable with browser_act selectors.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ interactiveOnly: { type: "boolean", description: "Only interactive elements (default true)" },
72
+ session: { type: "string", description: "Session name (default: mcp)" }
73
+ }
74
+ }
75
+ },
76
+ {
77
+ name: "browser_screenshot",
78
+ description: "Take a screenshot. Returns the file path (use screenshot --base64 chain for inline images).",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ output: { type: "string", description: "Output PNG path" },
83
+ fullPage: { type: "boolean" },
84
+ session: { type: "string", description: "Session name (default: mcp)" }
85
+ }
86
+ }
87
+ },
88
+ {
89
+ name: "browser_network",
90
+ description: "Inspect captured network requests: list / top / analyze.",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ action: { type: "string", enum: ["list", "top", "analyze"] },
95
+ session: { type: "string", description: "Session name (default: mcp)" }
96
+ },
97
+ required: ["action"]
98
+ }
99
+ },
100
+ {
101
+ name: "browser_replay",
102
+ description: "Replay a recorded flow with the self-healing engine \u2014 survives site redesigns via selector cascade (semantic candidates \u2192 fingerprints \u2192 coordinates \u2192 knowledge base). Deterministic, no LLM cost.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ file: { type: "string", description: "Path to recorded YAML/JSON" },
107
+ slowMo: { type: "number", description: "ms delay between steps" },
108
+ session: { type: "string", description: "Session name (default: mcp)" }
109
+ },
110
+ required: ["file"]
111
+ }
112
+ }
113
+ ];
114
+ var DEFAULT_SESSION = "mcp";
115
+ async function runTool(name, args) {
116
+ const session = args.session || DEFAULT_SESSION;
117
+ const sessionOpts = { sessionName: session };
118
+ const text = (v) => ({ content: [{ type: "text", text: JSON.stringify(v, null, 2) }] });
119
+ switch (name) {
120
+ case "browser_navigate": {
121
+ const url = args.url;
122
+ let chain = `goto ${url}`;
123
+ if (args.chain) chain += ` && ${args.chain}`;
124
+ const result = await executeChain(chain, sessionOpts);
125
+ return text({ ok: result.success, steps: result.steps?.map((s) => ({ command: s.command, success: s.success })) });
126
+ }
127
+ case "browser_act": {
128
+ const command = args.command;
129
+ const cmdArgs = args.args || {};
130
+ const result = await executeCommand(command, cmdArgs, session, {});
131
+ return text(result);
132
+ }
133
+ case "browser_read": {
134
+ const kind = args.kind;
135
+ const params = {};
136
+ if (args.selector) params.selector = args.selector;
137
+ const result = await executeCommand(kind, params, session, {});
138
+ return text(result);
139
+ }
140
+ case "browser_snapshot": {
141
+ const params = { interactiveOnly: args.interactiveOnly !== false };
142
+ const result = await executeCommand("snapshot", params, session, {});
143
+ return text(result);
144
+ }
145
+ case "browser_screenshot": {
146
+ const params = {
147
+ output: args.output || `output/mcp-${Date.now()}.png`,
148
+ ...args.fullPage ? { fullPage: true } : {}
149
+ };
150
+ const result = await executeCommand("screenshot", params, session, {});
151
+ return text(result);
152
+ }
153
+ case "browser_network": {
154
+ const result = await executeCommand(`network:${args.action}`, {}, session, {});
155
+ return text(result);
156
+ }
157
+ case "browser_replay": {
158
+ const params = {
159
+ file: args.file,
160
+ ...typeof args.slowMo === "number" ? { slowMo: args.slowMo } : {}
161
+ };
162
+ const result = await executeCommand("replay", params, session, {});
163
+ return text(result);
164
+ }
165
+ default:
166
+ throw new Error(`unknown tool: ${name}`);
167
+ }
168
+ }
169
+ function reply(id, result) {
170
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
171
+ }
172
+ function replyError(id, code, message) {
173
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
174
+ }
175
+ async function handleRequest(req) {
176
+ if (req.id === void 0) return;
177
+ switch (req.method) {
178
+ case "initialize":
179
+ reply(req.id, {
180
+ protocolVersion: "2024-11-05",
181
+ capabilities: { tools: {} },
182
+ serverInfo: { name: "xbrowser", version: process.env.npm_package_version || "1.23.0" }
183
+ });
184
+ break;
185
+ case "notifications/initialized":
186
+ break;
187
+ case "tools/list":
188
+ reply(req.id, { tools: TOOLS });
189
+ break;
190
+ case "tools/call": {
191
+ const toolName = req.params.name;
192
+ const toolArgs = req.params.arguments || {};
193
+ try {
194
+ const result = await runTool(toolName, toolArgs);
195
+ reply(req.id, { content: result.content });
196
+ } catch (err) {
197
+ reply(req.id, {
198
+ content: [{ type: "text", text: `Error: ${err.message}` }],
199
+ isError: true
200
+ });
201
+ }
202
+ break;
203
+ }
204
+ default:
205
+ replyError(req.id, -32601, `method not found: ${req.method}`);
206
+ }
207
+ }
208
+ var inflight = 0;
209
+ function startMcpStdio() {
210
+ let buffer = "";
211
+ process.stdin.setEncoding("utf8");
212
+ process.stdin.on("data", (chunk) => {
213
+ buffer += chunk;
214
+ let idx;
215
+ while ((idx = buffer.indexOf("\n")) >= 0) {
216
+ const line = buffer.slice(0, idx).trim();
217
+ buffer = buffer.slice(idx + 1);
218
+ if (!line) continue;
219
+ try {
220
+ const req = JSON.parse(line);
221
+ inflight++;
222
+ handleRequest(req).catch((e) => {
223
+ if (req.id !== void 0) replyError(req.id, -32603, e.message);
224
+ }).finally(() => {
225
+ inflight--;
226
+ if (stdinClosed && inflight === 0) process.exit(0);
227
+ });
228
+ } catch {
229
+ }
230
+ }
231
+ });
232
+ let stdinClosed = false;
233
+ process.stdin.on("end", () => {
234
+ stdinClosed = true;
235
+ if (inflight === 0) process.exit(0);
236
+ });
237
+ }
238
+ export {
239
+ startMcpStdio
240
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -130,4 +130,4 @@
130
130
  "optionalDependencies": {
131
131
  "undici": "^7.29.1"
132
132
  }
133
- }
133
+ }