poke-browser 0.2.8
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 +60 -0
- package/cli.mjs +457 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +23 -0
- package/dist/logger.js.map +1 -0
- package/dist/run.d.ts +7 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +279 -0
- package/dist/run.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +13 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +707 -0
- package/dist/tools.js.map +1 -0
- package/dist/transport.d.ts +60 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +387 -0
- package/dist/transport.js.map +1 -0
- package/extension/background.js +1998 -0
- package/extension/content.js +1416 -0
- package/extension/icon.png +0 -0
- package/extension/manifest.json +48 -0
- package/extension/offscreen.html +10 -0
- package/extension/offscreen.js +246 -0
- package/extension/popup.html +426 -0
- package/extension/popup.js +205 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# poke-browser-mcp
|
|
2
|
+
|
|
3
|
+
Node process that implements an **MCP server** (stdio) and a **WebSocket server** (localhost) used by the **poke-browser** Chrome extension.
|
|
4
|
+
|
|
5
|
+
## Role in the stack
|
|
6
|
+
|
|
7
|
+
1. **WebSocket (extension → this process)**
|
|
8
|
+
The extension connects to `ws://127.0.0.1:<port>`. This package listens on that port (default **9009**). When a new client connects, any previous client is closed so only one browser session is attached at a time.
|
|
9
|
+
|
|
10
|
+
2. **JSON commands**
|
|
11
|
+
The server sends messages shaped like:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "type": "command", "id": "<uuid>", "command": "navigate", "payload": { "url": "https://example.com" } }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The extension replies with:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{ "type": "response", "id": "<uuid>", "ok": true, "result": { } }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
or `ok: false` and an `error` string.
|
|
24
|
+
|
|
25
|
+
On connect, the extension may send `{ "type": "hello", "client": "poke-browser-extension", "version": "..." }` for logging only.
|
|
26
|
+
|
|
27
|
+
3. **MCP (AI client → this process)**
|
|
28
|
+
The official `@modelcontextprotocol/sdk` exposes tools (`list_tabs`, `get_active_tab`, `navigate`, `click`, `screenshot`, `evaluate_js`, `new_tab`, `close_tab`). Each tool forwards to the extension over the WebSocket and returns JSON text in the MCP result.
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
| Variable | Default | Meaning |
|
|
33
|
+
|----------|---------|---------|
|
|
34
|
+
| `POKE_BROWSER_WS_PORT` | `9009` | WebSocket listen port (`WS_PORT` is also read as a fallback) |
|
|
35
|
+
|
|
36
|
+
The Chrome extension stores its target port in `chrome.storage.local` (`wsPort`); keep it aligned with this value.
|
|
37
|
+
|
|
38
|
+
## Scripts
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
|---------|-------------|
|
|
42
|
+
| `npm start` | Run `index.ts` with `tsx` (stdio MCP + WebSocket) |
|
|
43
|
+
| `npm run build` | Emit JavaScript to `dist/` |
|
|
44
|
+
| `npm run serve` | Run `node dist/index.js` |
|
|
45
|
+
|
|
46
|
+
## Dependencies
|
|
47
|
+
|
|
48
|
+
- `@modelcontextprotocol/sdk` — MCP server over stdio
|
|
49
|
+
- `ws` — WebSocket server for the extension
|
|
50
|
+
- `zod` — tool input schemas (peer-style dependency of the SDK)
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
Typecheck:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx tsc --noEmit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The server logs WebSocket lifecycle messages on **stderr** so stdio **stdout** stays clean for MCP JSON-RPC.
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Launcher for poke-browser MCP (same Poke auth + tunnel pattern as @leokok/poke-apple-music).
|
|
4
|
+
* Auth: `npx poke@latest whoami` / `poke login` — not POKE_API_KEY.
|
|
5
|
+
* Tunnel: `npx poke@latest tunnel <local /mcp URL> -n "<label>"` (stdio inherit).
|
|
6
|
+
*
|
|
7
|
+
* Mode selection (first match wins):
|
|
8
|
+
* 1. stdin is a pipe (not TTY) → stdio MCP mode (Cursor, Claude Desktop, etc.)
|
|
9
|
+
* 2. --http or --stdio flag → explicit HTTP / stdio mode
|
|
10
|
+
* 3. everything else → poke-tunnel mode (interactive terminal)
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { stdin as input, stderr as output } from "node:process";
|
|
18
|
+
|
|
19
|
+
async function findAvailablePort(startPort) {
|
|
20
|
+
const net = await import("node:net");
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const server = net.createServer();
|
|
23
|
+
server.listen(startPort, "127.0.0.1", () => {
|
|
24
|
+
const port = server.address().port;
|
|
25
|
+
server.close(() => resolve(port));
|
|
26
|
+
});
|
|
27
|
+
server.on("error", () => resolve(findAvailablePort(startPort + 1)));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle setup command
|
|
32
|
+
if (process.argv.includes("setup") || process.argv.includes("--install")) {
|
|
33
|
+
const { cp, mkdir } = await import("node:fs/promises");
|
|
34
|
+
const { resolve } = await import("node:path");
|
|
35
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const srcExt = resolve(__dir, "..", "extension");
|
|
37
|
+
const destExt = resolve(process.cwd(), "poke-browser-extension");
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(destExt, { recursive: true });
|
|
40
|
+
await cp(srcExt, destExt, { recursive: true, force: true });
|
|
41
|
+
console.log("\n poke-browser setup complete!\n");
|
|
42
|
+
console.log(" Extension copied to: " + destExt + "\n");
|
|
43
|
+
console.log(" Next: open chrome://extensions, enable Developer Mode,");
|
|
44
|
+
console.log(' click "Load unpacked" and select: ' + destExt + "\n");
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error(" Setup failed:", e.message);
|
|
47
|
+
}
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const pkg = require("./package.json");
|
|
53
|
+
const VERSION = pkg.version;
|
|
54
|
+
|
|
55
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
56
|
+
const entry = join(root, "dist", "index.js");
|
|
57
|
+
|
|
58
|
+
const rawArgs = process.argv.slice(2);
|
|
59
|
+
const verboseCli =
|
|
60
|
+
rawArgs.includes("--debug") || rawArgs.includes("--verbose");
|
|
61
|
+
|
|
62
|
+
/** Same shape as @leokok/poke-agents `argAfter` (used there for `--mcp-name`). */
|
|
63
|
+
function argAfter(flag) {
|
|
64
|
+
const i = process.argv.indexOf(flag);
|
|
65
|
+
if (i === -1) return null;
|
|
66
|
+
const v = process.argv[i + 1];
|
|
67
|
+
if (v == null || String(v).startsWith("-")) return null;
|
|
68
|
+
return String(v);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function slugifyMcpServerName(s) {
|
|
72
|
+
const t = String(s)
|
|
73
|
+
.trim()
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
76
|
+
.replace(/^-+|-+$/g, "");
|
|
77
|
+
if (t.length === 0) return "poke-browser-mcp";
|
|
78
|
+
return t.length > 64 ? t.slice(0, 64) : t;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Custom Poke tunnel `-n` label + MCP `initialize` server name (when `--name` is passed). */
|
|
82
|
+
const customMcpName = argAfter("--name");
|
|
83
|
+
|
|
84
|
+
const color =
|
|
85
|
+
output.isTTY && !process.env.NO_COLOR
|
|
86
|
+
? {
|
|
87
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
88
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
89
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
90
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
91
|
+
grey: (s) => `\x1b[90m${s}\x1b[0m`,
|
|
92
|
+
}
|
|
93
|
+
: {
|
|
94
|
+
dim: (s) => s,
|
|
95
|
+
bold: (s) => s,
|
|
96
|
+
green: (s) => s,
|
|
97
|
+
red: (s) => s,
|
|
98
|
+
grey: (s) => s,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Same check as poke-apple-music `checkPokeLogin`. */
|
|
102
|
+
function checkPokeLogin() {
|
|
103
|
+
const r = spawnSync("npx", ["--yes", "poke@latest", "whoami"], {
|
|
104
|
+
encoding: "utf8",
|
|
105
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
106
|
+
env: process.env,
|
|
107
|
+
});
|
|
108
|
+
return r.status === 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Same flow as poke-apple-music preflight: `poke whoami`, then `poke login` if needed.
|
|
113
|
+
* Set POKE_BROWSER_SKIP_POKE_LOGIN=1 to skip (e.g. CI).
|
|
114
|
+
*/
|
|
115
|
+
function ensurePokeLoginForTunnel() {
|
|
116
|
+
if (process.env.POKE_BROWSER_SKIP_POKE_LOGIN === "1") {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (checkPokeLogin()) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
console.error(
|
|
123
|
+
color.dim(" Not signed in to Poke (`npx poke@latest whoami` failed)."),
|
|
124
|
+
);
|
|
125
|
+
if (!input.isTTY) {
|
|
126
|
+
console.error(
|
|
127
|
+
color.red(
|
|
128
|
+
" Run `npx poke@latest login` in a terminal, or set POKE_BROWSER_SKIP_POKE_LOGIN=1.",
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
console.error(
|
|
134
|
+
color.dim(
|
|
135
|
+
" Starting Poke login in your browser (complete the flow, then return here).",
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
const login = spawnSync("npx", ["--yes", "poke@latest", "login"], {
|
|
139
|
+
stdio: "inherit",
|
|
140
|
+
env: process.env,
|
|
141
|
+
});
|
|
142
|
+
if (login.status !== 0) {
|
|
143
|
+
console.error(color.red(" `poke login` did not finish successfully."));
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (!checkPokeLogin()) {
|
|
147
|
+
console.error(color.red(" Still not signed in after login."));
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function extensionFolderPath() {
|
|
154
|
+
const inPkg = join(root, "extension");
|
|
155
|
+
if (existsSync(inPkg)) return inPkg;
|
|
156
|
+
return join(root, "..", "extension");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function printQuietStartupBanner({
|
|
160
|
+
mcpPort,
|
|
161
|
+
wsPort,
|
|
162
|
+
mcpDesiredStart,
|
|
163
|
+
wsDesiredStart,
|
|
164
|
+
showMcpLine,
|
|
165
|
+
}) {
|
|
166
|
+
const extPath = extensionFolderPath();
|
|
167
|
+
const mcpAuto = mcpPort !== mcpDesiredStart ? " (auto-selected)" : "";
|
|
168
|
+
const wsAuto = wsPort !== wsDesiredStart ? " (auto-selected)" : "";
|
|
169
|
+
console.error("");
|
|
170
|
+
console.error(
|
|
171
|
+
customMcpName
|
|
172
|
+
? ` poke-browser v${VERSION} (as '${customMcpName}')`
|
|
173
|
+
: ` poke-browser v${VERSION}`,
|
|
174
|
+
);
|
|
175
|
+
console.error("");
|
|
176
|
+
console.error(" Load the Chrome extension:");
|
|
177
|
+
console.error("");
|
|
178
|
+
console.error(" 1. Open chrome://extensions");
|
|
179
|
+
console.error("");
|
|
180
|
+
console.error(" 2. Enable Developer Mode");
|
|
181
|
+
console.error("");
|
|
182
|
+
console.error(" 3. Click Load unpacked \u2192 select the /extension folder");
|
|
183
|
+
console.error(
|
|
184
|
+
color.grey(
|
|
185
|
+
" (NOT the root \u2014 open poke-browser/extension specifically)",
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
console.error(color.dim(` ${extPath}`));
|
|
189
|
+
console.error("");
|
|
190
|
+
console.error(" 4. Extension auto-connects to this server");
|
|
191
|
+
console.error("");
|
|
192
|
+
console.error(" \u2605 Star us: https://github.com/leoakok/poke-browser");
|
|
193
|
+
console.error("");
|
|
194
|
+
console.error(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
195
|
+
if (showMcpLine) {
|
|
196
|
+
console.error(
|
|
197
|
+
` Local MCP: http://127.0.0.1:${mcpPort}/mcp${mcpAuto}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
console.error(` Local WS: ws://127.0.0.1:${wsPort}${wsAuto}`);
|
|
201
|
+
console.error("");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function childEnv() {
|
|
205
|
+
const env = { ...process.env };
|
|
206
|
+
if (verboseCli) env.POKE_BROWSER_VERBOSE = "1";
|
|
207
|
+
if (customMcpName) {
|
|
208
|
+
env.POKE_BROWSER_TUNNEL_NAME = customMcpName;
|
|
209
|
+
env.POKE_BROWSER_MCP_SERVER_NAME = slugifyMcpServerName(customMcpName);
|
|
210
|
+
}
|
|
211
|
+
return env;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
215
|
+
console.error(`poke-browser \u2014 MCP server for the poke-browser Chrome extension
|
|
216
|
+
|
|
217
|
+
Usage:
|
|
218
|
+
poke-browser Poke tunnel mode when run in a terminal (default)
|
|
219
|
+
poke-browser --poke-tunnel Same as above (explicit)
|
|
220
|
+
poke-browser --stdio Force stdio MCP mode (Cursor, Claude Desktop, etc.)
|
|
221
|
+
poke-browser --http [port] Streamable HTTP MCP on 127.0.0.1 (default: env POKE_BROWSER_MCP_PORT or 8755)
|
|
222
|
+
poke-browser --tunnel [port] Same as --http, then: npx poke@latest tunnel \u2026/mcp
|
|
223
|
+
poke-browser --name <label> Poke tunnel -n label and MCP server id
|
|
224
|
+
poke-browser --debug Verbose stderr ([poke-browser], WebSocket port, MCP debug)
|
|
225
|
+
poke-browser --verbose Same as --debug
|
|
226
|
+
|
|
227
|
+
Mode selection (first match wins):
|
|
228
|
+
- stdin is NOT a TTY (piped) \u2192 stdio mode (Cursor / Claude Desktop auto-detection)
|
|
229
|
+
- --http or --stdio flag \u2192 explicit HTTP / stdio mode
|
|
230
|
+
- interactive terminal \u2192 poke-tunnel mode (shows public URL)
|
|
231
|
+
|
|
232
|
+
Poke auth (tunnel flows):
|
|
233
|
+
Uses the global Poke CLI \u2014 same as @leokok/poke-apple-music:
|
|
234
|
+
npx poke@latest whoami # must succeed before tunnel
|
|
235
|
+
npx poke@latest login # browser login if needed
|
|
236
|
+
Optional: POKE_BROWSER_SKIP_POKE_LOGIN=1 to skip the whoami/login gate.
|
|
237
|
+
|
|
238
|
+
Environment:
|
|
239
|
+
POKE_BROWSER_WS_PORT WebSocket port for the extension (default 9009)
|
|
240
|
+
POKE_BROWSER_MCP_PORT HTTP MCP listen port for --http / tunnel (default 8755)
|
|
241
|
+
POKE_BROWSER_PORT Alias for HTTP MCP port (same as run.ts)
|
|
242
|
+
POKE_BROWSER_TUNNEL_NAME poke tunnel -n label (default: poke-browser; --name overrides)
|
|
243
|
+
POKE_BROWSER_MCP_SERVER_NAME MCP initialize server name slug (set from --name when passed)
|
|
244
|
+
|
|
245
|
+
Build once (or pass --build):
|
|
246
|
+
npm run build
|
|
247
|
+
`);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
|
|
252
|
+
console.error(pkg.version ?? "0.0.0");
|
|
253
|
+
process.exit(0);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const wantBuild = rawArgs.includes("--build");
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Tunnel mode is active when ANY of:
|
|
260
|
+
* 1. npm run start:tunnel lifecycle
|
|
261
|
+
* 2. explicit --poke-tunnel flag
|
|
262
|
+
* 3. --name flag (custom label implies tunnel intent)
|
|
263
|
+
* 4. stdin is an interactive TTY AND no explicit --http / --stdio / pipe mode
|
|
264
|
+
*
|
|
265
|
+
* stdio mode (for Cursor / Claude Desktop) is used when:
|
|
266
|
+
* - stdin is NOT a TTY (process is spawned with a pipe), OR
|
|
267
|
+
* - --stdio flag is explicitly passed, OR
|
|
268
|
+
* - --http flag is explicitly passed
|
|
269
|
+
*/
|
|
270
|
+
const wantStdioMode =
|
|
271
|
+
!input.isTTY ||
|
|
272
|
+
rawArgs.includes("--stdio") ||
|
|
273
|
+
rawArgs.includes("--http");
|
|
274
|
+
|
|
275
|
+
const wantPokeTunnelFlow =
|
|
276
|
+
!wantStdioMode ||
|
|
277
|
+
process.env.npm_lifecycle_event === "start:tunnel" ||
|
|
278
|
+
rawArgs.includes("--poke-tunnel") ||
|
|
279
|
+
!!customMcpName;
|
|
280
|
+
|
|
281
|
+
const childArgs = rawArgs.filter((a, i, arr) => {
|
|
282
|
+
if (
|
|
283
|
+
a === "--build" ||
|
|
284
|
+
a === "--poke-tunnel" ||
|
|
285
|
+
a === "--stdio" ||
|
|
286
|
+
a === "--debug" ||
|
|
287
|
+
a === "--verbose" ||
|
|
288
|
+
a === "--name"
|
|
289
|
+
) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
if (i > 0 && arr[i - 1] === "--name") return false;
|
|
293
|
+
return true;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function readMcpDesiredStartFromCliArgs(args) {
|
|
297
|
+
const i = args.indexOf("--http");
|
|
298
|
+
if (i === -1) return null;
|
|
299
|
+
const next = args[i + 1];
|
|
300
|
+
if (next != null && /^\d+$/.test(String(next))) return Math.trunc(Number(next));
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function readMcpDesiredStart(args) {
|
|
305
|
+
const fromCli = readMcpDesiredStartFromCliArgs(args);
|
|
306
|
+
if (fromCli !== null) return fromCli;
|
|
307
|
+
const raw =
|
|
308
|
+
process.env.POKE_BROWSER_MCP_PORT ??
|
|
309
|
+
process.env.POKE_BROWSER_PORT ??
|
|
310
|
+
"8755";
|
|
311
|
+
const httpPort = Number(raw);
|
|
312
|
+
return Number.isFinite(httpPort) && httpPort > 0 && httpPort <= 65535
|
|
313
|
+
? Math.trunc(httpPort)
|
|
314
|
+
: 8755;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function readWsDesiredStart() {
|
|
318
|
+
const raw = process.env.POKE_BROWSER_WS_PORT ?? "9009";
|
|
319
|
+
const n = Number(raw);
|
|
320
|
+
return Number.isFinite(n) && n > 0 && n <= 65535 ? Math.trunc(n) : 9009;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function cloneArgsWithResolvedMcpPort(args, mcpPort) {
|
|
324
|
+
const out = [...args];
|
|
325
|
+
const i = out.indexOf("--http");
|
|
326
|
+
if (i !== -1 && out[i + 1] != null && /^\d+$/.test(String(out[i + 1]))) {
|
|
327
|
+
out[i + 1] = String(mcpPort);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function runBuild() {
|
|
333
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
334
|
+
const b = spawnSync(npm, ["run", "build"], {
|
|
335
|
+
cwd: root,
|
|
336
|
+
stdio: "inherit",
|
|
337
|
+
env: process.env,
|
|
338
|
+
});
|
|
339
|
+
if (b.error) {
|
|
340
|
+
console.error(b.error);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
if (b.status !== 0) {
|
|
344
|
+
process.exit(b.status ?? 1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (wantBuild) {
|
|
349
|
+
runBuild();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!existsSync(entry)) {
|
|
353
|
+
console.error(
|
|
354
|
+
"poke-browser: missing dist/. Run `npm run build` in this package, or pass `--build` once.",
|
|
355
|
+
);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
(async () => {
|
|
360
|
+
const mcpDesiredStart = readMcpDesiredStart(childArgs);
|
|
361
|
+
const wsDesiredStart = readWsDesiredStart();
|
|
362
|
+
const mcpPort = await findAvailablePort(mcpDesiredStart);
|
|
363
|
+
const wsPort = await findAvailablePort(wsDesiredStart);
|
|
364
|
+
const showMcpLine =
|
|
365
|
+
wantPokeTunnelFlow || childArgs.includes("--http");
|
|
366
|
+
const envWithPorts = {
|
|
367
|
+
...childEnv(),
|
|
368
|
+
POKE_BROWSER_MCP_PORT: String(mcpPort),
|
|
369
|
+
POKE_BROWSER_WS_PORT: String(wsPort),
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
printQuietStartupBanner({
|
|
373
|
+
mcpPort,
|
|
374
|
+
wsPort,
|
|
375
|
+
mcpDesiredStart,
|
|
376
|
+
wsDesiredStart,
|
|
377
|
+
showMcpLine,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Check for newer version (non-blocking)
|
|
381
|
+
(async () => {
|
|
382
|
+
try {
|
|
383
|
+
const https = await import("node:https");
|
|
384
|
+
const latest = await new Promise((resolve, reject) => {
|
|
385
|
+
const req = https.get(
|
|
386
|
+
"https://registry.npmjs.org/poke-browser/latest",
|
|
387
|
+
{ timeout: 3000 },
|
|
388
|
+
(res) => {
|
|
389
|
+
let data = "";
|
|
390
|
+
res.on("data", (d) => (data += d));
|
|
391
|
+
res.on("end", () => {
|
|
392
|
+
try {
|
|
393
|
+
resolve(JSON.parse(data).version);
|
|
394
|
+
} catch {
|
|
395
|
+
reject();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
req.on("error", reject);
|
|
401
|
+
req.on("timeout", () => {
|
|
402
|
+
req.destroy();
|
|
403
|
+
reject();
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
const current = pkg.version;
|
|
407
|
+
if (latest && latest !== current) {
|
|
408
|
+
console.log(
|
|
409
|
+
"\x1b[33m \u26a1 Update available: v" +
|
|
410
|
+
latest +
|
|
411
|
+
" (you have v" +
|
|
412
|
+
current +
|
|
413
|
+
")\x1b[0m",
|
|
414
|
+
);
|
|
415
|
+
console.log(
|
|
416
|
+
"\x1b[90m npx poke-browser@latest\x1b[0m\n",
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
} catch {}
|
|
420
|
+
})();
|
|
421
|
+
|
|
422
|
+
const resolvedChildArgs = cloneArgsWithResolvedMcpPort(childArgs, mcpPort);
|
|
423
|
+
|
|
424
|
+
if (wantPokeTunnelFlow) {
|
|
425
|
+
if (!ensurePokeLoginForTunnel()) {
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
console.error(color.green(" Signed in to Poke \u2014 starting HTTP MCP and tunnel."));
|
|
429
|
+
console.error(
|
|
430
|
+
color.dim(" Poke can use poke-browser while this window stays open."),
|
|
431
|
+
);
|
|
432
|
+
console.error("");
|
|
433
|
+
const r = spawnSync(
|
|
434
|
+
process.execPath,
|
|
435
|
+
[
|
|
436
|
+
entry,
|
|
437
|
+
"--http",
|
|
438
|
+
String(mcpPort),
|
|
439
|
+
"--tunnel",
|
|
440
|
+
...(customMcpName ? ["--name", customMcpName] : []),
|
|
441
|
+
...resolvedChildArgs,
|
|
442
|
+
],
|
|
443
|
+
{ stdio: "inherit", env: envWithPorts },
|
|
444
|
+
);
|
|
445
|
+
process.exit(r.status ?? 1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// stdio mode: Cursor / Claude Desktop (stdin is a pipe)
|
|
449
|
+
const r = spawnSync(process.execPath, [entry, ...resolvedChildArgs], {
|
|
450
|
+
stdio: "inherit",
|
|
451
|
+
env: envWithPorts,
|
|
452
|
+
});
|
|
453
|
+
process.exit(r.status ?? 1);
|
|
454
|
+
})().catch((err) => {
|
|
455
|
+
console.error(err);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// All server logs MUST go to stderr - stdout is reserved for MCP JSON-RPC.
|
|
2
|
+
// Each MCP tool call is logged to stderr from `registerTools()` in tools.ts.
|
|
3
|
+
import { logError } from "./logger.js";
|
|
4
|
+
import { main } from "./run.js";
|
|
5
|
+
main().catch((err) => {
|
|
6
|
+
logError("[poke-browser-mcp] Fatal startup error:", err);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,6EAA6E;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,QAAQ,CAAC,yCAAyC,EAAE,GAAG,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All server logs MUST go to stderr — stdout is reserved for MCP JSON-RPC over stdio.
|
|
3
|
+
* Quiet mode (default): only logError / logNotice print. Verbose: set POKE_BROWSER_VERBOSE=1
|
|
4
|
+
* (CLI --debug / --verbose).
|
|
5
|
+
*/
|
|
6
|
+
export declare function isVerbose(): boolean;
|
|
7
|
+
/** Verbose / debug logging only (--debug / --verbose). */
|
|
8
|
+
export declare function log(...args: unknown[]): void;
|
|
9
|
+
/** Always printed (fatal errors, port bind failures, security rejects). */
|
|
10
|
+
export declare function logError(...args: unknown[]): void;
|
|
11
|
+
/** Short user-facing lines in quiet mode (e.g. tunnel URL, browser connected). */
|
|
12
|
+
export declare function logNotice(...args: unknown[]): void;
|
|
13
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,0DAA0D;AAC1D,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAG5C;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAElD"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All server logs MUST go to stderr — stdout is reserved for MCP JSON-RPC over stdio.
|
|
3
|
+
* Quiet mode (default): only logError / logNotice print. Verbose: set POKE_BROWSER_VERBOSE=1
|
|
4
|
+
* (CLI --debug / --verbose).
|
|
5
|
+
*/
|
|
6
|
+
export function isVerbose() {
|
|
7
|
+
return process.env.POKE_BROWSER_VERBOSE === "1";
|
|
8
|
+
}
|
|
9
|
+
/** Verbose / debug logging only (--debug / --verbose). */
|
|
10
|
+
export function log(...args) {
|
|
11
|
+
if (!isVerbose())
|
|
12
|
+
return;
|
|
13
|
+
console.error(...args);
|
|
14
|
+
}
|
|
15
|
+
/** Always printed (fatal errors, port bind failures, security rejects). */
|
|
16
|
+
export function logError(...args) {
|
|
17
|
+
console.error(...args);
|
|
18
|
+
}
|
|
19
|
+
/** Short user-facing lines in quiet mode (e.g. tunnel URL, browser connected). */
|
|
20
|
+
export function logNotice(...args) {
|
|
21
|
+
console.error(...args);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG,CAAC;AAClD,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,GAAG,CAAC,GAAG,IAAe;IACpC,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,GAAG,IAAe;IACzC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,GAAG,IAAe;IAC1C,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACzB,CAAC"}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAwGA,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC;AAkBvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IACzC,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAqBA;AAuMD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAa1C"}
|