@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/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("--format <format>", "Screenshot format (png, jpeg, webp)").option("--quality <quality>", "Screenshot quality (0-100) for jpeg/webp").action(async (options, command) => {
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) => {