@bsbofmusic/agent-browser-mcp-opencode 0.1.1 → 1.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/CHANGELOG.md +29 -0
- package/README.md +237 -13
- package/THIRD_PARTY_NOTICES.md +291 -0
- package/index.js +186 -0
- package/package.json +33 -18
- package/src/detect.js +286 -0
- package/src/doctor.js +387 -0
- package/src/ensure.js +425 -0
- package/src/tools/actions/click.js +77 -0
- package/src/tools/actions/close.js +45 -0
- package/src/tools/actions/fill.js +77 -0
- package/src/tools/actions/find.js +107 -0
- package/src/tools/actions/getText.js +70 -0
- package/src/tools/actions/open.js +96 -0
- package/src/tools/actions/screenshot.js +81 -0
- package/src/tools/actions/snapshot.js +94 -0
- package/src/tools/actions/tab.js +91 -0
- package/src/tools/actions/wait.js +85 -0
- package/src/tools/doctor.js +42 -0
- package/src/tools/ensure.js +46 -0
- package/src/tools/exec.js +120 -0
- package/src/tools/help.js +76 -0
- package/src/tools/index.js +50 -0
- package/src/tools/version.js +83 -0
- package/dist/bootstrap.js +0 -72
- package/dist/index.js +0 -121
- package/src/bootstrap.ts +0 -75
- package/src/index.ts +0 -162
- package/tsconfig.json +0 -14
package/src/index.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
|
|
6
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
-
|
|
9
|
-
type RunResult = {
|
|
10
|
-
ok: boolean;
|
|
11
|
-
exitCode: number | null;
|
|
12
|
-
stdout: string;
|
|
13
|
-
stderr: string;
|
|
14
|
-
parsedJson?: unknown;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const env = process.env;
|
|
18
|
-
const AGENT_BROWSER_BIN = env.AGENT_BROWSER_BIN || "agent-browser";
|
|
19
|
-
const DEFAULT_SESSION = env.AGENT_BROWSER_SESSION || "opencode";
|
|
20
|
-
const STATE_PATH = env.AGENT_BROWSER_STATE || "";
|
|
21
|
-
const ALLOW_DOMAINS = (env.AGENT_BROWSER_ALLOW_DOMAINS || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
22
|
-
|
|
23
|
-
const DEFAULT_TIMEOUT_MS = Number(env.AGENT_BROWSER_TIMEOUT_MS || "60000");
|
|
24
|
-
const MAX_OUTPUT_CHARS = Number(env.AGENT_BROWSER_MAX_OUTPUT_CHARS || "200000");
|
|
25
|
-
|
|
26
|
-
// helper: ensure literal type "text"
|
|
27
|
-
const txt = (text: string) => ({ type: "text" as const, text });
|
|
28
|
-
|
|
29
|
-
let queue = Promise.resolve();
|
|
30
|
-
function enqueue<T>(fn: () => Promise<T>): Promise<T> {
|
|
31
|
-
const next = queue.then(fn, fn);
|
|
32
|
-
queue = next.then(() => undefined, () => undefined);
|
|
33
|
-
return next;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function limitOutput(s: string): string {
|
|
37
|
-
if (s.length <= MAX_OUTPUT_CHARS) return s;
|
|
38
|
-
return s.slice(0, MAX_OUTPUT_CHARS) + "\n...[truncated]";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function domainAllowed(urlStr: string): boolean {
|
|
42
|
-
if (!ALLOW_DOMAINS.length) return true;
|
|
43
|
-
try {
|
|
44
|
-
const u = new URL(urlStr);
|
|
45
|
-
const host = u.hostname.toLowerCase();
|
|
46
|
-
return ALLOW_DOMAINS.some(d => host === d.toLowerCase() || host.endsWith("." + d.toLowerCase()));
|
|
47
|
-
} catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function runAgentBrowser(args: string[], timeoutMs = DEFAULT_TIMEOUT_MS): Promise<RunResult> {
|
|
53
|
-
return await enqueue(() => new Promise<RunResult>((resolveRun) => {
|
|
54
|
-
const finalArgs: string[] = [];
|
|
55
|
-
finalArgs.push(...args, "--json");
|
|
56
|
-
finalArgs.push("--session-name", DEFAULT_SESSION);
|
|
57
|
-
if (STATE_PATH) finalArgs.push("--state", STATE_PATH);
|
|
58
|
-
|
|
59
|
-
const child = spawn(AGENT_BROWSER_BIN, finalArgs, { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
60
|
-
let stdout = "";
|
|
61
|
-
let stderr = "";
|
|
62
|
-
|
|
63
|
-
const timer = setTimeout(() => child.kill(), timeoutMs);
|
|
64
|
-
|
|
65
|
-
child.stdout.on("data", (d) => { stdout += d.toString("utf8"); });
|
|
66
|
-
child.stderr.on("data", (d) => { stderr += d.toString("utf8"); });
|
|
67
|
-
|
|
68
|
-
child.on("close", (code) => {
|
|
69
|
-
clearTimeout(timer);
|
|
70
|
-
const out = limitOutput(stdout.trim());
|
|
71
|
-
const err = limitOutput(stderr.trim());
|
|
72
|
-
|
|
73
|
-
let parsed: unknown | undefined;
|
|
74
|
-
try { parsed = out ? JSON.parse(out) : undefined; } catch {
|
|
75
|
-
const lines = out.split("\n").map(l => l.trim()).filter(Boolean);
|
|
76
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
77
|
-
try { parsed = JSON.parse(lines[i]); break; } catch {}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
resolveRun({ ok: code === 0, exitCode: code, stdout: out, stderr: err, parsedJson: parsed });
|
|
81
|
-
});
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const server = new McpServer({ name: "agent-browser-mcp", version: "0.1.0" });
|
|
86
|
-
|
|
87
|
-
server.tool(
|
|
88
|
-
"browser_open",
|
|
89
|
-
"Open a URL in agent-browser (reuses session).",
|
|
90
|
-
{ url: z.string().url(), headed: z.boolean().optional().default(false), timeoutMs: z.number().int().positive().optional() },
|
|
91
|
-
async ({ url, headed, timeoutMs }) => {
|
|
92
|
-
if (!domainAllowed(url)) return { content: [txt(`Blocked by allowlist: ${url}`)] };
|
|
93
|
-
const args = ["open", url];
|
|
94
|
-
if (headed) args.push("--headed");
|
|
95
|
-
const r = await runAgentBrowser(args, timeoutMs);
|
|
96
|
-
const content = [txt(r.ok ? "OK" : "FAILED"), txt(r.stdout || "")];
|
|
97
|
-
if (r.stderr) content.push(txt(`stderr:\n${r.stderr}`));
|
|
98
|
-
return { content };
|
|
99
|
-
}
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
server.tool(
|
|
103
|
-
"browser_click",
|
|
104
|
-
"Click an element by selector.",
|
|
105
|
-
{ selector: z.string().min(1), timeoutMs: z.number().int().positive().optional() },
|
|
106
|
-
async ({ selector, timeoutMs }) => {
|
|
107
|
-
const r = await runAgentBrowser(["click", selector], timeoutMs);
|
|
108
|
-
return { content: [txt(r.ok ? (r.stdout || "OK") : (r.stderr || r.stdout || "FAILED"))] };
|
|
109
|
-
}
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
server.tool(
|
|
113
|
-
"browser_type",
|
|
114
|
-
"Type into an input/textarea by selector.",
|
|
115
|
-
{ selector: z.string().min(1), text: z.string(), submit: z.boolean().optional().default(false), timeoutMs: z.number().int().positive().optional() },
|
|
116
|
-
async ({ selector, text, submit, timeoutMs }) => {
|
|
117
|
-
const args = ["type", selector, text];
|
|
118
|
-
if (submit) args.push("--submit");
|
|
119
|
-
const r = await runAgentBrowser(args, timeoutMs);
|
|
120
|
-
return { content: [txt(r.ok ? (r.stdout || "OK") : (r.stderr || r.stdout || "FAILED"))] };
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
server.tool(
|
|
125
|
-
"browser_extract",
|
|
126
|
-
"Extract text/html from a selector.",
|
|
127
|
-
{ selector: z.string().min(1), mode: z.enum(["text", "html"]).optional().default("text"), timeoutMs: z.number().int().positive().optional() },
|
|
128
|
-
async ({ selector, mode, timeoutMs }) => {
|
|
129
|
-
const r = await runAgentBrowser(["extract", mode, selector], timeoutMs);
|
|
130
|
-
const payload = r.parsedJson ?? r.stdout;
|
|
131
|
-
return { content: [txt(typeof payload === "string" ? payload : JSON.stringify(payload, null, 2))] };
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
server.tool(
|
|
136
|
-
"browser_screenshot",
|
|
137
|
-
"Take a screenshot and save to a file path.",
|
|
138
|
-
{ path: z.string().min(1).optional().default("agent-browser.png"), fullPage: z.boolean().optional().default(true), timeoutMs: z.number().int().positive().optional() },
|
|
139
|
-
async ({ path, fullPage, timeoutMs }) => {
|
|
140
|
-
const outPath = resolve(process.cwd(), path);
|
|
141
|
-
const args = ["screenshot", outPath];
|
|
142
|
-
if (fullPage) args.push("--full-page");
|
|
143
|
-
const r = await runAgentBrowser(args, timeoutMs);
|
|
144
|
-
const content = [txt(r.ok ? `OK: ${outPath}` : "FAILED")];
|
|
145
|
-
if (r.stdout) content.push(txt(r.stdout));
|
|
146
|
-
if (r.stderr) content.push(txt(`stderr:\n${r.stderr}`));
|
|
147
|
-
return { content };
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
server.tool(
|
|
152
|
-
"browser_set_viewport",
|
|
153
|
-
"Set viewport size.",
|
|
154
|
-
{ width: z.number().int().min(200).max(4000), height: z.number().int().min(200).max(4000), timeoutMs: z.number().int().positive().optional() },
|
|
155
|
-
async ({ width, height, timeoutMs }) => {
|
|
156
|
-
const r = await runAgentBrowser(["set", "viewport", String(width), String(height)], timeoutMs);
|
|
157
|
-
return { content: [txt(r.ok ? (r.stdout || "OK") : (r.stderr || r.stdout || "FAILED"))] };
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const transport = new StdioServerTransport();
|
|
162
|
-
await server.connect(transport);
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"lib": ["ES2022"],
|
|
5
|
-
"module": "ES2022",
|
|
6
|
-
"moduleResolution": "Bundler",
|
|
7
|
-
"types": ["node"],
|
|
8
|
-
"outDir": "dist",
|
|
9
|
-
"rootDir": "src",
|
|
10
|
-
"strict": true,
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*.ts"]
|
|
14
|
-
}
|