@btraut/browser-bridge 0.4.0 → 0.4.3
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 +18 -2
- package/README.md +57 -32
- package/dist/api.js +189 -26
- package/dist/api.js.map +3 -3
- package/dist/index.js +222 -1
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +413 -0
- package/extension/dist/background.js.map +3 -3
- package/extension/dist/content.js +71 -0
- package/extension/dist/content.js.map +2 -2
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
package/dist/index.js
CHANGED
|
@@ -553,10 +553,30 @@ var ArtifactsScreenshotInputSchema = import_zod2.z.object({
|
|
|
553
553
|
session_id: import_zod2.z.string().min(1),
|
|
554
554
|
target: import_zod2.z.enum(["viewport", "full"]).default("viewport"),
|
|
555
555
|
fullPage: import_zod2.z.boolean().default(false),
|
|
556
|
+
selector: import_zod2.z.string().min(1).optional(),
|
|
556
557
|
format: import_zod2.z.enum(["png", "jpeg", "webp"]).default("png"),
|
|
557
558
|
quality: import_zod2.z.number().min(0).max(100).optional()
|
|
558
559
|
});
|
|
559
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();
|
|
560
580
|
var DiagnosticsDoctorInputSchema = import_zod2.z.object({
|
|
561
581
|
session_id: import_zod2.z.string().min(1).optional()
|
|
562
582
|
});
|
|
@@ -844,12 +864,16 @@ var parseNumber = (value) => {
|
|
|
844
864
|
};
|
|
845
865
|
var registerArtifactsCommands = (program2) => {
|
|
846
866
|
const artifacts = program2.command("artifacts").description("Artifact commands");
|
|
847
|
-
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) => {
|
|
848
871
|
await runCommand(command, (client) => {
|
|
849
872
|
const payload = parseInput(ArtifactsScreenshotInputSchema, {
|
|
850
873
|
session_id: options.sessionId,
|
|
851
874
|
target: options.target,
|
|
852
875
|
fullPage: Boolean(options.fullPage),
|
|
876
|
+
selector: options.selector,
|
|
853
877
|
format: options.format,
|
|
854
878
|
quality: parseNumber(options.quality)
|
|
855
879
|
});
|
|
@@ -900,6 +924,12 @@ var registerDiagnosticsCommands = (program2) => {
|
|
|
900
924
|
return client.post("/diagnostics/doctor", payload);
|
|
901
925
|
});
|
|
902
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
|
+
});
|
|
903
933
|
};
|
|
904
934
|
|
|
905
935
|
// packages/cli/src/locator.ts
|
|
@@ -1780,6 +1810,16 @@ var TOOL_DEFINITIONS = [
|
|
|
1780
1810
|
corePath: "/artifacts/screenshot"
|
|
1781
1811
|
}
|
|
1782
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
|
+
},
|
|
1783
1823
|
{
|
|
1784
1824
|
name: "diagnostics.doctor",
|
|
1785
1825
|
config: {
|
|
@@ -1822,10 +1862,16 @@ var registerBrowserBridgeTools = (server, client) => {
|
|
|
1822
1862
|
};
|
|
1823
1863
|
|
|
1824
1864
|
// packages/mcp-adapter/src/server.ts
|
|
1865
|
+
var import_node_http = require("node:http");
|
|
1866
|
+
var import_node_crypto = require("node:crypto");
|
|
1825
1867
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
1826
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");
|
|
1827
1871
|
var DEFAULT_SERVER_NAME = "browser-bridge";
|
|
1828
1872
|
var DEFAULT_SERVER_VERSION = "0.0.0";
|
|
1873
|
+
var DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
1874
|
+
var DEFAULT_HTTP_PATH = "/mcp";
|
|
1829
1875
|
var createMcpServer = (options = {}) => {
|
|
1830
1876
|
const server = new import_mcp.McpServer({
|
|
1831
1877
|
name: options.name ?? DEFAULT_SERVER_NAME,
|
|
@@ -1841,6 +1887,151 @@ var startMcpServer = async (options = {}) => {
|
|
|
1841
1887
|
await handle.server.connect(transport);
|
|
1842
1888
|
return { ...handle, transport };
|
|
1843
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
|
+
};
|
|
1844
2035
|
|
|
1845
2036
|
// packages/cli/src/tui.ts
|
|
1846
2037
|
var requireTty = () => {
|
|
@@ -2071,6 +2262,31 @@ var registerMcpCommand = (program2) => {
|
|
|
2071
2262
|
});
|
|
2072
2263
|
mcp.option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(startServer);
|
|
2073
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
|
+
});
|
|
2074
2290
|
};
|
|
2075
2291
|
|
|
2076
2292
|
// packages/core/src/server.ts
|
|
@@ -2217,6 +2433,11 @@ var PAGE_STATE_SCRIPT = [
|
|
|
2217
2433
|
"})()"
|
|
2218
2434
|
].join("\n");
|
|
2219
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
|
+
|
|
2220
2441
|
// packages/cli/src/open-path.ts
|
|
2221
2442
|
var import_node_child_process3 = require("node:child_process");
|
|
2222
2443
|
var openPath = async (target) => {
|