chrome-relay 0.1.1 → 0.1.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/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
+ import { writeFileSync } from "fs";
5
6
 
6
7
  // src/index.ts
7
8
  var CHROME_RELAY_VERSION = "0.1.0";
@@ -266,6 +267,34 @@ async function runStdioProxy() {
266
267
  await server.connect(new StdioServerTransport());
267
268
  }
268
269
 
270
+ // src/client/call.ts
271
+ import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
272
+ import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
273
+ async function callTool(name, args) {
274
+ const transport = new StreamableHTTPClientTransport2(
275
+ new URL(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/mcp`)
276
+ );
277
+ const client = new Client2(
278
+ { name: "chrome-relay-cli", version: "0.1.2" },
279
+ { capabilities: {} }
280
+ );
281
+ await client.connect(transport);
282
+ try {
283
+ const result = await client.callTool({ name, arguments: args });
284
+ const content = result.content ?? [];
285
+ const text = content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
286
+ if (!text) return result;
287
+ try {
288
+ return JSON.parse(text);
289
+ } catch {
290
+ return text;
291
+ }
292
+ } finally {
293
+ await client.close().catch(() => {
294
+ });
295
+ }
296
+ }
297
+
269
298
  // src/cli.ts
270
299
  var program = new Command();
271
300
  program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through MCP.").version(CHROME_RELAY_VERSION);
@@ -279,4 +308,108 @@ program.command("doctor").description("Validate the local Chrome Relay installat
279
308
  program.command("stdio").description("Expose Chrome Relay over stdio for MCP clients that do not support HTTP.").action(async () => {
280
309
  await runStdioProxy();
281
310
  });
282
- program.parse(process.argv);
311
+ async function run(name, args) {
312
+ try {
313
+ const result = await callTool(name, args);
314
+ if (typeof result === "string") {
315
+ process.stdout.write(result + "\n");
316
+ } else {
317
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
318
+ }
319
+ } catch (error) {
320
+ process.stderr.write(
321
+ (error instanceof Error ? error.message : String(error)) + "\n"
322
+ );
323
+ process.exit(1);
324
+ }
325
+ }
326
+ function tabOpt(cmd) {
327
+ return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v));
328
+ }
329
+ tabOpt(
330
+ program.command("tabs").description("List open Chrome windows and tabs.")
331
+ ).action(async () => {
332
+ await run("get_windows_and_tabs", {});
333
+ });
334
+ tabOpt(
335
+ program.command("navigate <url>").description("Navigate a tab to a URL.").option("--new", "open in a new tab").option("--inactive", "do not activate the tab")
336
+ ).action(async (url, opts) => {
337
+ const args = { url };
338
+ if (opts.tab !== void 0) args.tabId = opts.tab;
339
+ if (opts.new) args.newTab = true;
340
+ if (opts.inactive) args.active = false;
341
+ await run("chrome_navigate", args);
342
+ });
343
+ tabOpt(
344
+ program.command("screenshot").description("Capture a screenshot of the current page.").option("--full", "capture full page").option("-o, --out <path>", "save image to path (base64 PNG decoded)")
345
+ ).action(async (opts) => {
346
+ const args = {};
347
+ if (opts.tab !== void 0) args.tabId = opts.tab;
348
+ if (opts.full) args.fullPage = true;
349
+ try {
350
+ const result = await callTool("chrome_screenshot", args);
351
+ if (opts.out && result && typeof result === "object") {
352
+ const data = result.dataUrl ?? result.data;
353
+ if (typeof data === "string") {
354
+ const b64 = data.includes(",") ? data.split(",")[1] : data;
355
+ writeFileSync(opts.out, Buffer.from(b64, "base64"));
356
+ process.stdout.write(`Saved screenshot to ${opts.out}
357
+ `);
358
+ return;
359
+ }
360
+ }
361
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
362
+ } catch (error) {
363
+ process.stderr.write(
364
+ (error instanceof Error ? error.message : String(error)) + "\n"
365
+ );
366
+ process.exit(1);
367
+ }
368
+ });
369
+ tabOpt(
370
+ program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
371
+ ).action(async (opts) => {
372
+ const args = {};
373
+ if (opts.tab !== void 0) args.tabId = opts.tab;
374
+ if (opts.interactive) args.interactiveOnly = true;
375
+ await run("chrome_read_page", args);
376
+ });
377
+ tabOpt(
378
+ program.command("click <selector>").description("Click an element by CSS selector.")
379
+ ).action(async (selector, opts) => {
380
+ const args = { selector };
381
+ if (opts.tab !== void 0) args.tabId = opts.tab;
382
+ await run("chrome_click_element", args);
383
+ });
384
+ tabOpt(
385
+ program.command("fill <selector> <value>").description("Fill an input or textarea.")
386
+ ).action(async (selector, value, opts) => {
387
+ const args = { selector, value };
388
+ if (opts.tab !== void 0) args.tabId = opts.tab;
389
+ await run("chrome_fill_or_select", args);
390
+ });
391
+ tabOpt(
392
+ program.command("keys <keys>").description("Send keyboard input (e.g. Enter, Meta+L).")
393
+ ).action(async (keys, opts) => {
394
+ const args = { keys };
395
+ if (opts.tab !== void 0) args.tabId = opts.tab;
396
+ await run("chrome_keyboard", args);
397
+ });
398
+ tabOpt(
399
+ program.command("js <expression>").description("Evaluate JavaScript in the page context.")
400
+ ).action(async (expression, opts) => {
401
+ const args = { expression };
402
+ if (opts.tab !== void 0) args.tabId = opts.tab;
403
+ await run("chrome_javascript", args);
404
+ });
405
+ program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
406
+ await run("chrome_switch_tab", { tabId: Number(tabId) });
407
+ });
408
+ program.command("close <tabIds...>").description("Close one or more tabs by ID.").action(async (tabIds) => {
409
+ await run("chrome_close_tabs", { tabIds: tabIds.map(Number) });
410
+ });
411
+ program.command("call <tool> [json]").description("Call any Chrome Relay tool with raw JSON args.").action(async (tool, json) => {
412
+ const args = json ? JSON.parse(json) : {};
413
+ await run(tool, args);
414
+ });
415
+ program.parseAsync(process.argv);
@@ -205,13 +205,13 @@ function createMcpServer(bridge2) {
205
205
  // src/http/server.ts
206
206
  var RelayHttpServer = class {
207
207
  constructor(bridge2, port = DEFAULT_HTTP_PORT) {
208
+ this.bridge = bridge2;
208
209
  this.port = port;
209
- this.mcpServer = createMcpServer(bridge2);
210
210
  }
211
+ bridge;
211
212
  port;
212
213
  app = Fastify({ logger: false });
213
214
  transports = /* @__PURE__ */ new Map();
214
- mcpServer;
215
215
  async start() {
216
216
  await this.app.register(cors, { origin: true });
217
217
  this.app.get("/ping", async () => ({ ok: true, port: this.port }));
@@ -236,7 +236,8 @@ var RelayHttpServer = class {
236
236
  this.transports.delete(transport.sessionId);
237
237
  }
238
238
  };
239
- await this.mcpServer.connect(transport);
239
+ const mcpServer = createMcpServer(this.bridge);
240
+ await mcpServer.connect(transport);
240
241
  }
241
242
  await transport.handleRequest(request.raw, reply.raw, request.body);
242
243
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",