chrome-relay 0.1.2 → 0.2.0

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/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # chrome-relay
2
+
3
+ `chrome-relay` connects your local Chrome browser to coding agents through a local bridge and a Chrome extension.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -g chrome-relay
9
+ chrome-relay install
10
+ chrome-relay doctor
11
+ ```
12
+
13
+ Then load the Browser Relay extension in Chrome.
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ chrome-relay tabs
19
+ chrome-relay read -i
20
+ chrome-relay navigate "https://example.com" --new
21
+ chrome-relay click "<selector>"
22
+ chrome-relay fill "<selector>" "value"
23
+ chrome-relay keys "Enter"
24
+ chrome-relay screenshot -o page.png
25
+ ```
26
+
27
+ ## How it works
28
+
29
+ `chrome-relay` is a CLI-first browser bridge:
30
+
31
+ ```text
32
+ chrome-relay CLI
33
+ -> local bridge on your machine
34
+ -> Chrome native host
35
+ -> Browser Relay extension
36
+ -> Chrome APIs
37
+ ```
38
+
39
+ The CLI does not need separate MCP configuration. It talks to the local bridge for you.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import { writeFileSync } from "fs";
6
6
 
7
7
  // src/index.ts
8
- var CHROME_RELAY_VERSION = "0.1.0";
8
+ var CHROME_RELAY_VERSION = "0.2.0";
9
9
 
10
10
  // src/install/install.ts
11
11
  import os from "os";
@@ -17,144 +17,6 @@ import { fileURLToPath } from "url";
17
17
  var NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
18
18
  var DEFAULT_HTTP_PORT = 12122;
19
19
  var DEFAULT_EXTENSION_ID = "cdmmkpadhnpcfjljhgpdnnljhjafmhop";
20
- var TOOL_NAMES = {
21
- GET_WINDOWS_AND_TABS: "get_windows_and_tabs",
22
- NAVIGATE: "chrome_navigate",
23
- SWITCH_TAB: "chrome_switch_tab",
24
- CLOSE_TABS: "chrome_close_tabs",
25
- SCREENSHOT: "chrome_screenshot",
26
- READ_PAGE: "chrome_read_page",
27
- CLICK: "chrome_click_element",
28
- FILL: "chrome_fill_or_select",
29
- KEYBOARD: "chrome_keyboard",
30
- JAVASCRIPT: "chrome_javascript"
31
- };
32
- var TOOL_SCHEMAS = [
33
- {
34
- name: TOOL_NAMES.GET_WINDOWS_AND_TABS,
35
- description: "List open Chrome windows and tabs.",
36
- inputSchema: { type: "object", properties: {}, required: [] }
37
- },
38
- {
39
- name: TOOL_NAMES.NAVIGATE,
40
- description: "Navigate the current tab or a specific tab to a URL.",
41
- inputSchema: {
42
- type: "object",
43
- properties: {
44
- url: { type: "string", description: "Destination URL." },
45
- tabId: { type: "number", description: "Optional target tab ID." },
46
- newTab: { type: "boolean", description: "Open the URL in a new tab." },
47
- active: { type: "boolean", description: "Whether the navigated tab should be active." }
48
- },
49
- required: ["url"]
50
- }
51
- },
52
- {
53
- name: TOOL_NAMES.SWITCH_TAB,
54
- description: "Switch the active browser tab.",
55
- inputSchema: {
56
- type: "object",
57
- properties: {
58
- tabId: { type: "number", description: "Tab ID to activate." }
59
- },
60
- required: ["tabId"]
61
- }
62
- },
63
- {
64
- name: TOOL_NAMES.CLOSE_TABS,
65
- description: "Close one or more tabs.",
66
- inputSchema: {
67
- type: "object",
68
- properties: {
69
- tabIds: {
70
- type: "array",
71
- description: "Tab IDs to close.",
72
- items: { type: "number" }
73
- }
74
- },
75
- required: ["tabIds"]
76
- }
77
- },
78
- {
79
- name: TOOL_NAMES.SCREENSHOT,
80
- description: "Capture a screenshot of the current page.",
81
- inputSchema: {
82
- type: "object",
83
- properties: {
84
- tabId: { type: "number", description: "Optional target tab ID." },
85
- fullPage: { type: "boolean", description: "Capture the full page when supported." }
86
- },
87
- required: []
88
- }
89
- },
90
- {
91
- name: TOOL_NAMES.READ_PAGE,
92
- description: "Extract visible page structure and interactive elements.",
93
- inputSchema: {
94
- type: "object",
95
- properties: {
96
- tabId: { type: "number", description: "Optional target tab ID." },
97
- interactiveOnly: {
98
- type: "boolean",
99
- description: "Return only interactive elements."
100
- }
101
- },
102
- required: []
103
- }
104
- },
105
- {
106
- name: TOOL_NAMES.CLICK,
107
- description: "Click a page element by selector.",
108
- inputSchema: {
109
- type: "object",
110
- properties: {
111
- selector: { type: "string", description: "CSS selector to click." },
112
- tabId: { type: "number", description: "Optional target tab ID." }
113
- },
114
- required: ["selector"]
115
- }
116
- },
117
- {
118
- name: TOOL_NAMES.FILL,
119
- description: "Fill an input or textarea by selector.",
120
- inputSchema: {
121
- type: "object",
122
- properties: {
123
- selector: { type: "string", description: "CSS selector to fill." },
124
- value: { type: "string", description: "Text to insert." },
125
- tabId: { type: "number", description: "Optional target tab ID." }
126
- },
127
- required: ["selector", "value"]
128
- }
129
- },
130
- {
131
- name: TOOL_NAMES.KEYBOARD,
132
- description: "Send keyboard input to the active page.",
133
- inputSchema: {
134
- type: "object",
135
- properties: {
136
- keys: {
137
- type: "string",
138
- description: "Literal key text or chord, for example Enter or Meta+L."
139
- },
140
- tabId: { type: "number", description: "Optional target tab ID." }
141
- },
142
- required: ["keys"]
143
- }
144
- },
145
- {
146
- name: TOOL_NAMES.JAVASCRIPT,
147
- description: "Evaluate JavaScript in the page context.",
148
- inputSchema: {
149
- type: "object",
150
- properties: {
151
- expression: { type: "string", description: "JavaScript expression to run." },
152
- tabId: { type: "number", description: "Optional target tab ID." }
153
- },
154
- required: ["expression"]
155
- }
156
- }
157
- ];
158
20
 
159
21
  // src/install/install.ts
160
22
  var APP_DIR = path.join(os.homedir(), ".chrome-relay");
@@ -206,7 +68,7 @@ async function runInstall() {
206
68
  console.log(`Installed Chrome Relay native host.`);
207
69
  console.log(`Wrapper: ${wrapperPath}`);
208
70
  console.log(`Manifest: ${manifestPath}`);
209
- console.log(`HTTP MCP endpoint: http://127.0.0.1:${DEFAULT_HTTP_PORT}/mcp`);
71
+ console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
210
72
  }
211
73
  async function runDoctor() {
212
74
  try {
@@ -225,7 +87,7 @@ async function runDoctor() {
225
87
  console.log(`Wrapper present: yes`);
226
88
  console.log(`Manifest present: yes`);
227
89
  console.log(`Allowed origin: ${manifest.allowed_origins?.[0] ?? "missing"}`);
228
- console.log(`HTTP server reachable: ${serverReachable ? "yes" : "no"}`);
90
+ console.log(`Local bridge reachable: ${serverReachable ? "yes" : "no"}`);
229
91
  if (!serverReachable) {
230
92
  console.log(`Tip: load the extension so it can launch the native host.`);
231
93
  }
@@ -236,68 +98,31 @@ async function runDoctor() {
236
98
  }
237
99
  }
238
100
 
239
- // src/stdio.ts
240
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
241
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
242
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
243
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
244
- import {
245
- CallToolRequestSchema,
246
- ListPromptsRequestSchema,
247
- ListResourcesRequestSchema,
248
- ListToolsRequestSchema
249
- } from "@modelcontextprotocol/sdk/types.js";
250
- async function runStdioProxy() {
251
- const client = new Client({ name: "chrome-relay-stdio", version: "0.1.0" }, { capabilities: {} });
252
- const transport = new StreamableHTTPClientTransport(
253
- new URL(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/mcp`)
254
- );
255
- await client.connect(transport);
256
- const server = new Server(
257
- { name: "chrome-relay-stdio", version: "0.1.0" },
258
- { capabilities: { tools: {}, resources: {}, prompts: {} } }
259
- );
260
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
261
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
262
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
263
- server.setRequestHandler(
264
- CallToolRequestSchema,
265
- async (request) => client.callTool({ name: request.params.name, arguments: request.params.arguments ?? {} })
266
- );
267
- await server.connect(new StdioServerTransport());
268
- }
269
-
270
101
  // 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
102
  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
- });
103
+ const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/call`, {
104
+ method: "POST",
105
+ headers: {
106
+ "content-type": "application/json"
107
+ },
108
+ body: JSON.stringify({
109
+ name,
110
+ args
111
+ })
112
+ });
113
+ const payload = await response.json().catch(() => null);
114
+ if (!response.ok) {
115
+ throw new Error(payload?.error || `Bridge request failed with ${response.status}`);
295
116
  }
117
+ if (!payload?.ok) {
118
+ throw new Error(payload?.error || "Bridge call failed.");
119
+ }
120
+ return payload.data;
296
121
  }
297
122
 
298
123
  // src/cli.ts
299
124
  var program = new Command();
300
- program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through MCP.").version(CHROME_RELAY_VERSION);
125
+ program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through a local bridge.").version(CHROME_RELAY_VERSION);
301
126
  program.command("install").description("Install and register the local Chrome Relay host.").action(async () => {
302
127
  await runInstall();
303
128
  });
@@ -305,9 +130,6 @@ program.command("doctor").description("Validate the local Chrome Relay installat
305
130
  const ok = await runDoctor();
306
131
  process.exit(ok ? 0 : 1);
307
132
  });
308
- program.command("stdio").description("Expose Chrome Relay over stdio for MCP clients that do not support HTTP.").action(async () => {
309
- await runStdioProxy();
310
- });
311
133
  async function run(name, args) {
312
134
  try {
313
135
  const result = await callTool(name, args);
@@ -395,13 +217,6 @@ tabOpt(
395
217
  if (opts.tab !== void 0) args.tabId = opts.tab;
396
218
  await run("chrome_keyboard", args);
397
219
  });
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
220
  program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
406
221
  await run("chrome_switch_tab", { tabId: Number(tabId) });
407
222
  });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare const CHROME_RELAY_VERSION = "0.1.0";
1
+ declare const CHROME_RELAY_VERSION = "0.2.0";
2
2
 
3
3
  export { CHROME_RELAY_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = "0.1.0";
2
+ var CHROME_RELAY_VERSION = "0.2.0";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -5,202 +5,9 @@ import process from "process";
5
5
 
6
6
  // src/http/server.ts
7
7
  import Fastify from "fastify";
8
- import cors from "@fastify/cors";
9
- import { randomUUID } from "crypto";
10
- import {
11
- StreamableHTTPServerTransport
12
- } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
14
8
 
15
9
  // ../protocol/dist/index.js
16
10
  var DEFAULT_HTTP_PORT = 12122;
17
- var TOOL_NAMES = {
18
- GET_WINDOWS_AND_TABS: "get_windows_and_tabs",
19
- NAVIGATE: "chrome_navigate",
20
- SWITCH_TAB: "chrome_switch_tab",
21
- CLOSE_TABS: "chrome_close_tabs",
22
- SCREENSHOT: "chrome_screenshot",
23
- READ_PAGE: "chrome_read_page",
24
- CLICK: "chrome_click_element",
25
- FILL: "chrome_fill_or_select",
26
- KEYBOARD: "chrome_keyboard",
27
- JAVASCRIPT: "chrome_javascript"
28
- };
29
- var TOOL_SCHEMAS = [
30
- {
31
- name: TOOL_NAMES.GET_WINDOWS_AND_TABS,
32
- description: "List open Chrome windows and tabs.",
33
- inputSchema: { type: "object", properties: {}, required: [] }
34
- },
35
- {
36
- name: TOOL_NAMES.NAVIGATE,
37
- description: "Navigate the current tab or a specific tab to a URL.",
38
- inputSchema: {
39
- type: "object",
40
- properties: {
41
- url: { type: "string", description: "Destination URL." },
42
- tabId: { type: "number", description: "Optional target tab ID." },
43
- newTab: { type: "boolean", description: "Open the URL in a new tab." },
44
- active: { type: "boolean", description: "Whether the navigated tab should be active." }
45
- },
46
- required: ["url"]
47
- }
48
- },
49
- {
50
- name: TOOL_NAMES.SWITCH_TAB,
51
- description: "Switch the active browser tab.",
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- tabId: { type: "number", description: "Tab ID to activate." }
56
- },
57
- required: ["tabId"]
58
- }
59
- },
60
- {
61
- name: TOOL_NAMES.CLOSE_TABS,
62
- description: "Close one or more tabs.",
63
- inputSchema: {
64
- type: "object",
65
- properties: {
66
- tabIds: {
67
- type: "array",
68
- description: "Tab IDs to close.",
69
- items: { type: "number" }
70
- }
71
- },
72
- required: ["tabIds"]
73
- }
74
- },
75
- {
76
- name: TOOL_NAMES.SCREENSHOT,
77
- description: "Capture a screenshot of the current page.",
78
- inputSchema: {
79
- type: "object",
80
- properties: {
81
- tabId: { type: "number", description: "Optional target tab ID." },
82
- fullPage: { type: "boolean", description: "Capture the full page when supported." }
83
- },
84
- required: []
85
- }
86
- },
87
- {
88
- name: TOOL_NAMES.READ_PAGE,
89
- description: "Extract visible page structure and interactive elements.",
90
- inputSchema: {
91
- type: "object",
92
- properties: {
93
- tabId: { type: "number", description: "Optional target tab ID." },
94
- interactiveOnly: {
95
- type: "boolean",
96
- description: "Return only interactive elements."
97
- }
98
- },
99
- required: []
100
- }
101
- },
102
- {
103
- name: TOOL_NAMES.CLICK,
104
- description: "Click a page element by selector.",
105
- inputSchema: {
106
- type: "object",
107
- properties: {
108
- selector: { type: "string", description: "CSS selector to click." },
109
- tabId: { type: "number", description: "Optional target tab ID." }
110
- },
111
- required: ["selector"]
112
- }
113
- },
114
- {
115
- name: TOOL_NAMES.FILL,
116
- description: "Fill an input or textarea by selector.",
117
- inputSchema: {
118
- type: "object",
119
- properties: {
120
- selector: { type: "string", description: "CSS selector to fill." },
121
- value: { type: "string", description: "Text to insert." },
122
- tabId: { type: "number", description: "Optional target tab ID." }
123
- },
124
- required: ["selector", "value"]
125
- }
126
- },
127
- {
128
- name: TOOL_NAMES.KEYBOARD,
129
- description: "Send keyboard input to the active page.",
130
- inputSchema: {
131
- type: "object",
132
- properties: {
133
- keys: {
134
- type: "string",
135
- description: "Literal key text or chord, for example Enter or Meta+L."
136
- },
137
- tabId: { type: "number", description: "Optional target tab ID." }
138
- },
139
- required: ["keys"]
140
- }
141
- },
142
- {
143
- name: TOOL_NAMES.JAVASCRIPT,
144
- description: "Evaluate JavaScript in the page context.",
145
- inputSchema: {
146
- type: "object",
147
- properties: {
148
- expression: { type: "string", description: "JavaScript expression to run." },
149
- tabId: { type: "number", description: "Optional target tab ID." }
150
- },
151
- required: ["expression"]
152
- }
153
- }
154
- ];
155
-
156
- // src/mcp/server.ts
157
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
158
- import {
159
- CallToolRequestSchema,
160
- ListPromptsRequestSchema,
161
- ListResourcesRequestSchema,
162
- ListToolsRequestSchema
163
- } from "@modelcontextprotocol/sdk/types.js";
164
- function toCallToolResult(data) {
165
- return {
166
- content: [
167
- {
168
- type: "text",
169
- text: typeof data === "string" ? data : JSON.stringify(data)
170
- }
171
- ],
172
- isError: false
173
- };
174
- }
175
- function createMcpServer(bridge2) {
176
- const server2 = new Server(
177
- { name: "chrome-relay", version: "0.1.0" },
178
- { capabilities: { tools: {}, resources: {}, prompts: {} } }
179
- );
180
- server2.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
181
- server2.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
182
- server2.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
183
- server2.setRequestHandler(CallToolRequestSchema, async (request) => {
184
- try {
185
- const result = await bridge2.callTool(
186
- request.params.name,
187
- request.params.arguments ?? {}
188
- );
189
- return toCallToolResult(result);
190
- } catch (error) {
191
- return {
192
- content: [
193
- {
194
- type: "text",
195
- text: error instanceof Error ? error.message : String(error)
196
- }
197
- ],
198
- isError: true
199
- };
200
- }
201
- });
202
- return server2;
203
- }
204
11
 
205
12
  // src/http/server.ts
206
13
  var RelayHttpServer = class {
@@ -211,61 +18,30 @@ var RelayHttpServer = class {
211
18
  bridge;
212
19
  port;
213
20
  app = Fastify({ logger: false });
214
- transports = /* @__PURE__ */ new Map();
215
21
  async start() {
216
- await this.app.register(cors, { origin: true });
217
22
  this.app.get("/ping", async () => ({ ok: true, port: this.port }));
218
- this.app.post("/mcp", async (request, reply) => {
219
- const sessionId = request.headers["mcp-session-id"];
220
- let transport = sessionId ? this.transports.get(sessionId) : void 0;
221
- if (!transport) {
222
- if (sessionId || !isInitializeRequest(request.body)) {
223
- reply.code(400).send({ error: "Invalid MCP session." });
224
- return;
225
- }
226
- transport = new StreamableHTTPServerTransport({
227
- sessionIdGenerator: () => randomUUID(),
228
- onsessioninitialized: (nextSessionId) => {
229
- if (transport) {
230
- this.transports.set(nextSessionId, transport);
231
- }
232
- }
233
- });
234
- transport.onclose = () => {
235
- if (transport?.sessionId) {
236
- this.transports.delete(transport.sessionId);
237
- }
238
- };
239
- const mcpServer = createMcpServer(this.bridge);
240
- await mcpServer.connect(transport);
241
- }
242
- await transport.handleRequest(request.raw, reply.raw, request.body);
243
- });
244
- this.app.get("/mcp", async (request, reply) => {
245
- const sessionId = request.headers["mcp-session-id"];
246
- if (!sessionId) {
247
- reply.code(400).send({ error: "Missing MCP session ID." });
248
- return;
249
- }
250
- const transport = this.transports.get(sessionId);
251
- if (!transport) {
252
- reply.code(404).send({ error: "Unknown MCP session." });
23
+ this.app.post("/call", async (request, reply) => {
24
+ if (request.headers.origin) {
25
+ reply.code(403).send({ error: "Browser-origin bridge requests are not accepted." });
253
26
  return;
254
27
  }
255
- await transport.handleRequest(request.raw, reply.raw);
256
- });
257
- this.app.delete("/mcp", async (request, reply) => {
258
- const sessionId = request.headers["mcp-session-id"];
259
- if (!sessionId) {
260
- reply.code(400).send({ error: "Missing MCP session ID." });
28
+ const body = request.body ?? {};
29
+ if (typeof body.name !== "string") {
30
+ reply.code(400).send({ ok: false, error: "Missing tool name." });
261
31
  return;
262
32
  }
263
- const transport = this.transports.get(sessionId);
264
- if (!transport) {
265
- reply.code(404).send({ error: "Unknown MCP session." });
266
- return;
33
+ try {
34
+ const data = await this.bridge.callTool(
35
+ body.name,
36
+ body.args ?? {}
37
+ );
38
+ reply.send({ ok: true, data });
39
+ } catch (error) {
40
+ reply.code(500).send({
41
+ ok: false,
42
+ error: error instanceof Error ? error.message : String(error)
43
+ });
267
44
  }
268
- await transport.handleRequest(request.raw, reply.raw);
269
45
  });
270
46
  await this.app.listen({ port: this.port, host: "127.0.0.1" });
271
47
  }
@@ -275,7 +51,7 @@ var RelayHttpServer = class {
275
51
  };
276
52
 
277
53
  // src/native/bridge.ts
278
- import { randomUUID as randomUUID2 } from "crypto";
54
+ import { randomUUID } from "crypto";
279
55
  var ExtensionBridge = class {
280
56
  constructor(send) {
281
57
  this.send = send;
@@ -340,7 +116,7 @@ var ExtensionBridge = class {
340
116
  });
341
117
  }
342
118
  async ping(timeoutMs = 2e3) {
343
- const id = randomUUID2();
119
+ const id = randomUUID();
344
120
  const message = { type: "bridge.ping", id };
345
121
  return new Promise((resolve) => {
346
122
  const timer = setTimeout(() => {
@@ -357,7 +133,7 @@ var ExtensionBridge = class {
357
133
  }
358
134
  async callTool(name, args, timeoutMs = 3e4) {
359
135
  await this.waitUntilReady();
360
- const id = randomUUID2();
136
+ const id = randomUUID();
361
137
  return new Promise((resolve, reject) => {
362
138
  const timer = setTimeout(() => {
363
139
  this.pending.delete(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,12 +15,10 @@
15
15
  "lint": "tsc -p tsconfig.json --noEmit",
16
16
  "typecheck": "tsc -p tsconfig.json --noEmit"
17
17
  },
18
- "description": "Connect your local Chrome browser to coding agents through MCP.",
19
- "keywords": ["mcp", "chrome", "browser", "automation", "agents"],
18
+ "description": "Connect your local Chrome browser to coding agents through a local bridge.",
19
+ "keywords": ["chrome", "browser", "automation", "agents", "native-messaging"],
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@fastify/cors": "^11.0.1",
23
- "@modelcontextprotocol/sdk": "^1.11.0",
24
22
  "chalk": "^5.4.1",
25
23
  "commander": "^13.1.0",
26
24
  "fastify": "^5.3.2"
package/dist/stdio.d.ts DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- declare function runStdioProxy(): Promise<void>;
3
-
4
- export { runStdioProxy };
package/dist/stdio.js DELETED
@@ -1,178 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/stdio.ts
4
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
- import {
9
- CallToolRequestSchema,
10
- ListPromptsRequestSchema,
11
- ListResourcesRequestSchema,
12
- ListToolsRequestSchema
13
- } from "@modelcontextprotocol/sdk/types.js";
14
-
15
- // ../protocol/dist/index.js
16
- var DEFAULT_HTTP_PORT = 12122;
17
- var TOOL_NAMES = {
18
- GET_WINDOWS_AND_TABS: "get_windows_and_tabs",
19
- NAVIGATE: "chrome_navigate",
20
- SWITCH_TAB: "chrome_switch_tab",
21
- CLOSE_TABS: "chrome_close_tabs",
22
- SCREENSHOT: "chrome_screenshot",
23
- READ_PAGE: "chrome_read_page",
24
- CLICK: "chrome_click_element",
25
- FILL: "chrome_fill_or_select",
26
- KEYBOARD: "chrome_keyboard",
27
- JAVASCRIPT: "chrome_javascript"
28
- };
29
- var TOOL_SCHEMAS = [
30
- {
31
- name: TOOL_NAMES.GET_WINDOWS_AND_TABS,
32
- description: "List open Chrome windows and tabs.",
33
- inputSchema: { type: "object", properties: {}, required: [] }
34
- },
35
- {
36
- name: TOOL_NAMES.NAVIGATE,
37
- description: "Navigate the current tab or a specific tab to a URL.",
38
- inputSchema: {
39
- type: "object",
40
- properties: {
41
- url: { type: "string", description: "Destination URL." },
42
- tabId: { type: "number", description: "Optional target tab ID." },
43
- newTab: { type: "boolean", description: "Open the URL in a new tab." },
44
- active: { type: "boolean", description: "Whether the navigated tab should be active." }
45
- },
46
- required: ["url"]
47
- }
48
- },
49
- {
50
- name: TOOL_NAMES.SWITCH_TAB,
51
- description: "Switch the active browser tab.",
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- tabId: { type: "number", description: "Tab ID to activate." }
56
- },
57
- required: ["tabId"]
58
- }
59
- },
60
- {
61
- name: TOOL_NAMES.CLOSE_TABS,
62
- description: "Close one or more tabs.",
63
- inputSchema: {
64
- type: "object",
65
- properties: {
66
- tabIds: {
67
- type: "array",
68
- description: "Tab IDs to close.",
69
- items: { type: "number" }
70
- }
71
- },
72
- required: ["tabIds"]
73
- }
74
- },
75
- {
76
- name: TOOL_NAMES.SCREENSHOT,
77
- description: "Capture a screenshot of the current page.",
78
- inputSchema: {
79
- type: "object",
80
- properties: {
81
- tabId: { type: "number", description: "Optional target tab ID." },
82
- fullPage: { type: "boolean", description: "Capture the full page when supported." }
83
- },
84
- required: []
85
- }
86
- },
87
- {
88
- name: TOOL_NAMES.READ_PAGE,
89
- description: "Extract visible page structure and interactive elements.",
90
- inputSchema: {
91
- type: "object",
92
- properties: {
93
- tabId: { type: "number", description: "Optional target tab ID." },
94
- interactiveOnly: {
95
- type: "boolean",
96
- description: "Return only interactive elements."
97
- }
98
- },
99
- required: []
100
- }
101
- },
102
- {
103
- name: TOOL_NAMES.CLICK,
104
- description: "Click a page element by selector.",
105
- inputSchema: {
106
- type: "object",
107
- properties: {
108
- selector: { type: "string", description: "CSS selector to click." },
109
- tabId: { type: "number", description: "Optional target tab ID." }
110
- },
111
- required: ["selector"]
112
- }
113
- },
114
- {
115
- name: TOOL_NAMES.FILL,
116
- description: "Fill an input or textarea by selector.",
117
- inputSchema: {
118
- type: "object",
119
- properties: {
120
- selector: { type: "string", description: "CSS selector to fill." },
121
- value: { type: "string", description: "Text to insert." },
122
- tabId: { type: "number", description: "Optional target tab ID." }
123
- },
124
- required: ["selector", "value"]
125
- }
126
- },
127
- {
128
- name: TOOL_NAMES.KEYBOARD,
129
- description: "Send keyboard input to the active page.",
130
- inputSchema: {
131
- type: "object",
132
- properties: {
133
- keys: {
134
- type: "string",
135
- description: "Literal key text or chord, for example Enter or Meta+L."
136
- },
137
- tabId: { type: "number", description: "Optional target tab ID." }
138
- },
139
- required: ["keys"]
140
- }
141
- },
142
- {
143
- name: TOOL_NAMES.JAVASCRIPT,
144
- description: "Evaluate JavaScript in the page context.",
145
- inputSchema: {
146
- type: "object",
147
- properties: {
148
- expression: { type: "string", description: "JavaScript expression to run." },
149
- tabId: { type: "number", description: "Optional target tab ID." }
150
- },
151
- required: ["expression"]
152
- }
153
- }
154
- ];
155
-
156
- // src/stdio.ts
157
- async function runStdioProxy() {
158
- const client = new Client({ name: "chrome-relay-stdio", version: "0.1.0" }, { capabilities: {} });
159
- const transport = new StreamableHTTPClientTransport(
160
- new URL(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/mcp`)
161
- );
162
- await client.connect(transport);
163
- const server = new Server(
164
- { name: "chrome-relay-stdio", version: "0.1.0" },
165
- { capabilities: { tools: {}, resources: {}, prompts: {} } }
166
- );
167
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
168
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
169
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
170
- server.setRequestHandler(
171
- CallToolRequestSchema,
172
- async (request) => client.callTool({ name: request.params.name, arguments: request.params.arguments ?? {} })
173
- );
174
- await server.connect(new StdioServerTransport());
175
- }
176
- export {
177
- runStdioProxy
178
- };