@btraut/browser-bridge 0.3.0 → 0.4.2
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 +42 -0
- package/README.md +72 -31
- package/dist/api.js +1671 -1384
- package/dist/api.js.map +4 -4
- package/dist/index.js +228 -2
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +509 -2
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +71 -0
- package/extension/dist/content.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +12 -0
- package/skills/browser-bridge/skill.json +1 -1
package/dist/index.js
CHANGED
|
@@ -142,6 +142,11 @@ var DiagnosticReportSchema = import_zod2.z.object({
|
|
|
142
142
|
ok: import_zod2.z.boolean(),
|
|
143
143
|
session_id: import_zod2.z.string().optional(),
|
|
144
144
|
checks: import_zod2.z.array(DiagnosticCheckSchema).optional(),
|
|
145
|
+
sessions: import_zod2.z.object({
|
|
146
|
+
count: import_zod2.z.number().finite().optional(),
|
|
147
|
+
max_age_ms: import_zod2.z.number().finite().optional(),
|
|
148
|
+
max_idle_ms: import_zod2.z.number().finite().optional()
|
|
149
|
+
}).optional(),
|
|
145
150
|
extension: import_zod2.z.object({
|
|
146
151
|
connected: import_zod2.z.boolean().optional(),
|
|
147
152
|
version: import_zod2.z.string().optional(),
|
|
@@ -548,10 +553,30 @@ var ArtifactsScreenshotInputSchema = import_zod2.z.object({
|
|
|
548
553
|
session_id: import_zod2.z.string().min(1),
|
|
549
554
|
target: import_zod2.z.enum(["viewport", "full"]).default("viewport"),
|
|
550
555
|
fullPage: import_zod2.z.boolean().default(false),
|
|
556
|
+
selector: import_zod2.z.string().min(1).optional(),
|
|
551
557
|
format: import_zod2.z.enum(["png", "jpeg", "webp"]).default("png"),
|
|
552
558
|
quality: import_zod2.z.number().min(0).max(100).optional()
|
|
553
559
|
});
|
|
554
560
|
var ArtifactsScreenshotOutputSchema = ArtifactInfoSchema;
|
|
561
|
+
var HealthCheckInputSchema = import_zod2.z.object({});
|
|
562
|
+
var HealthCheckOutputSchema = import_zod2.z.object({
|
|
563
|
+
started_at: import_zod2.z.string().min(1),
|
|
564
|
+
uptime_ms: import_zod2.z.number().finite().nonnegative(),
|
|
565
|
+
memory: import_zod2.z.object({
|
|
566
|
+
rss: import_zod2.z.number().finite().nonnegative(),
|
|
567
|
+
heapTotal: import_zod2.z.number().finite().nonnegative(),
|
|
568
|
+
heapUsed: import_zod2.z.number().finite().nonnegative(),
|
|
569
|
+
external: import_zod2.z.number().finite().nonnegative(),
|
|
570
|
+
arrayBuffers: import_zod2.z.number().finite().nonnegative().optional()
|
|
571
|
+
}).passthrough(),
|
|
572
|
+
sessions: import_zod2.z.object({
|
|
573
|
+
active: import_zod2.z.number().finite().nonnegative()
|
|
574
|
+
}).passthrough(),
|
|
575
|
+
extension: import_zod2.z.object({
|
|
576
|
+
connected: import_zod2.z.boolean(),
|
|
577
|
+
last_seen_at: import_zod2.z.string().min(1).optional()
|
|
578
|
+
}).passthrough()
|
|
579
|
+
}).passthrough();
|
|
555
580
|
var DiagnosticsDoctorInputSchema = import_zod2.z.object({
|
|
556
581
|
session_id: import_zod2.z.string().min(1).optional()
|
|
557
582
|
});
|
|
@@ -839,12 +864,16 @@ var parseNumber = (value) => {
|
|
|
839
864
|
};
|
|
840
865
|
var registerArtifactsCommands = (program2) => {
|
|
841
866
|
const artifacts = program2.command("artifacts").description("Artifact commands");
|
|
842
|
-
artifacts.command("screenshot").description("Request a screenshot artifact").requiredOption("--session-id <id>", "Session identifier").option("--target <target>", "Screenshot target (viewport, full)").option("--full-page", "Capture full page (alias for --target full)").option(
|
|
867
|
+
artifacts.command("screenshot").description("Request a screenshot artifact").requiredOption("--session-id <id>", "Session identifier").option("--target <target>", "Screenshot target (viewport, full)").option("--full-page", "Capture full page (alias for --target full)").option(
|
|
868
|
+
"--selector <selector>",
|
|
869
|
+
"Capture a specific element by CSS selector"
|
|
870
|
+
).option("--format <format>", "Screenshot format (png, jpeg, webp)").option("--quality <quality>", "Screenshot quality (0-100) for jpeg/webp").action(async (options, command) => {
|
|
843
871
|
await runCommand(command, (client) => {
|
|
844
872
|
const payload = parseInput(ArtifactsScreenshotInputSchema, {
|
|
845
873
|
session_id: options.sessionId,
|
|
846
874
|
target: options.target,
|
|
847
875
|
fullPage: Boolean(options.fullPage),
|
|
876
|
+
selector: options.selector,
|
|
848
877
|
format: options.format,
|
|
849
878
|
quality: parseNumber(options.quality)
|
|
850
879
|
});
|
|
@@ -895,6 +924,12 @@ var registerDiagnosticsCommands = (program2) => {
|
|
|
895
924
|
return client.post("/diagnostics/doctor", payload);
|
|
896
925
|
});
|
|
897
926
|
});
|
|
927
|
+
diagnostics.command("health-check").description("Run a lightweight health check").action(async (_options, command) => {
|
|
928
|
+
await runCommand(command, (client) => {
|
|
929
|
+
const payload = parseInput(HealthCheckInputSchema, {});
|
|
930
|
+
return client.post("/health_check", payload);
|
|
931
|
+
});
|
|
932
|
+
});
|
|
898
933
|
};
|
|
899
934
|
|
|
900
935
|
// packages/cli/src/locator.ts
|
|
@@ -1775,6 +1810,16 @@ var TOOL_DEFINITIONS = [
|
|
|
1775
1810
|
corePath: "/artifacts/screenshot"
|
|
1776
1811
|
}
|
|
1777
1812
|
},
|
|
1813
|
+
{
|
|
1814
|
+
name: "health_check",
|
|
1815
|
+
config: {
|
|
1816
|
+
title: "Health Check",
|
|
1817
|
+
description: "Check server health including uptime, memory usage, active session count, and extension connection status.",
|
|
1818
|
+
inputSchema: HealthCheckInputSchema,
|
|
1819
|
+
outputSchema: envelope(HealthCheckOutputSchema),
|
|
1820
|
+
corePath: "/health_check"
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1778
1823
|
{
|
|
1779
1824
|
name: "diagnostics.doctor",
|
|
1780
1825
|
config: {
|
|
@@ -1817,10 +1862,16 @@ var registerBrowserBridgeTools = (server, client) => {
|
|
|
1817
1862
|
};
|
|
1818
1863
|
|
|
1819
1864
|
// packages/mcp-adapter/src/server.ts
|
|
1865
|
+
var import_node_http = require("node:http");
|
|
1866
|
+
var import_node_crypto = require("node:crypto");
|
|
1820
1867
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
1821
1868
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
1869
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1870
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
1822
1871
|
var DEFAULT_SERVER_NAME = "browser-bridge";
|
|
1823
1872
|
var DEFAULT_SERVER_VERSION = "0.0.0";
|
|
1873
|
+
var DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
1874
|
+
var DEFAULT_HTTP_PATH = "/mcp";
|
|
1824
1875
|
var createMcpServer = (options = {}) => {
|
|
1825
1876
|
const server = new import_mcp.McpServer({
|
|
1826
1877
|
name: options.name ?? DEFAULT_SERVER_NAME,
|
|
@@ -1836,6 +1887,151 @@ var startMcpServer = async (options = {}) => {
|
|
|
1836
1887
|
await handle.server.connect(transport);
|
|
1837
1888
|
return { ...handle, transport };
|
|
1838
1889
|
};
|
|
1890
|
+
var readJsonBody = async (req, maxBytes = 5 * 1024 * 1024) => {
|
|
1891
|
+
const chunks = [];
|
|
1892
|
+
let total = 0;
|
|
1893
|
+
await new Promise((resolve2, reject) => {
|
|
1894
|
+
req.on("data", (chunk) => {
|
|
1895
|
+
total += chunk.length;
|
|
1896
|
+
if (total > maxBytes) {
|
|
1897
|
+
reject(new Error("Request body too large."));
|
|
1898
|
+
req.destroy();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
chunks.push(chunk);
|
|
1902
|
+
});
|
|
1903
|
+
req.on("end", () => resolve2());
|
|
1904
|
+
req.on("error", (err) => reject(err));
|
|
1905
|
+
});
|
|
1906
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1907
|
+
if (!raw) {
|
|
1908
|
+
return void 0;
|
|
1909
|
+
}
|
|
1910
|
+
return JSON.parse(raw);
|
|
1911
|
+
};
|
|
1912
|
+
var getHeaderValue = (value) => {
|
|
1913
|
+
if (typeof value === "string") {
|
|
1914
|
+
return value;
|
|
1915
|
+
}
|
|
1916
|
+
if (Array.isArray(value)) {
|
|
1917
|
+
return value[0];
|
|
1918
|
+
}
|
|
1919
|
+
return void 0;
|
|
1920
|
+
};
|
|
1921
|
+
var startMcpHttpServer = async (options = {}) => {
|
|
1922
|
+
const host = options.host ?? DEFAULT_HTTP_HOST;
|
|
1923
|
+
const port = typeof options.port === "number" ? options.port : 0;
|
|
1924
|
+
const path9 = options.path ?? DEFAULT_HTTP_PATH;
|
|
1925
|
+
const client = options.coreClient ?? createCoreClient2(options);
|
|
1926
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1927
|
+
const closeAllSessions = async () => {
|
|
1928
|
+
const entries = Array.from(sessions.values());
|
|
1929
|
+
sessions.clear();
|
|
1930
|
+
await Promise.all(
|
|
1931
|
+
entries.map(async (entry) => {
|
|
1932
|
+
try {
|
|
1933
|
+
await entry.transport.close();
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
try {
|
|
1937
|
+
await entry.server.close();
|
|
1938
|
+
} catch {
|
|
1939
|
+
}
|
|
1940
|
+
})
|
|
1941
|
+
);
|
|
1942
|
+
};
|
|
1943
|
+
const httpServer = (0, import_node_http.createServer)(async (req, res) => {
|
|
1944
|
+
try {
|
|
1945
|
+
const url = new URL(req.url ?? "", `http://${host}`);
|
|
1946
|
+
if (url.pathname !== path9) {
|
|
1947
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
1948
|
+
res.end(JSON.stringify({ error: "Not Found" }));
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
const sessionId = getHeaderValue(req.headers["mcp-session-id"]);
|
|
1952
|
+
const parsedBody = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
1953
|
+
if (sessionId) {
|
|
1954
|
+
const entry = sessions.get(sessionId);
|
|
1955
|
+
if (!entry) {
|
|
1956
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
1957
|
+
res.end(JSON.stringify({ error: "Unknown session." }));
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
await entry.transport.handleRequest(req, res, parsedBody);
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (req.method !== "POST" || !(0, import_types.isInitializeRequest)(parsedBody)) {
|
|
1964
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
1965
|
+
res.end(
|
|
1966
|
+
JSON.stringify({
|
|
1967
|
+
error: "Missing mcp-session-id header. First request must be initialize."
|
|
1968
|
+
})
|
|
1969
|
+
);
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
let sessionEntry;
|
|
1973
|
+
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
1974
|
+
sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
|
|
1975
|
+
onsessioninitialized: (sid) => {
|
|
1976
|
+
if (sessionEntry) {
|
|
1977
|
+
sessions.set(sid, sessionEntry);
|
|
1978
|
+
}
|
|
1979
|
+
},
|
|
1980
|
+
onsessionclosed: async (sid) => {
|
|
1981
|
+
const entry = sessions.get(sid);
|
|
1982
|
+
sessions.delete(sid);
|
|
1983
|
+
if (!entry) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
await Promise.allSettled([
|
|
1987
|
+
entry.transport.close(),
|
|
1988
|
+
entry.server.close()
|
|
1989
|
+
]);
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
const sessionServer = new import_mcp.McpServer({
|
|
1993
|
+
name: options.name ?? DEFAULT_SERVER_NAME,
|
|
1994
|
+
version: options.version ?? DEFAULT_SERVER_VERSION
|
|
1995
|
+
});
|
|
1996
|
+
registerBrowserBridgeTools(sessionServer, client);
|
|
1997
|
+
await sessionServer.connect(transport);
|
|
1998
|
+
sessionEntry = { transport, server: sessionServer };
|
|
1999
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
2002
|
+
res.end(
|
|
2003
|
+
JSON.stringify({
|
|
2004
|
+
error: error instanceof Error ? error.message : "Internal error."
|
|
2005
|
+
})
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
const resolvedPort = await new Promise((resolve2, reject) => {
|
|
2010
|
+
httpServer.listen(port, host, () => {
|
|
2011
|
+
const address = httpServer.address();
|
|
2012
|
+
resolve2(typeof address === "object" && address ? address.port : port);
|
|
2013
|
+
});
|
|
2014
|
+
httpServer.on("error", reject);
|
|
2015
|
+
});
|
|
2016
|
+
return {
|
|
2017
|
+
client,
|
|
2018
|
+
host,
|
|
2019
|
+
port: resolvedPort,
|
|
2020
|
+
path: path9,
|
|
2021
|
+
close: async () => {
|
|
2022
|
+
await new Promise((resolve2, reject) => {
|
|
2023
|
+
httpServer.close((err) => {
|
|
2024
|
+
if (err) {
|
|
2025
|
+
reject(err);
|
|
2026
|
+
} else {
|
|
2027
|
+
resolve2();
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
});
|
|
2031
|
+
await closeAllSessions();
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
};
|
|
1839
2035
|
|
|
1840
2036
|
// packages/cli/src/tui.ts
|
|
1841
2037
|
var requireTty = () => {
|
|
@@ -2066,6 +2262,31 @@ var registerMcpCommand = (program2) => {
|
|
|
2066
2262
|
});
|
|
2067
2263
|
mcp.option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(startServer);
|
|
2068
2264
|
mcp.command("serve").description("Run the MCP server over stdio").option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(startServer);
|
|
2265
|
+
mcp.command("serve-http").description("Run the MCP server over Streamable HTTP").option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").option("--host <host>", "HTTP host (default 127.0.0.1)").option("--port <port>", "HTTP port (default random available port)").option("--path <path>", "HTTP path (default /mcp)").action(async (options, command) => {
|
|
2266
|
+
const globals = getGlobalOptions(command);
|
|
2267
|
+
const coreClient = createCoreClient({
|
|
2268
|
+
host: globals.host,
|
|
2269
|
+
port: globals.port,
|
|
2270
|
+
ensureDaemon: globals.daemon !== false
|
|
2271
|
+
});
|
|
2272
|
+
const parsedPort = typeof options.port === "string" && options.port.length > 0 ? Number(options.port) : void 0;
|
|
2273
|
+
try {
|
|
2274
|
+
const handle = await startMcpHttpServer({
|
|
2275
|
+
name: options.name,
|
|
2276
|
+
version: options.version,
|
|
2277
|
+
host: options.host,
|
|
2278
|
+
port: typeof parsedPort === "number" && Number.isFinite(parsedPort) ? parsedPort : void 0,
|
|
2279
|
+
path: options.path,
|
|
2280
|
+
coreClient
|
|
2281
|
+
});
|
|
2282
|
+
console.error(
|
|
2283
|
+
`MCP HTTP server listening on http://${handle.host}:${handle.port}${handle.path}`
|
|
2284
|
+
);
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
console.error(error);
|
|
2287
|
+
process.exitCode = 1;
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2069
2290
|
};
|
|
2070
2291
|
|
|
2071
2292
|
// packages/core/src/server.ts
|
|
@@ -2074,7 +2295,7 @@ var import_express2 = __toESM(require("express"));
|
|
|
2074
2295
|
// packages/core/src/routes/session.ts
|
|
2075
2296
|
var import_express = require("express");
|
|
2076
2297
|
|
|
2077
|
-
// packages/core/src/inspect.ts
|
|
2298
|
+
// packages/core/src/inspect/service.ts
|
|
2078
2299
|
var import_readability = require("@mozilla/readability");
|
|
2079
2300
|
var import_jsdom = require("jsdom");
|
|
2080
2301
|
var import_turndown = __toESM(require("turndown"));
|
|
@@ -2212,6 +2433,11 @@ var PAGE_STATE_SCRIPT = [
|
|
|
2212
2433
|
"})()"
|
|
2213
2434
|
].join("\n");
|
|
2214
2435
|
|
|
2436
|
+
// packages/core/src/routes/diagnostics.ts
|
|
2437
|
+
var PROCESS_STARTED_AT = new Date(
|
|
2438
|
+
Date.now() - Math.floor(process.uptime() * 1e3)
|
|
2439
|
+
).toISOString();
|
|
2440
|
+
|
|
2215
2441
|
// packages/cli/src/open-path.ts
|
|
2216
2442
|
var import_node_child_process3 = require("node:child_process");
|
|
2217
2443
|
var openPath = async (target) => {
|