@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/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("--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) => {
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) => {