agentic-browser 0.1.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/AGENTS.md +128 -0
- package/README.md +226 -0
- package/dist/cli/index.mjs +374 -0
- package/dist/index.mjs +3 -0
- package/dist/mcp/index.mjs +170 -0
- package/dist/runtime-C-oYEtN0.mjs +1708 -0
- package/dist/setup-CULSgM_M.mjs +76 -0
- package/extension/background/index.ts +3 -0
- package/extension/content/index.ts +3 -0
- package/extension/manifest.json +18 -0
- package/package.json +68 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { r as createCliRuntime } from "../runtime-C-oYEtN0.mjs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
//#region src/cli/commands/agent.ts
|
|
8
|
+
const STATE_FILE_NAME = "agent-state.json";
|
|
9
|
+
function stateFilePath(runtime) {
|
|
10
|
+
return path.join(runtime.context.config.logDir, STATE_FILE_NAME);
|
|
11
|
+
}
|
|
12
|
+
function loadState(runtime) {
|
|
13
|
+
const filePath = stateFilePath(runtime);
|
|
14
|
+
if (!fs.existsSync(filePath)) return { sessionId: null };
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return { sessionId: null };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function saveState(runtime, state) {
|
|
22
|
+
const dir = path.dirname(stateFilePath(runtime));
|
|
23
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
fs.writeFileSync(stateFilePath(runtime), JSON.stringify(state, null, 2));
|
|
25
|
+
}
|
|
26
|
+
function requireSessionId(runtime) {
|
|
27
|
+
const state = loadState(runtime);
|
|
28
|
+
if (!state.sessionId) throw new Error("No active agent session. Run: agentic-browser agent start");
|
|
29
|
+
return state.sessionId;
|
|
30
|
+
}
|
|
31
|
+
function nextCommandId() {
|
|
32
|
+
return `cmd-${Date.now()}-${crypto.randomBytes(3).toString("hex")}`;
|
|
33
|
+
}
|
|
34
|
+
async function ensureReady(runtime, sessionId) {
|
|
35
|
+
if (runtime.api.getSession(sessionId).status === "ready") return;
|
|
36
|
+
const restarted = await runtime.api.restartSession(sessionId);
|
|
37
|
+
if (restarted.status !== "ready") throw new Error(`Session not ready after restart: ${restarted.status}`);
|
|
38
|
+
}
|
|
39
|
+
async function agentStart(runtime) {
|
|
40
|
+
const session = await runtime.api.createSession({ browser: "chrome" });
|
|
41
|
+
saveState(runtime, { sessionId: session.sessionId });
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
action: "start",
|
|
45
|
+
sessionId: session.sessionId
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function agentStatus(runtime) {
|
|
49
|
+
const sessionId = requireSessionId(runtime);
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
action: "status",
|
|
53
|
+
...runtime.api.getSession(sessionId)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function agentStop(runtime) {
|
|
57
|
+
const sessionId = requireSessionId(runtime);
|
|
58
|
+
await runtime.api.terminateSession(sessionId);
|
|
59
|
+
saveState(runtime, { sessionId: null });
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
action: "stop",
|
|
63
|
+
sessionId
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function agentRun(runtime, input) {
|
|
67
|
+
const sessionId = requireSessionId(runtime);
|
|
68
|
+
await ensureReady(runtime, sessionId);
|
|
69
|
+
const attempt = () => runtime.api.executeCommand(sessionId, {
|
|
70
|
+
commandId: nextCommandId(),
|
|
71
|
+
type: input.type,
|
|
72
|
+
payload: input.payload
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const first = await attempt();
|
|
76
|
+
if (first.resultStatus === "failed") throw new Error(first.resultMessage ?? "Command failed");
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
action: "run",
|
|
80
|
+
...first
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
await runtime.api.restartSession(sessionId);
|
|
84
|
+
const second = await attempt();
|
|
85
|
+
if (second.resultStatus === "failed") throw new Error(second.resultMessage ?? "Command failed after retry");
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
action: "run",
|
|
89
|
+
...second
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function agentContent(runtime, input) {
|
|
94
|
+
const sessionId = requireSessionId(runtime);
|
|
95
|
+
await ensureReady(runtime, sessionId);
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
action: "content",
|
|
99
|
+
...await runtime.api.getContent(sessionId, {
|
|
100
|
+
mode: input.mode,
|
|
101
|
+
selector: input.selector
|
|
102
|
+
})
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function agentElements(runtime, input) {
|
|
106
|
+
const sessionId = requireSessionId(runtime);
|
|
107
|
+
await ensureReady(runtime, sessionId);
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
action: "elements",
|
|
111
|
+
...await runtime.api.getInteractiveElements(sessionId, {
|
|
112
|
+
roles: input.roles,
|
|
113
|
+
visibleOnly: input.visibleOnly,
|
|
114
|
+
limit: input.limit,
|
|
115
|
+
selector: input.selector
|
|
116
|
+
})
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function agentMemorySearch(runtime, input) {
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
action: "memory-search",
|
|
123
|
+
...runtime.api.searchMemory(input)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function agentCleanup(runtime, input) {
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
action: "cleanup",
|
|
130
|
+
...runtime.api.cleanupSessions(input)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/cli/output/result-formatter.ts
|
|
136
|
+
function formatResult(resultStatus, message) {
|
|
137
|
+
return {
|
|
138
|
+
resultStatus,
|
|
139
|
+
message
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/cli/commands/session-start.ts
|
|
145
|
+
async function runSessionStart(runtime, input) {
|
|
146
|
+
const session = await runtime.api.createSession(input);
|
|
147
|
+
return {
|
|
148
|
+
...formatResult("success", "Session started"),
|
|
149
|
+
sessionId: session.sessionId
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/cli/commands/session-status.ts
|
|
155
|
+
async function runSessionStatus(runtime, input) {
|
|
156
|
+
return runtime.api.getSession(input.sessionId);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/cli/commands/session-stop.ts
|
|
161
|
+
async function runSessionStop(runtime, input) {
|
|
162
|
+
await runtime.api.terminateSession(input.sessionId);
|
|
163
|
+
return { terminated: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/cli/commands/session-cleanup.ts
|
|
168
|
+
async function runSessionCleanup(runtime, input) {
|
|
169
|
+
return runtime.api.cleanupSessions(input);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/cli/commands/session-restart.ts
|
|
174
|
+
async function runSessionRestart(runtime, input) {
|
|
175
|
+
return await runtime.api.restartSession(input.sessionId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/cli/commands/session-auth.ts
|
|
180
|
+
async function runSessionAuth(runtime, input) {
|
|
181
|
+
const token = runtime.api.rotateSessionToken(input.sessionId);
|
|
182
|
+
return {
|
|
183
|
+
token,
|
|
184
|
+
valid: runtime.context.tokenService.validate(input.sessionId, token)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/cli/commands/command-run.ts
|
|
190
|
+
async function runCommand(runtime, input) {
|
|
191
|
+
if (!input.sessionId) throw new Error("No active session. Start or restart a session first.");
|
|
192
|
+
try {
|
|
193
|
+
return await runtime.api.executeCommand(input.sessionId, {
|
|
194
|
+
commandId: input.commandId,
|
|
195
|
+
type: input.type,
|
|
196
|
+
payload: input.payload
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw new Error(`Command rejected: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/cli/commands/page-content.ts
|
|
205
|
+
async function runPageContent(runtime, input) {
|
|
206
|
+
return await runtime.api.getContent(input.sessionId, {
|
|
207
|
+
mode: input.mode,
|
|
208
|
+
selector: input.selector
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/cli/commands/memory-search.ts
|
|
214
|
+
async function runMemorySearch(runtime, input) {
|
|
215
|
+
if (!input.taskIntent || !input.taskIntent.trim()) throw new Error("taskIntent is required");
|
|
216
|
+
return runtime.api.searchMemory(input);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/cli/commands/memory-inspect.ts
|
|
221
|
+
async function runMemoryInspect(runtime, input) {
|
|
222
|
+
if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
|
|
223
|
+
return runtime.api.inspectMemory(input.insightId);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/cli/commands/memory-verify.ts
|
|
228
|
+
async function runMemoryVerify(runtime, input) {
|
|
229
|
+
if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
|
|
230
|
+
return runtime.api.verifyMemory(input.insightId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/cli/commands/memory-stats.ts
|
|
235
|
+
async function runMemoryStats(runtime) {
|
|
236
|
+
return runtime.api.memoryStats();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/cli/index.ts
|
|
241
|
+
async function main() {
|
|
242
|
+
const runtime = createCliRuntime();
|
|
243
|
+
const program = new Command();
|
|
244
|
+
program.name("agentic-browser").description("Agentic browser CLI");
|
|
245
|
+
program.command("session:start").action(async () => {
|
|
246
|
+
const result = await runSessionStart(runtime, { browser: "chrome" });
|
|
247
|
+
console.log(JSON.stringify(result));
|
|
248
|
+
});
|
|
249
|
+
program.command("session:status").argument("<sessionId>").action(async (sessionId) => {
|
|
250
|
+
const result = await runSessionStatus(runtime, { sessionId });
|
|
251
|
+
console.log(JSON.stringify(result));
|
|
252
|
+
});
|
|
253
|
+
program.command("session:stop").argument("<sessionId>").action(async (sessionId) => {
|
|
254
|
+
const result = await runSessionStop(runtime, { sessionId });
|
|
255
|
+
console.log(JSON.stringify(result));
|
|
256
|
+
});
|
|
257
|
+
program.command("session:cleanup").option("--max-age-days <days>", "remove terminated sessions older than N days", "7").option("--dry-run", "show what would be removed without deleting").action(async (options) => {
|
|
258
|
+
const result = await runSessionCleanup(runtime, {
|
|
259
|
+
maxAgeDays: Number.parseFloat(options.maxAgeDays),
|
|
260
|
+
dryRun: Boolean(options.dryRun)
|
|
261
|
+
});
|
|
262
|
+
console.log(JSON.stringify(result));
|
|
263
|
+
});
|
|
264
|
+
program.command("session:restart").argument("<sessionId>").action(async (sessionId) => {
|
|
265
|
+
const result = await runSessionRestart(runtime, { sessionId });
|
|
266
|
+
console.log(JSON.stringify(result));
|
|
267
|
+
});
|
|
268
|
+
program.command("session:auth").argument("<sessionId>").action(async (sessionId) => {
|
|
269
|
+
const result = await runSessionAuth(runtime, { sessionId });
|
|
270
|
+
console.log(JSON.stringify(result));
|
|
271
|
+
});
|
|
272
|
+
program.command("command:run").argument("<sessionId>").argument("<commandId>").argument("<type>").argument("<payloadJson>").action(async (sessionId, commandId, type, payloadJson) => {
|
|
273
|
+
const result = await runCommand(runtime, {
|
|
274
|
+
sessionId,
|
|
275
|
+
commandId,
|
|
276
|
+
type,
|
|
277
|
+
payload: JSON.parse(payloadJson)
|
|
278
|
+
});
|
|
279
|
+
console.log(JSON.stringify(result));
|
|
280
|
+
});
|
|
281
|
+
program.command("page:content").argument("<sessionId>").option("--mode <mode>", "title|text|html", "text").option("--selector <selector>", "optional CSS selector").action(async (sessionId, options) => {
|
|
282
|
+
const result = await runPageContent(runtime, {
|
|
283
|
+
sessionId,
|
|
284
|
+
mode: options.mode,
|
|
285
|
+
selector: options.selector
|
|
286
|
+
});
|
|
287
|
+
console.log(JSON.stringify(result));
|
|
288
|
+
});
|
|
289
|
+
program.command("memory:search").argument("<taskIntent>").option("--domain <domain>", "website domain filter").option("--limit <limit>", "max results", "10").action(async (taskIntent, options) => {
|
|
290
|
+
const result = await runMemorySearch(runtime, {
|
|
291
|
+
taskIntent,
|
|
292
|
+
siteDomain: options.domain,
|
|
293
|
+
limit: Number.parseInt(options.limit, 10)
|
|
294
|
+
});
|
|
295
|
+
console.log(JSON.stringify(result));
|
|
296
|
+
});
|
|
297
|
+
program.command("memory:inspect").argument("<insightId>").action(async (insightId) => {
|
|
298
|
+
const result = await runMemoryInspect(runtime, { insightId });
|
|
299
|
+
console.log(JSON.stringify(result));
|
|
300
|
+
});
|
|
301
|
+
program.command("memory:verify").argument("<insightId>").action(async (insightId) => {
|
|
302
|
+
const result = await runMemoryVerify(runtime, { insightId });
|
|
303
|
+
console.log(JSON.stringify(result));
|
|
304
|
+
});
|
|
305
|
+
program.command("memory:stats").action(async () => {
|
|
306
|
+
const result = await runMemoryStats(runtime);
|
|
307
|
+
console.log(JSON.stringify(result));
|
|
308
|
+
});
|
|
309
|
+
const agent = program.command("agent").description("Stateful agent wrapper with session persistence and auto-retry");
|
|
310
|
+
agent.command("start").action(async () => {
|
|
311
|
+
const result = await agentStart(runtime);
|
|
312
|
+
console.log(JSON.stringify(result));
|
|
313
|
+
});
|
|
314
|
+
agent.command("status").action(async () => {
|
|
315
|
+
const result = await agentStatus(runtime);
|
|
316
|
+
console.log(JSON.stringify(result));
|
|
317
|
+
});
|
|
318
|
+
agent.command("stop").action(async () => {
|
|
319
|
+
const result = await agentStop(runtime);
|
|
320
|
+
console.log(JSON.stringify(result));
|
|
321
|
+
});
|
|
322
|
+
agent.command("run").argument("<type>", "navigate|interact|restart|terminate").argument("<payloadJson>", "JSON payload").action(async (type, payloadJson) => {
|
|
323
|
+
const result = await agentRun(runtime, {
|
|
324
|
+
type,
|
|
325
|
+
payload: JSON.parse(payloadJson)
|
|
326
|
+
});
|
|
327
|
+
console.log(JSON.stringify(result));
|
|
328
|
+
});
|
|
329
|
+
agent.command("content").option("--mode <mode>", "title|text|html", "text").option("--selector <selector>", "optional CSS selector").action(async (options) => {
|
|
330
|
+
const result = await agentContent(runtime, options);
|
|
331
|
+
console.log(JSON.stringify(result));
|
|
332
|
+
});
|
|
333
|
+
agent.command("elements").description("List interactive elements on the current page").option("--roles <roles>", "comma-separated roles filter", (v) => v.split(",")).option("--visible-only", "only visible elements", true).option("--no-visible-only", "include hidden elements").option("--limit <n>", "max elements", "50").option("--selector <selector>", "scope to CSS selector subtree").action(async (options) => {
|
|
334
|
+
const result = await agentElements(runtime, {
|
|
335
|
+
roles: options.roles,
|
|
336
|
+
visibleOnly: options.visibleOnly,
|
|
337
|
+
limit: Number.parseInt(options.limit, 10),
|
|
338
|
+
selector: options.selector
|
|
339
|
+
});
|
|
340
|
+
console.log(JSON.stringify(result));
|
|
341
|
+
});
|
|
342
|
+
agent.command("memory-search").argument("<taskIntent>").option("--domain <domain>", "website domain filter").option("--limit <limit>", "max results", "5").action(async (taskIntent, options) => {
|
|
343
|
+
const result = await agentMemorySearch(runtime, {
|
|
344
|
+
taskIntent,
|
|
345
|
+
siteDomain: options.domain,
|
|
346
|
+
limit: Number.parseInt(options.limit, 10)
|
|
347
|
+
});
|
|
348
|
+
console.log(JSON.stringify(result));
|
|
349
|
+
});
|
|
350
|
+
agent.command("cleanup").option("--max-age-days <days>", "remove sessions older than N days", "7").option("--dry-run", "show what would be removed").action(async (options) => {
|
|
351
|
+
const result = await agentCleanup(runtime, {
|
|
352
|
+
maxAgeDays: Number.parseFloat(options.maxAgeDays),
|
|
353
|
+
dryRun: Boolean(options.dryRun)
|
|
354
|
+
});
|
|
355
|
+
console.log(JSON.stringify(result));
|
|
356
|
+
});
|
|
357
|
+
program.command("mcp").description("Start the MCP server (stdio transport)").action(async () => {
|
|
358
|
+
const { main: startMcpServer } = await import("../mcp/index.mjs");
|
|
359
|
+
await startMcpServer();
|
|
360
|
+
});
|
|
361
|
+
program.command("setup").description("Configure agentic-browser as MCP server for your AI tool").action(async () => {
|
|
362
|
+
const { runSetup } = await import("../setup-CULSgM_M.mjs");
|
|
363
|
+
await runSetup();
|
|
364
|
+
});
|
|
365
|
+
await program.parseAsync(process.argv);
|
|
366
|
+
}
|
|
367
|
+
main().catch((error) => {
|
|
368
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
369
|
+
console.error(JSON.stringify({ error: message }));
|
|
370
|
+
process.exit(1);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { n as createAgenticBrowserCore } from "../runtime-C-oYEtN0.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
//#region src/mcp/index.ts
|
|
7
|
+
let core;
|
|
8
|
+
let activeSessionId;
|
|
9
|
+
function getCore() {
|
|
10
|
+
if (!core) core = createAgenticBrowserCore();
|
|
11
|
+
return core;
|
|
12
|
+
}
|
|
13
|
+
function genId(prefix) {
|
|
14
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
15
|
+
}
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: "agentic-browser",
|
|
18
|
+
version: "0.1.0"
|
|
19
|
+
});
|
|
20
|
+
server.tool("browser_start_session", "Start a Chrome browser session for web automation. Call this first before using any other browser tool. Returns a sessionId you'll need for all subsequent calls.", {}, async () => {
|
|
21
|
+
const session = await getCore().startSession();
|
|
22
|
+
activeSessionId = session.sessionId;
|
|
23
|
+
return { content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify(session)
|
|
26
|
+
}] };
|
|
27
|
+
});
|
|
28
|
+
server.tool("browser_navigate", "Navigate the browser to a URL. The browser must have an active session.", {
|
|
29
|
+
url: z.string().describe("The URL to navigate to"),
|
|
30
|
+
sessionId: z.string().optional().describe("Session ID (uses active session if omitted)")
|
|
31
|
+
}, async ({ url, sessionId }) => {
|
|
32
|
+
const sid = sessionId ?? activeSessionId;
|
|
33
|
+
if (!sid) throw new Error("No active session. Call browser_start_session first.");
|
|
34
|
+
const result = await getCore().runCommand({
|
|
35
|
+
sessionId: sid,
|
|
36
|
+
commandId: genId("nav"),
|
|
37
|
+
type: "navigate",
|
|
38
|
+
payload: { url }
|
|
39
|
+
});
|
|
40
|
+
return { content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: JSON.stringify(result)
|
|
43
|
+
}] };
|
|
44
|
+
});
|
|
45
|
+
server.tool("browser_interact", "Interact with a page element. Actions: \"click\" (click element), \"type\" (type text into input), \"press\" (press a keyboard key like Enter), \"waitFor\" (wait for element to appear).", {
|
|
46
|
+
action: z.enum([
|
|
47
|
+
"click",
|
|
48
|
+
"type",
|
|
49
|
+
"press",
|
|
50
|
+
"waitFor"
|
|
51
|
+
]).describe("The interaction type"),
|
|
52
|
+
selector: z.string().optional().describe("CSS selector for the target element"),
|
|
53
|
+
text: z.string().optional().describe("Text to type (required for \"type\" action)"),
|
|
54
|
+
key: z.string().optional().describe("Key to press (required for \"press\" action, e.g. \"Enter\", \"Tab\")"),
|
|
55
|
+
timeoutMs: z.number().optional().describe("Timeout in milliseconds (for \"waitFor\" action, default 4000)"),
|
|
56
|
+
sessionId: z.string().optional().describe("Session ID (uses active session if omitted)")
|
|
57
|
+
}, async ({ action, selector, text, key, timeoutMs, sessionId }) => {
|
|
58
|
+
const sid = sessionId ?? activeSessionId;
|
|
59
|
+
if (!sid) throw new Error("No active session. Call browser_start_session first.");
|
|
60
|
+
const payload = { action };
|
|
61
|
+
if (selector) payload.selector = selector;
|
|
62
|
+
if (text) payload.text = text;
|
|
63
|
+
if (key) payload.key = key;
|
|
64
|
+
if (timeoutMs) payload.timeoutMs = timeoutMs;
|
|
65
|
+
const result = await getCore().runCommand({
|
|
66
|
+
sessionId: sid,
|
|
67
|
+
commandId: genId("int"),
|
|
68
|
+
type: "interact",
|
|
69
|
+
payload
|
|
70
|
+
});
|
|
71
|
+
return { content: [{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: JSON.stringify(result)
|
|
74
|
+
}] };
|
|
75
|
+
});
|
|
76
|
+
server.tool("browser_get_content", "Get the current page content. Modes: \"title\" (page title only), \"text\" (readable text content), \"html\" (raw HTML). Use selector to scope to a specific element.", {
|
|
77
|
+
mode: z.enum([
|
|
78
|
+
"title",
|
|
79
|
+
"text",
|
|
80
|
+
"html"
|
|
81
|
+
]).default("text").describe("Content extraction mode"),
|
|
82
|
+
selector: z.string().optional().describe("CSS selector to scope content (e.g. \"main\", \"#content\")"),
|
|
83
|
+
sessionId: z.string().optional().describe("Session ID (uses active session if omitted)")
|
|
84
|
+
}, async ({ mode, selector, sessionId }) => {
|
|
85
|
+
const sid = sessionId ?? activeSessionId;
|
|
86
|
+
if (!sid) throw new Error("No active session. Call browser_start_session first.");
|
|
87
|
+
const result = await getCore().getPageContent({
|
|
88
|
+
sessionId: sid,
|
|
89
|
+
mode,
|
|
90
|
+
selector
|
|
91
|
+
});
|
|
92
|
+
return { content: [{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: JSON.stringify(result)
|
|
95
|
+
}] };
|
|
96
|
+
});
|
|
97
|
+
server.tool("browser_get_elements", "Discover all interactive elements on the current page (buttons, links, inputs, etc.). Returns CSS selectors you can use with browser_interact. Call this to understand what's on the page before interacting.", {
|
|
98
|
+
roles: z.array(z.enum([
|
|
99
|
+
"link",
|
|
100
|
+
"button",
|
|
101
|
+
"input",
|
|
102
|
+
"select",
|
|
103
|
+
"textarea",
|
|
104
|
+
"checkbox",
|
|
105
|
+
"radio",
|
|
106
|
+
"contenteditable",
|
|
107
|
+
"custom"
|
|
108
|
+
])).optional().describe("Filter by element roles (omit for all)"),
|
|
109
|
+
visibleOnly: z.boolean().default(true).describe("Only return visible elements"),
|
|
110
|
+
limit: z.number().default(50).describe("Maximum number of elements to return"),
|
|
111
|
+
selector: z.string().optional().describe("CSS selector to scope element discovery to a subtree"),
|
|
112
|
+
sessionId: z.string().optional().describe("Session ID (uses active session if omitted)")
|
|
113
|
+
}, async ({ roles, visibleOnly, limit, selector, sessionId }) => {
|
|
114
|
+
const sid = sessionId ?? activeSessionId;
|
|
115
|
+
if (!sid) throw new Error("No active session. Call browser_start_session first.");
|
|
116
|
+
const result = await getCore().getInteractiveElements({
|
|
117
|
+
sessionId: sid,
|
|
118
|
+
roles,
|
|
119
|
+
visibleOnly,
|
|
120
|
+
limit,
|
|
121
|
+
selector
|
|
122
|
+
});
|
|
123
|
+
return { content: [{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify(result)
|
|
126
|
+
}] };
|
|
127
|
+
});
|
|
128
|
+
server.tool("browser_search_memory", "Search task memory for previously learned selectors and interaction patterns. Use this before interacting with a known site to reuse proven selectors instead of rediscovering them.", {
|
|
129
|
+
taskIntent: z.string().describe("What you want to do, e.g. \"login:github.com\" or \"search:amazon.de\""),
|
|
130
|
+
siteDomain: z.string().optional().describe("Domain to scope the search"),
|
|
131
|
+
limit: z.number().default(5).describe("Maximum number of results")
|
|
132
|
+
}, async ({ taskIntent, siteDomain, limit }) => {
|
|
133
|
+
const result = getCore().searchMemory({
|
|
134
|
+
taskIntent,
|
|
135
|
+
siteDomain,
|
|
136
|
+
limit
|
|
137
|
+
});
|
|
138
|
+
return { content: [{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: JSON.stringify(result)
|
|
141
|
+
}] };
|
|
142
|
+
});
|
|
143
|
+
server.tool("browser_stop_session", "Stop the browser session and terminate Chrome. Call this when you're done with browser automation.", { sessionId: z.string().optional().describe("Session ID (uses active session if omitted)") }, async ({ sessionId }) => {
|
|
144
|
+
const sid = sessionId ?? activeSessionId;
|
|
145
|
+
if (!sid) throw new Error("No active session.");
|
|
146
|
+
await getCore().stopSession(sid);
|
|
147
|
+
if (activeSessionId === sid) activeSessionId = void 0;
|
|
148
|
+
return { content: [{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: JSON.stringify({
|
|
151
|
+
ok: true,
|
|
152
|
+
stopped: sid
|
|
153
|
+
})
|
|
154
|
+
}] };
|
|
155
|
+
});
|
|
156
|
+
async function main() {
|
|
157
|
+
const transport = new StdioServerTransport();
|
|
158
|
+
transport.onclose = async () => {
|
|
159
|
+
if (activeSessionId) {
|
|
160
|
+
try {
|
|
161
|
+
await getCore().stopSession(activeSessionId);
|
|
162
|
+
} catch {}
|
|
163
|
+
activeSessionId = void 0;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
await server.connect(transport);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
export { main };
|