@attson/atwebpilot-mcp 0.0.68 → 0.0.70

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +107 -30
  3. 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` / `browser_*` 时才绑一个空闲端口,并自动打开配对页。
20
+ 2. AI 第一次调 `list_tabs` / `browser_*` 时才绑一个空闲端口,尝试用系统默认浏览器打开配对页,并立即在 MCP 进度消息中显示配对 URL。
21
21
  3. 你在浏览器里点「允许」,扩展即接入;正在等待的调用会自动继续,无需告诉 AI 授权成功或手动重试。
22
22
  4. 之后本机的会话都免确认(信任是安装级的);端口能复用时连配对页都不会弹。
23
23
 
24
- 首次调用默认等待授权 90 秒。拒绝或超时会返回明确错误和配对页地址;授权成功只以扩展实际建立连接并发送 `HELLO` 为准。
24
+ 首次调用默认等待授权 90 秒。若系统未弹出浏览器,可直接打开进度消息中的 URL;拒绝或超时也会返回明确错误和配对页地址。授权成功只以扩展实际建立连接并发送 `HELLO` 为准。
25
25
 
26
26
  多个 Codex / Claude Code 会话可以同时接入同一个浏览器,各自一条连接。可在扩展设置 →
27
27
  Coordinator 查看已接入的会话、单独断开,或撤销信任。
package/dist/index.js CHANGED
@@ -932,7 +932,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
932
932
  var CONTROL_TOOLS = [
933
933
  {
934
934
  name: "list_tabs",
935
- description: "\u5217\u51FA\u5F53\u524D\u8FDE\u5165\u7684\u6D4F\u89C8\u5668\uFF08worker\uFF09\u53EF\u7528\u7684\u6807\u7B7E\u9875\uFF1A[{tab_id,url,title}]\u3002\u5148\u8C03\u5B83\u62FF tab_id\u3002",
935
+ description: "\u5217\u51FA\u6D4F\u89C8\u5668\u53EF\u7528\u6807\u7B7E\u9875\uFF1A[{tab_id,url,title}]\u3002\u82E5\u6269\u5C55\u5C1A\u672A\u8FDE\u63A5\uFF0C\u9996\u6B21\u8C03\u7528\u4F1A\u5C1D\u8BD5\u81EA\u52A8\u6253\u5F00\u914D\u5BF9\u9875\u5E76\u7B49\u5F85\u6388\u6743\uFF08\u6700\u591A 90 \u79D2\uFF09\uFF0C\u7B49\u5F85\u671F\u95F4\u4F1A\u901A\u77E5\u5B9E\u9645\u914D\u5BF9 URL\u3002\u5148\u8C03\u5B83\u62FF tab_id\u3002",
936
936
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
937
937
  },
938
938
  {
@@ -1312,7 +1312,7 @@ var TOOL_DEFS = [
1312
1312
  },
1313
1313
  {
1314
1314
  name: "runJS",
1315
- description: "[LAST RESORT\xB7DANGER] \u5728 MAIN world \u6CE8\u5165\u5E76\u6267\u884C async \u51FD\u6570\u4F53\uFF08receives `ctx` = bindings\uFF09\u3002\u5FC5\u987B return \u503C\u3002\n**\u4EC5\u5728\u7ED3\u6784\u5316\u5DE5\u5177\u4E0D\u591F\u7528\u65F6\u4F7F\u7528**\u2014\u2014\u4F1A\u7ECF\u8FC7\u9759\u6001\u626B\u63CF\u4E0E\u4EBA\u5DE5\u5BA1\u9605\u3002",
1315
+ description: "[LAST RESORT\xB7DANGER] \u5728 MAIN world \u6CE8\u5165\u5E76\u6267\u884C async \u51FD\u6570\u4F53\uFF08receives `ctx` = bindings\uFF09\u3002\u5FC5\u987B return \u503C\u3002\n**\u4EC5\u5728\u7ED3\u6784\u5316\u5DE5\u5177\u4E0D\u591F\u7528\u65F6\u4F7F\u7528**\u2014\u2014\u4F1A\u7ECF\u8FC7\u9759\u6001\u626B\u63CF\u4E0E\u4EBA\u5DE5\u5BA1\u9605\u3002\u4E25\u683C CSP \u9875\u9762\u9700\u8981\u7528\u6237\u9884\u5148\u5728\u6269\u5C55\u8BBE\u7F6E\u4E2D\u5F00\u542F CDP/debugger\uFF1B\u8BFB\u53D6\u6837\u5F0F\u3001\u4F4D\u7F6E\u6216\u7956\u5148\u94FE\u8BF7\u4F18\u5148\u4F7F\u7528 inspectElement\u3002",
1316
1316
  input_schema: {
1317
1317
  type: "object",
1318
1318
  properties: {
@@ -1993,16 +1993,16 @@ function locate(source, offset) {
1993
1993
  }
1994
1994
 
1995
1995
  // src/handlers.ts
1996
- async function handleListTabs(deps) {
1996
+ async function handleListTabs(deps, onPairingRequired) {
1997
1997
  const { coordinator } = await deps.ensure();
1998
- const workerId = await deps.waitForWorker();
1998
+ const workerId = await deps.waitForWorker(void 0, onPairingRequired);
1999
1999
  const w = coordinator.workers.get(workerId);
2000
2000
  if (!w) throw new Error(`worker ${workerId} disconnected while opening browser context`);
2001
2001
  return { tabs: w.available_tabs };
2002
2002
  }
2003
- async function handleOpenSession(deps, args) {
2003
+ async function handleOpenSession(deps, args, onPairingRequired) {
2004
2004
  const { coordinator } = await deps.ensure();
2005
- const worker_id = await deps.waitForWorker();
2005
+ const worker_id = await deps.waitForWorker(void 0, onPairingRequired);
2006
2006
  const tab_id = String(args.tab_id);
2007
2007
  const requested = Array.isArray(args.capabilities) ? args.capabilities.map(String).filter(isCapability) : [];
2008
2008
  const scope = new Set(requested.length ? requested : CAPABILITIES);
@@ -2181,14 +2181,14 @@ function toolResult(gen, data) {
2181
2181
  return { content: [{ type: "image", data: d.data, mimeType }] };
2182
2182
  }
2183
2183
  var fail2 = (message) => ({ content: [{ type: "text", text: message }], isError: true });
2184
- async function dispatchCall(deps, name, args) {
2184
+ async function dispatchCall(deps, name, args, onPairingRequired) {
2185
2185
  try {
2186
2186
  if (name === SKILL_TOOL.name) {
2187
2187
  const bundle = readSkillBundle();
2188
2188
  return { content: [{ type: "text", text: bundle.content }] };
2189
2189
  }
2190
- if (name === "list_tabs") return ok(await handleListTabs(deps));
2191
- if (name === "open_session") return ok(await handleOpenSession(deps, args));
2190
+ if (name === "list_tabs") return ok(await handleListTabs(deps, onPairingRequired));
2191
+ if (name === "open_session") return ok(await handleOpenSession(deps, args, onPairingRequired));
2192
2192
  if (name === "close_session") return ok(await handleCloseSession(deps, args));
2193
2193
  if (name === "get_quota") return ok(await handleGetQuota(deps, args));
2194
2194
  const gen = BROWSER_BY_NAME.get(name);
@@ -2199,11 +2199,33 @@ async function dispatchCall(deps, name, args) {
2199
2199
  }
2200
2200
  }
2201
2201
  function createMcpServer(deps) {
2202
- const server = new Server({ name: "atwebpilot-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
2202
+ const server = new Server(
2203
+ { name: "atwebpilot-mcp", version: "0.1.0" },
2204
+ { capabilities: { tools: {}, logging: {} } }
2205
+ );
2203
2206
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: buildToolList(deps) }));
2204
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
2207
+ server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
2205
2208
  const args = req.params.arguments ?? {};
2206
- return dispatchCall(deps, req.params.name, args);
2209
+ const onPairingRequired = async (url) => {
2210
+ const message = `\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\uFF08\u6700\u591A 90 \u79D2\uFF09\u3002\u914D\u5BF9\u9875\uFF1A${url}\u3002\u5DF2\u5C1D\u8BD5\u81EA\u52A8\u6253\u5F00\uFF1B\u82E5\u672A\u5F39\u51FA\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u3002`;
2211
+ try {
2212
+ const progressToken = extra._meta?.progressToken;
2213
+ if (progressToken != null) {
2214
+ await extra.sendNotification({
2215
+ method: "notifications/progress",
2216
+ params: { progressToken, progress: 0, total: 1, message }
2217
+ });
2218
+ } else {
2219
+ await server.sendLoggingMessage({ level: "info", logger: "atwebpilot", data: message });
2220
+ }
2221
+ } catch (error) {
2222
+ console.error(
2223
+ "[atwebpilot-mcp] failed to report pairing URL:",
2224
+ error instanceof Error ? error.message : String(error)
2225
+ );
2226
+ }
2227
+ };
2228
+ return dispatchCall(deps, req.params.name, args, onPairingRequired);
2207
2229
  });
2208
2230
  return server;
2209
2231
  }
@@ -2695,6 +2717,7 @@ function createHubEnsurer(d) {
2695
2717
  let inflight = null;
2696
2718
  let pairUrlValue = null;
2697
2719
  let opened = false;
2720
+ let opening = null;
2698
2721
  let hubIsClosed = false;
2699
2722
  let pendingDenial = false;
2700
2723
  let workerWait = null;
@@ -2773,20 +2796,33 @@ function createHubEnsurer(d) {
2773
2796
  };
2774
2797
  }
2775
2798
  }
2776
- async function ensure() {
2777
- if (bundle) return bundle;
2778
- inflight ??= bind().then((b2) => {
2779
- bundle = b2;
2780
- return b2;
2781
- });
2782
- const b = await inflight;
2783
- if (!opened) {
2799
+ async function openPairPage() {
2800
+ if (opened || !pairUrlValue || !d.openUrl) return;
2801
+ const url = pairUrlValue;
2802
+ opening ??= Promise.resolve().then(() => d.openUrl(url)).then(() => {
2784
2803
  opened = true;
2785
- if (pairUrlValue) d.openUrl?.(pairUrlValue);
2804
+ }).catch((error) => {
2805
+ console.error(
2806
+ `[atwebpilot-mcp] failed to open pairing page ${url}:`,
2807
+ error instanceof Error ? error.message : String(error)
2808
+ );
2809
+ }).finally(() => {
2810
+ opening = null;
2811
+ });
2812
+ await opening;
2813
+ }
2814
+ async function ensure() {
2815
+ if (!bundle) {
2816
+ inflight ??= bind().then((b) => {
2817
+ bundle = b;
2818
+ return b;
2819
+ });
2820
+ await inflight;
2786
2821
  }
2787
- return b;
2822
+ await openPairPage();
2823
+ return bundle;
2788
2824
  }
2789
- async function waitForWorker(timeoutMs = DEFAULT_WORKER_WAIT_TIMEOUT_MS) {
2825
+ async function waitForWorker(timeoutMs = DEFAULT_WORKER_WAIT_TIMEOUT_MS, onWaiting) {
2790
2826
  const { coordinator } = await ensure();
2791
2827
  if (hubIsClosed) throw new Error("\u6D4F\u89C8\u5668\u914D\u5BF9\u670D\u52A1\u5DF2\u5173\u95ED\u3002");
2792
2828
  const connected = connectedWorkerId(coordinator);
@@ -2797,6 +2833,7 @@ function createHubEnsurer(d) {
2797
2833
  "\u7528\u6237\u62D2\u7EDD\u4E86\u6D4F\u89C8\u5668\u914D\u5BF9\u6388\u6743\u3002" + (pairUrlValue ? `\u914D\u5BF9\u9875\uFF1A${pairUrlValue}` : "")
2798
2834
  );
2799
2835
  }
2836
+ if (pairUrlValue) await onWaiting?.(pairUrlValue);
2800
2837
  if (workerWait) return workerWait.promise;
2801
2838
  let resolve;
2802
2839
  let reject;
@@ -2807,7 +2844,7 @@ function createHubEnsurer(d) {
2807
2844
  const timer = setTimeout(() => {
2808
2845
  finishWorkerWait({
2809
2846
  error: new Error(
2810
- `\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u8D85\u65F6\uFF08${Math.ceil(timeoutMs / 1e3)} \u79D2\uFF09\u3002` + (pairUrlValue ? `\u8BF7\u5728\u5DF2\u6253\u5F00\u7684\u914D\u5BF9\u9875 ${pairUrlValue} \u5B8C\u6210\u6388\u6743\u540E\u91CD\u8BD5\u3002` : "")
2847
+ `\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` : "")
2811
2848
  )
2812
2849
  });
2813
2850
  }, timeoutMs);
@@ -2823,14 +2860,54 @@ function createHubEnsurer(d) {
2823
2860
  bound: () => bundle != null
2824
2861
  };
2825
2862
  }
2826
- function defaultOpenUrl(url) {
2827
- void import("child_process").then(({ spawn }) => {
2828
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2863
+ function openCommands(url) {
2864
+ if (process.platform === "darwin") return [{ command: "open", args: [url] }];
2865
+ if (process.platform === "win32") {
2866
+ return [{ command: "cmd.exe", args: ["/d", "/s", "/c", "start", "", url] }];
2867
+ }
2868
+ if (process.env.WSL_DISTRO_NAME) {
2869
+ return [{ command: "cmd.exe", args: ["/c", "start", "", url] }];
2870
+ }
2871
+ return [
2872
+ { command: "xdg-open", args: [url] },
2873
+ { command: "gio", args: ["open", url] }
2874
+ ];
2875
+ }
2876
+ async function runOpenCommand({ command, args }) {
2877
+ const { spawn } = await import("child_process");
2878
+ await new Promise((resolve, reject) => {
2879
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
2880
+ let settled = false;
2881
+ const finish = (error) => {
2882
+ if (settled) return;
2883
+ settled = true;
2884
+ clearTimeout(timer);
2885
+ if (error) reject(error);
2886
+ else resolve();
2887
+ };
2888
+ const timer = setTimeout(() => {
2889
+ child.unref();
2890
+ finish();
2891
+ }, 3e3);
2892
+ timer.unref?.();
2893
+ child.once("error", (error) => finish(error));
2894
+ child.once("exit", (code, signal) => {
2895
+ if (code === 0) finish();
2896
+ else finish(new Error(`${command} exited with ${code ?? signal ?? "unknown status"}`));
2897
+ });
2898
+ });
2899
+ }
2900
+ async function defaultOpenUrl(url) {
2901
+ const errors = [];
2902
+ for (const candidate of openCommands(url)) {
2829
2903
  try {
2830
- spawn(cmd, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref();
2831
- } catch {
2904
+ await runOpenCommand(candidate);
2905
+ return;
2906
+ } catch (error) {
2907
+ errors.push(error instanceof Error ? error.message : String(error));
2832
2908
  }
2833
- });
2909
+ }
2910
+ throw new Error(errors.join("; ") || "no browser opener available");
2834
2911
  }
2835
2912
 
2836
2913
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attson/atwebpilot-mcp",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
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",