chrome-relay 0.1.0 → 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 +134 -1
- package/dist/native-host.js +4 -3
- package/package.json +4 -4
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
|
-
|
|
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);
|
package/dist/native-host.js
CHANGED
|
@@ -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
|
-
|
|
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,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-relay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"chrome-relay": "
|
|
8
|
+
"chrome-relay": "dist/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|