@attson/atwebpilot-mcp 0.0.78 → 0.0.80
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 +3 -3
- package/dist/index.js +138 -29
- package/dist/skill/SKILL.md +11 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,11 +17,11 @@ Claude Code:
|
|
|
17
17
|
**不需要手填端口。** 从 Plan 33 起是配对流程:
|
|
18
18
|
|
|
19
19
|
1. 会话启动时**不绑任何端口** —— 不碰网页的会话零副作用。
|
|
20
|
-
2. AI 第一次调 `list_tabs`
|
|
21
|
-
3.
|
|
20
|
+
2. AI 第一次调 `list_tabs` 时才绑一个空闲端口,通过 MCP URL elicitation 请宿主打开配对页;不支持该能力的宿主回退到系统默认浏览器。`list_tabs` 最多等待 45 秒。
|
|
21
|
+
3. 你在浏览器里点「允许」,扩展即接入;同一次 `list_tabs` 调用直接返回 tab,AI 无需让你回复「已连接」。只有等待超时才返回 `pairing_required + pair_url`;`pairing_status` 始终是立即返回的非阻塞状态检查。
|
|
22
22
|
4. 之后本机的会话都免确认(信任是安装级的);端口能复用时连配对页都不会弹。
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
若宿主和系统都未弹出浏览器,可在超时结果中打开 `pair_url`。授权成功只以扩展实际建立连接并发送 `HELLO` 为准;打开配对页本身不代表已连接。
|
|
25
25
|
|
|
26
26
|
多个 Codex / Claude Code 会话可以同时接入同一个浏览器,各自一条连接。可在扩展设置 →
|
|
27
27
|
Coordinator 查看已接入的会话、单独断开,或撤销信任。
|
package/dist/index.js
CHANGED
|
@@ -953,7 +953,7 @@ var CONTROL_TOOLS = [
|
|
|
953
953
|
},
|
|
954
954
|
{
|
|
955
955
|
name: "list_tabs",
|
|
956
|
-
description: "List browser tabs.
|
|
956
|
+
description: "List browser tabs. On first use, opens the pairing page and waits up to 45 seconds for authorization, normally returning connected tabs in the same call. Returns pairing_required with the URL only on timeout.",
|
|
957
957
|
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
958
958
|
},
|
|
959
959
|
{
|
|
@@ -2325,7 +2325,18 @@ function locate(source, offset) {
|
|
|
2325
2325
|
return { line, col };
|
|
2326
2326
|
}
|
|
2327
2327
|
|
|
2328
|
+
// src/pairing-errors.ts
|
|
2329
|
+
var PairingWaitTimeoutError = class extends Error {
|
|
2330
|
+
constructor(timeoutMs, pairUrl) {
|
|
2331
|
+
super(
|
|
2332
|
+
`\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u8D85\u65F6\uFF08${Math.ceil(timeoutMs / 1e3)} \u79D2\uFF09\u3002` + (pairUrl ? `\u8BF7\u6253\u5F00\u914D\u5BF9\u9875 ${pairUrl} \u5B8C\u6210\u6388\u6743\u540E\u91CD\u8BD5\u3002` : "")
|
|
2333
|
+
);
|
|
2334
|
+
this.name = "PairingWaitTimeoutError";
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2328
2338
|
// src/handlers.ts
|
|
2339
|
+
var LIST_TABS_PAIRING_TIMEOUT_MS = 45e3;
|
|
2329
2340
|
function singleWorkerId(c, pairUrl) {
|
|
2330
2341
|
const workers = c.workers.list();
|
|
2331
2342
|
if (workers.length === 0) {
|
|
@@ -2349,17 +2360,32 @@ async function handlePairingStatus(deps) {
|
|
|
2349
2360
|
if (!pairUrl) throw new Error("PAIRING_REQUIRED: \u914D\u5BF9\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u4F46\u914D\u5BF9\u9875\u5C1A\u4E0D\u53EF\u7528");
|
|
2350
2361
|
return { status: "pairing_required", pair_url: pairUrl };
|
|
2351
2362
|
}
|
|
2352
|
-
async function handleListTabs(deps) {
|
|
2363
|
+
async function handleListTabs(deps, onPairingRequired, timeoutMs = LIST_TABS_PAIRING_TIMEOUT_MS) {
|
|
2353
2364
|
const { coordinator } = await deps.ensure();
|
|
2354
2365
|
const workers = coordinator.workers.list();
|
|
2355
2366
|
if (workers.length > 1) {
|
|
2356
2367
|
throw new Error("\u68C0\u6D4B\u5230\u591A\u4E2A\u6D4F\u89C8\u5668\u8FDE\u5165\uFF1Bv1 \u4EC5\u652F\u6301\u5355 worker\uFF0C\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u8FDE\u63A5");
|
|
2357
2368
|
}
|
|
2358
|
-
|
|
2369
|
+
let worker = workers[0];
|
|
2359
2370
|
if (!worker) {
|
|
2360
2371
|
const pairUrl = deps.pairUrl();
|
|
2361
2372
|
if (!pairUrl) throw new Error("PAIRING_REQUIRED: \u914D\u5BF9\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u4F46\u914D\u5BF9\u9875\u5C1A\u4E0D\u53EF\u7528");
|
|
2362
|
-
|
|
2373
|
+
try {
|
|
2374
|
+
await deps.waitForWorker(timeoutMs, onPairingRequired);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
if (error instanceof PairingWaitTimeoutError) {
|
|
2377
|
+
return { status: "pairing_required", pair_url: pairUrl, tabs: [] };
|
|
2378
|
+
}
|
|
2379
|
+
throw error;
|
|
2380
|
+
}
|
|
2381
|
+
const connectedWorkers = coordinator.workers.list();
|
|
2382
|
+
if (connectedWorkers.length > 1) {
|
|
2383
|
+
throw new Error("\u68C0\u6D4B\u5230\u591A\u4E2A\u6D4F\u89C8\u5668\u8FDE\u5165\uFF1Bv1 \u4EC5\u652F\u6301\u5355 worker\uFF0C\u8BF7\u53EA\u4FDD\u7559\u4E00\u4E2A\u8FDE\u63A5");
|
|
2384
|
+
}
|
|
2385
|
+
worker = connectedWorkers[0];
|
|
2386
|
+
if (!worker) {
|
|
2387
|
+
return { status: "pairing_required", pair_url: pairUrl, tabs: [] };
|
|
2388
|
+
}
|
|
2363
2389
|
}
|
|
2364
2390
|
return { status: "connected", tabs: worker.available_tabs };
|
|
2365
2391
|
}
|
|
@@ -2460,16 +2486,16 @@ var SKILL_DESCRIPTION = "Strategy + scenarios + safety rails for driving the AtW
|
|
|
2460
2486
|
var TOPICS = {
|
|
2461
2487
|
quickstart: `# AtWebPilot quickstart
|
|
2462
2488
|
|
|
2463
|
-
1. Call list_tabs once.
|
|
2464
|
-
2.
|
|
2465
|
-
3.
|
|
2489
|
+
1. Call list_tabs once. On first use it opens the pairing page and waits for authorization, then normally returns connected tabs in the same call.
|
|
2490
|
+
2. If list_tabs times out and returns pairing_required, show pair_url and wait for browser approval. Use pairing_status for a later non-blocking status check; do not ask the user to reply just to continue.
|
|
2491
|
+
3. Choose a returned tab, then call open_session with its tab_id.
|
|
2466
2492
|
4. For interaction use browser_getPageInfo then browser_takeSnapshot; for broad reading use browser_createPageIndex.
|
|
2467
2493
|
5. Prefer purpose-built tools and verified results. Use browser_discoverTools only for capabilities outside the connected core surface. Its response always includes the selected schemas. If a newly enabled native tool is not visible in the current turn, call it immediately with browser_call; do not ask the user to send "continue".
|
|
2468
2494
|
6. Close the session when finished.
|
|
2469
2495
|
`,
|
|
2470
2496
|
pairing: `# AtWebPilot pairing
|
|
2471
2497
|
|
|
2472
|
-
list_tabs and
|
|
2498
|
+
list_tabs opens the pairing page when needed and waits up to 45 seconds for the extension's HELLO, so successful authorization normally returns connected tabs in the same call. pairing_status remains immediate and non-blocking. A pairing_required result from list_tabs means the bounded wait expired; show pair_url once and use pairing_status for a later check. Never ask the user to reply merely to continue, poll list_tabs, or inspect the pair-page HTML.
|
|
2473
2499
|
`,
|
|
2474
2500
|
reading: `# AtWebPilot reading
|
|
2475
2501
|
|
|
@@ -2543,7 +2569,7 @@ var SKILL_TOOL = {
|
|
|
2543
2569
|
// package.json
|
|
2544
2570
|
var package_default = {
|
|
2545
2571
|
name: "@attson/atwebpilot-mcp",
|
|
2546
|
-
version: "0.0.
|
|
2572
|
+
version: "0.0.80",
|
|
2547
2573
|
license: "Apache-2.0",
|
|
2548
2574
|
description: "MCP server for the atwebpilot browser extension - let Codex or Claude Code drive your browser.",
|
|
2549
2575
|
homepage: "https://github.com/attson/atwebpilot#readme",
|
|
@@ -2595,39 +2621,58 @@ var MCP_VERSION = package_default.version;
|
|
|
2595
2621
|
|
|
2596
2622
|
// src/pairing-presenter.ts
|
|
2597
2623
|
import { randomUUID } from "crypto";
|
|
2624
|
+
function isCodexClient(server) {
|
|
2625
|
+
return server.getClientVersion?.()?.name.toLowerCase().includes("codex") ?? false;
|
|
2626
|
+
}
|
|
2598
2627
|
function createPairingPresenter(server, fallbackOpenUrl) {
|
|
2599
2628
|
let presentedUrl = null;
|
|
2600
2629
|
const runFallback = async (url) => {
|
|
2601
|
-
if (!fallbackOpenUrl) return;
|
|
2630
|
+
if (!fallbackOpenUrl) return false;
|
|
2602
2631
|
try {
|
|
2603
2632
|
await fallbackOpenUrl(url);
|
|
2633
|
+
return true;
|
|
2604
2634
|
} catch (error) {
|
|
2605
|
-
presentedUrl = null;
|
|
2606
2635
|
console.error(
|
|
2607
2636
|
`[atwebpilot-mcp] failed to open pairing page ${url}:`,
|
|
2608
2637
|
error instanceof Error ? error.message : String(error)
|
|
2609
2638
|
);
|
|
2639
|
+
return false;
|
|
2610
2640
|
}
|
|
2611
2641
|
};
|
|
2612
|
-
|
|
2613
|
-
if (presentedUrl === url) return;
|
|
2614
|
-
presentedUrl = url;
|
|
2615
|
-
if (!server.getClientCapabilities()?.elicitation?.url) {
|
|
2616
|
-
void runFallback(url);
|
|
2617
|
-
return;
|
|
2618
|
-
}
|
|
2642
|
+
const requestHostPresentation = (url, fallbackOnFailure) => {
|
|
2619
2643
|
void server.elicitInput({
|
|
2620
2644
|
mode: "url",
|
|
2621
2645
|
message: "Open AtWebPilot in the browser to approve this pairing request.",
|
|
2622
2646
|
url,
|
|
2623
2647
|
elicitationId: `atwebpilot-pairing-${randomUUID()}`
|
|
2624
|
-
}).catch((error) => {
|
|
2648
|
+
}).catch(async (error) => {
|
|
2625
2649
|
if (!server.transport) return;
|
|
2626
2650
|
console.error(
|
|
2627
|
-
"[atwebpilot-mcp] URL elicitation failed; falling back to the system browser:",
|
|
2651
|
+
fallbackOnFailure ? "[atwebpilot-mcp] URL elicitation failed; falling back to the system browser:" : "[atwebpilot-mcp] URL elicitation failed:",
|
|
2628
2652
|
error instanceof Error ? error.message : String(error)
|
|
2629
2653
|
);
|
|
2630
|
-
|
|
2654
|
+
if (fallbackOnFailure && await runFallback(url)) return;
|
|
2655
|
+
presentedUrl = null;
|
|
2656
|
+
});
|
|
2657
|
+
};
|
|
2658
|
+
return (url) => {
|
|
2659
|
+
if (presentedUrl === url) return;
|
|
2660
|
+
presentedUrl = url;
|
|
2661
|
+
const supportsUrlElicitation = Boolean(server.getClientCapabilities()?.elicitation?.url);
|
|
2662
|
+
if (isCodexClient(server)) {
|
|
2663
|
+
void runFallback(url).then((opened) => {
|
|
2664
|
+
if (opened || !server.transport) return;
|
|
2665
|
+
if (supportsUrlElicitation) requestHostPresentation(url, false);
|
|
2666
|
+
else presentedUrl = null;
|
|
2667
|
+
});
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
if (supportsUrlElicitation) {
|
|
2671
|
+
requestHostPresentation(url, true);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
void runFallback(url).then((opened) => {
|
|
2675
|
+
if (!opened) presentedUrl = null;
|
|
2631
2676
|
});
|
|
2632
2677
|
};
|
|
2633
2678
|
}
|
|
@@ -2725,9 +2770,8 @@ async function dispatchCall(deps, name, args, state = DEFAULT_STATE, onListChang
|
|
|
2725
2770
|
return ok(result);
|
|
2726
2771
|
}
|
|
2727
2772
|
if (name === "list_tabs") {
|
|
2728
|
-
const result = await handleListTabs(deps);
|
|
2773
|
+
const result = await handleListTabs(deps, onPairingRequired);
|
|
2729
2774
|
if (result.status === "connected") await revealBrowserSurface(state, onListChanged);
|
|
2730
|
-
else onPairingRequired?.(result.pair_url);
|
|
2731
2775
|
return ok(result);
|
|
2732
2776
|
}
|
|
2733
2777
|
if (name === "open_session") {
|
|
@@ -2800,6 +2844,9 @@ function createMcpServer(deps, state = DEFAULT_STATE, options = {}) {
|
|
|
2800
2844
|
return server;
|
|
2801
2845
|
}
|
|
2802
2846
|
|
|
2847
|
+
// src/ensure-hub.ts
|
|
2848
|
+
import { readFile } from "fs/promises";
|
|
2849
|
+
|
|
2803
2850
|
// src/loopback-ws-hub.ts
|
|
2804
2851
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2805
2852
|
import {
|
|
@@ -3418,6 +3465,14 @@ function createHubEnsurer(d) {
|
|
|
3418
3465
|
);
|
|
3419
3466
|
}
|
|
3420
3467
|
if (pairUrlValue) await onWaiting?.(pairUrlValue);
|
|
3468
|
+
const connectedAfterPresentation = connectedWorkerId(coordinator);
|
|
3469
|
+
if (connectedAfterPresentation) return connectedAfterPresentation;
|
|
3470
|
+
if (pendingDenial) {
|
|
3471
|
+
pendingDenial = false;
|
|
3472
|
+
throw new Error(
|
|
3473
|
+
"\u7528\u6237\u62D2\u7EDD\u4E86\u6D4F\u89C8\u5668\u914D\u5BF9\u6388\u6743\u3002" + (pairUrlValue ? `\u914D\u5BF9\u9875\uFF1A${pairUrlValue}` : "")
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3421
3476
|
if (workerWait) return workerWait.promise;
|
|
3422
3477
|
let resolve;
|
|
3423
3478
|
let reject;
|
|
@@ -3427,9 +3482,7 @@ function createHubEnsurer(d) {
|
|
|
3427
3482
|
});
|
|
3428
3483
|
const timer = setTimeout(() => {
|
|
3429
3484
|
finishWorkerWait({
|
|
3430
|
-
error: new
|
|
3431
|
-
`\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u8D85\u65F6\uFF08${Math.ceil(timeoutMs / 1e3)} \u79D2\uFF09\u3002` + (pairUrlValue ? `\u8BF7\u6253\u5F00\u914D\u5BF9\u9875 ${pairUrlValue} \u5B8C\u6210\u6388\u6743\u540E\u91CD\u8BD5\u3002` : "")
|
|
3432
|
-
)
|
|
3485
|
+
error: new PairingWaitTimeoutError(timeoutMs, pairUrlValue)
|
|
3433
3486
|
});
|
|
3434
3487
|
}, timeoutMs);
|
|
3435
3488
|
timer.unref?.();
|
|
@@ -3444,6 +3497,57 @@ function createHubEnsurer(d) {
|
|
|
3444
3497
|
bound: () => bundle != null
|
|
3445
3498
|
};
|
|
3446
3499
|
}
|
|
3500
|
+
var DESKTOP_ENV_KEYS = [
|
|
3501
|
+
"DISPLAY",
|
|
3502
|
+
"WAYLAND_DISPLAY",
|
|
3503
|
+
"XDG_RUNTIME_DIR",
|
|
3504
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
3505
|
+
"XAUTHORITY"
|
|
3506
|
+
];
|
|
3507
|
+
function processId(field, status) {
|
|
3508
|
+
const match = new RegExp(`^${field}:\\s+(\\d+)`, "m").exec(status);
|
|
3509
|
+
if (!match) return null;
|
|
3510
|
+
const value = Number(match[1]);
|
|
3511
|
+
return Number.isSafeInteger(value) ? value : null;
|
|
3512
|
+
}
|
|
3513
|
+
function copyDesktopVariables(target, serialized) {
|
|
3514
|
+
for (const entry of serialized.split("\0")) {
|
|
3515
|
+
const separator = entry.indexOf("=");
|
|
3516
|
+
if (separator <= 0) continue;
|
|
3517
|
+
const key = entry.slice(0, separator);
|
|
3518
|
+
if (!DESKTOP_ENV_KEYS.includes(key) || target[key]) continue;
|
|
3519
|
+
const value = entry.slice(separator + 1);
|
|
3520
|
+
if (value) target[key] = value;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function resolveDesktopOpenEnvironment(options = {}) {
|
|
3524
|
+
const environment = { ...options.baseEnv ?? process.env };
|
|
3525
|
+
if ((options.platform ?? process.platform) !== "linux") return environment;
|
|
3526
|
+
const expectedUid = options.uid ?? process.getuid?.();
|
|
3527
|
+
const readTextFile = options.readTextFile ?? ((path) => readFile(path, "utf8"));
|
|
3528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3529
|
+
let pid = options.parentPid ?? process.ppid;
|
|
3530
|
+
const maxDepth = options.maxDepth ?? 12;
|
|
3531
|
+
for (let depth = 0; depth < maxDepth && pid > 1 && !seen.has(pid); depth += 1) {
|
|
3532
|
+
seen.add(pid);
|
|
3533
|
+
let status;
|
|
3534
|
+
try {
|
|
3535
|
+
status = await readTextFile(`/proc/${pid}/status`);
|
|
3536
|
+
} catch {
|
|
3537
|
+
break;
|
|
3538
|
+
}
|
|
3539
|
+
const ownerUid = processId("Uid", status);
|
|
3540
|
+
if (expectedUid !== void 0 && ownerUid !== expectedUid) break;
|
|
3541
|
+
try {
|
|
3542
|
+
copyDesktopVariables(environment, await readTextFile(`/proc/${pid}/environ`));
|
|
3543
|
+
} catch {
|
|
3544
|
+
}
|
|
3545
|
+
const parentPid = processId("PPid", status);
|
|
3546
|
+
if (parentPid === null || parentPid === pid) break;
|
|
3547
|
+
pid = parentPid;
|
|
3548
|
+
}
|
|
3549
|
+
return environment;
|
|
3550
|
+
}
|
|
3447
3551
|
function openCommands(url) {
|
|
3448
3552
|
if (process.platform === "darwin") return [{ command: "open", args: [url] }];
|
|
3449
3553
|
if (process.platform === "win32") {
|
|
@@ -3457,10 +3561,14 @@ function openCommands(url) {
|
|
|
3457
3561
|
{ command: "gio", args: ["open", url] }
|
|
3458
3562
|
];
|
|
3459
3563
|
}
|
|
3460
|
-
async function runOpenCommand({ command, args }) {
|
|
3564
|
+
async function runOpenCommand({ command, args }, environment) {
|
|
3461
3565
|
const { spawn } = await import("child_process");
|
|
3462
3566
|
await new Promise((resolve, reject) => {
|
|
3463
|
-
const child = spawn(command, args, {
|
|
3567
|
+
const child = spawn(command, args, {
|
|
3568
|
+
stdio: "ignore",
|
|
3569
|
+
detached: true,
|
|
3570
|
+
env: environment
|
|
3571
|
+
});
|
|
3464
3572
|
let settled = false;
|
|
3465
3573
|
const finish = (error) => {
|
|
3466
3574
|
if (settled) return;
|
|
@@ -3483,9 +3591,10 @@ async function runOpenCommand({ command, args }) {
|
|
|
3483
3591
|
}
|
|
3484
3592
|
async function defaultOpenUrl(url) {
|
|
3485
3593
|
const errors = [];
|
|
3594
|
+
const environment = await resolveDesktopOpenEnvironment();
|
|
3486
3595
|
for (const candidate of openCommands(url)) {
|
|
3487
3596
|
try {
|
|
3488
|
-
await runOpenCommand(candidate);
|
|
3597
|
+
await runOpenCommand(candidate, environment);
|
|
3489
3598
|
return;
|
|
3490
3599
|
} catch (error) {
|
|
3491
3600
|
errors.push(error instanceof Error ? error.message : String(error));
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -13,8 +13,9 @@ When this skill is loaded, you can drive any open tab through these tools
|
|
|
13
13
|
### Control plane
|
|
14
14
|
|
|
15
15
|
- `pairing_status` — start/check pairing without waiting. When it reports
|
|
16
|
-
`pairing_required`, show `pair_url` once
|
|
17
|
-
- `list_tabs` —
|
|
16
|
+
`pairing_required`, show `pair_url` once; use it only as a later status check.
|
|
17
|
+
- `list_tabs` — on first use, open the pairing page and wait up to 45 seconds
|
|
18
|
+
for browser authorization, normally returning tabs from the same call. Entries carry `busy`
|
|
18
19
|
and `busy_label` when **another** Claude Code session is already driving that
|
|
19
20
|
tab, and `mine` for tabs you hold. This is advisory — nothing blocks you — but
|
|
20
21
|
contending for a tab another agent is typing into is rarely what you want.
|
|
@@ -87,7 +88,7 @@ compatibility path; use the native tool once the host exposes it.
|
|
|
87
88
|
|
|
88
89
|
## Recommended flow
|
|
89
90
|
|
|
90
|
-
1. **先建立连接**:调一次 `list_tabs
|
|
91
|
+
1. **先建立连接**:调一次 `list_tabs`;首次配对时它会打开授权页并等待最多 45 秒,授权成功后同一次调用直接返回 tab,不要让用户回复「已连接」。只有超时才会返回 `pairing_required`;此时展示 `pair_url`,稍后用非阻塞的 `pairing_status` 检查,不要轮询 `list_tabs`。
|
|
91
92
|
2. **探查先于操作**:每次进入新页面,先 `getPageInfo` 确认位置,再 `takeSnapshot`(要点击/填表)或 `createPageIndex`(要读内容/抽字段);`snapshotDOM` / `querySelector` 属于 legacy-dom 组,只在需要分析整页结构时通过 `browser_discoverTools` 启用。
|
|
92
93
|
3. **最少充分验证**:危险或不可逆操作保持单步;多个独立字段用 `fillForm`。工具已返回 `verified` 或明确后置状态时,不要再用 `runJS` 重复验证。
|
|
93
94
|
4. **dangerous 工具会被人工审核**:调用前用 `extractText` 给用户看上下文,让审批更顺。
|
|
@@ -260,12 +261,13 @@ not the whole history.
|
|
|
260
261
|
Several Claude Code sessions can be attached to the same browser at once, each
|
|
261
262
|
through its own connection. Two consequences worth knowing:
|
|
262
263
|
|
|
263
|
-
- **Pairing
|
|
264
|
-
confirmation page,
|
|
265
|
-
`
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Once approved, later sessions on that
|
|
264
|
+
- **Pairing continues in the same call.** `list_tabs` lazily binds the port,
|
|
265
|
+
opens a confirmation page, and waits up to 45 seconds for the extension's
|
|
266
|
+
`HELLO`. Successful approval returns connected tabs directly; do not ask the
|
|
267
|
+
user to reply just to continue. On timeout it returns `pairing_required` with
|
|
268
|
+
`pair_url`; use non-blocking `pairing_status` for a later check. Never inspect
|
|
269
|
+
the pair-page HTML or poll `list_tabs`. Once approved, later sessions on that
|
|
270
|
+
machine connect silently.
|
|
269
271
|
- **Pick a free tab.** `list_tabs` marks tabs held by other sessions as `busy`.
|
|
270
272
|
Prefer an unowned tab, or open your own — two agents typing into one form
|
|
271
273
|
produces failures that look like your own tool calls not working.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attson/atwebpilot-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.80",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "MCP server for the atwebpilot browser extension - let Codex or Claude Code drive your browser.",
|
|
6
6
|
"homepage": "https://github.com/attson/atwebpilot#readme",
|