@ada-mcp/mcp-server 0.1.14 → 0.1.15

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 +139 -139
  2. package/dist/cli.cjs +278 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,139 +1,139 @@
1
- # @ada-mcp/mcp-server
2
-
3
- ADA MCP server package that supports:
4
-
5
- - Local stdio mode (default) for MCP hosts
6
- - Remote HTTP mode (`server`) with API key authentication
7
- - MCP **Streamable HTTP** on `POST|GET|DELETE /mcp` (same port as legacy REST), with optional SSE per MCP spec (`@modelcontextprotocol/sdk` transport)
8
-
9
- ## 标准安装(Cursor / MCP)
10
-
11
- 请使用 **`@ada-mcp/launcher@0.1.11`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
12
-
13
- ```json
14
- {
15
- "mcpServers": {
16
- "ada-mcp": {
17
- "command": "pnpm",
18
- "args": ["dlx", "@ada-mcp/launcher@0.1.11"]
19
- }
20
- }
21
- }
22
- ```
23
-
24
- 本包版本:**`@ada-mcp/mcp-server@0.1.14`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
25
-
26
- 直接调试本包(无 launcher 拉包前测速):
27
-
28
- ```bash
29
- pnpm dlx @ada-mcp/mcp-server@0.1.14
30
- # npx 等价:
31
- npx -y @ada-mcp/mcp-server@0.1.14
32
- ```
33
-
34
- ## 启动时自动安装依赖(默认仅 Playwright)
35
-
36
- 进程启动前会按配置自动执行 `install-deps`(日志在 stderr):
37
-
38
- | 配置 | 含义 |
39
- |------|------|
40
- | (未配置) | 仅安装 **Playwright + 浏览器** |
41
- | `playwright` | 仅 Playwright(显式写法,与默认相同) |
42
- | `selenium` | **仅** Selenium 原生驱动(GeckoDriver/ChromeDriver) |
43
- | `appium` | **仅** Appium 包 + 移动端驱动 |
44
- | `playwright,selenium` | 组合(逗号连接多类) |
45
- | `all` | 上述全部 |
46
- | `none` / `skip` | 不自动安装 |
47
-
48
- **环境变量**
49
-
50
- - `ADA_MCP_INSTALL_DEPS`:范围,如 `playwright`、`playwright,selenium`、`all`、`none`
51
- - `ADA_MCP_SKIP_INSTALL_DEPS=1`:跳过自动安装
52
- - `ADA_MCP_INSTALL_DEPS_FORCE=1`:强制重装
53
- - `ADA_MCP_GECKODRIVER_VERSION` / `ADA_MCP_CHROMEDRIVER_VERSION`:Selenium 驱动版本
54
- - `ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS`:浏览器下载超时(默认 15 分钟)
55
- - `ADA_INSTALL_STRATEGY_TIMEOUT_MS`:npm 装包超时(默认 2 分钟)
56
-
57
- ## 代理与镜像(`0.1.10+` 推荐)
58
-
59
- | 阶段 | 自动探测最快镜像 |
60
- |------|------------------|
61
- | `pnpm dlx @ada-mcp/launcher` | **是** — 拉包前测速(推荐) |
62
- | `pnpm dlx @ada-mcp/mcp-server` | tarball 仍走本机源;**同次安装的依赖**由 `preinstall` 测速(仅写入官方 Playwright CDN,`0.1.9+`) |
63
- | 启动后 `install-deps` | 是 — 内置国内镜像测速(**无需配置**) |
64
-
65
- 默认 npm 探测候选(按优先级,延迟相同取靠前):阿里云 npmmirror → 腾讯云 → 华为云 → npm 官方。
66
-
67
- | 变量 | 说明 |
68
- |------|------|
69
- | `npm_config_registry` | 可选;仅加速 **dlx** 拉包(推荐 `https://registry.npmmirror.com`) |
70
- | `ADA_REGISTRY_CANDIDATES` | 可选;在默认五镜像**之外**追加候选 |
71
- | `ADA_NPM_PROXY_REGISTRY` / `ADA_PNPM_PROXY_REGISTRY` | 可选;覆盖探测主候选(默认 npmmirror) |
72
- | `PLAYWRIGHT_DOWNLOAD_HOST` | 可选;Playwright 浏览器 CDN |
73
-
74
- 详见 [ADA-MCP-接入手册 §3.9.2](../../docs/ADA-MCP-接入手册.md#392-代理与镜像配置)。
75
-
76
- **CLI 参数**(写在 MCP `args` 末尾)
77
-
78
- - `--install-deps=playwright,selenium`
79
- - `--skip-install-deps`
80
- - `--install-deps-force`
81
- - `--geckodriver-version=latest` `--chromedriver-version=match-chrome`
82
-
83
- 在标准 `args` 后追加,例如安装全部依赖:
84
-
85
- ```json
86
- "args": ["dlx", "@ada-mcp/launcher@0.1.11", "--install-deps=all"]
87
- ```
88
-
89
- ## Cursor MCP 配置
90
-
91
- **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.11`
92
-
93
- **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.11`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
94
-
95
- ```json
96
- {
97
- "mcpServers": {
98
- "ada-mcp": {
99
- "command": "npx",
100
- "args": ["-y", "@ada-mcp/launcher@0.1.11"]
101
- }
102
- }
103
- }
104
- ```
105
-
106
- Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.14`(无 launcher 拉包测速)。
107
-
108
- ## Remote mode
109
-
110
- Set API key in environment variable first:
111
-
112
- ```bash
113
- export ADA_MCP_REMOTE_API_KEY=your_token
114
- ```
115
-
116
- Windows PowerShell:
117
-
118
- ```powershell
119
- $env:ADA_MCP_REMOTE_API_KEY="your_token"
120
- ```
121
-
122
- Then run:
123
-
124
- ```bash
125
- pnpm dlx @ada-mcp/mcp-server server --host=127.0.0.1 --port=8787 --allow-risky=true --risky-mode=whitelist --risky-commands=custom
126
- ```
127
-
128
- ### Streamable HTTP (`/mcp`)
129
-
130
- - Endpoint: `http://<host>:<port>/mcp` — same API key headers as below (`x-api-key` or `Authorization: Bearer`).
131
- - First request: `POST /mcp` with JSON-RPC `initialize` (no `Mcp-Session-Id`); server returns a session id in `Mcp-Session-Id` response header.
132
- - Follow-up: `POST /mcp` with body + `Mcp-Session-Id`; for server-initiated streaming, open `GET /mcp` with `Accept: text/event-stream` and the same session header.
133
- - Session teardown: `DELETE /mcp` with `Mcp-Session-Id`.
134
-
135
- When listening on all interfaces (e.g. `--host=0.0.0.0`), set allowed Host headers to satisfy DNS rebinding checks:
136
-
137
- ```bash
138
- pnpm dlx @ada-mcp/mcp-server server --host=0.0.0.0 --port=8787 --api-key=your_token --allowed-hosts=localhost,127.0.0.1
139
- ```
1
+ # @ada-mcp/mcp-server
2
+
3
+ ADA MCP server package that supports:
4
+
5
+ - Local stdio mode (default) for MCP hosts
6
+ - Remote HTTP mode (`server`) with API key authentication
7
+ - MCP **Streamable HTTP** on `POST|GET|DELETE /mcp` (same port as legacy REST), with optional SSE per MCP spec (`@modelcontextprotocol/sdk` transport)
8
+
9
+ ## 标准安装(Cursor / MCP)
10
+
11
+ 请使用 **`@ada-mcp/launcher@0.1.12`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "ada-mcp": {
17
+ "command": "pnpm",
18
+ "args": ["dlx", "@ada-mcp/launcher@0.1.12"]
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ 本包版本:**`@ada-mcp/mcp-server@0.1.15`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
25
+
26
+ 直接调试本包(无 launcher 拉包前测速):
27
+
28
+ ```bash
29
+ pnpm dlx @ada-mcp/mcp-server@0.1.15
30
+ # npx 等价:
31
+ npx -y @ada-mcp/mcp-server@0.1.15
32
+ ```
33
+
34
+ ## 启动时自动安装依赖(默认仅 Playwright)
35
+
36
+ 进程启动前会按配置自动执行 `install-deps`(日志在 stderr):
37
+
38
+ | 配置 | 含义 |
39
+ |------|------|
40
+ | (未配置) | 仅安装 **Playwright + 浏览器** |
41
+ | `playwright` | 仅 Playwright(显式写法,与默认相同) |
42
+ | `selenium` | **仅** Selenium 原生驱动(GeckoDriver/ChromeDriver) |
43
+ | `appium` | **仅** Appium 包 + 移动端驱动 |
44
+ | `playwright,selenium` | 组合(逗号连接多类) |
45
+ | `all` | 上述全部 |
46
+ | `none` / `skip` | 不自动安装 |
47
+
48
+ **环境变量**
49
+
50
+ - `ADA_MCP_INSTALL_DEPS`:范围,如 `playwright`、`playwright,selenium`、`all`、`none`
51
+ - `ADA_MCP_SKIP_INSTALL_DEPS=1`:跳过自动安装
52
+ - `ADA_MCP_INSTALL_DEPS_FORCE=1`:强制重装
53
+ - `ADA_MCP_GECKODRIVER_VERSION` / `ADA_MCP_CHROMEDRIVER_VERSION`:Selenium 驱动版本
54
+ - `ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS`:浏览器下载超时(默认 15 分钟)
55
+ - `ADA_INSTALL_STRATEGY_TIMEOUT_MS`:npm 装包超时(默认 2 分钟)
56
+
57
+ ## 代理与镜像(`0.1.10+` 推荐)
58
+
59
+ | 阶段 | 自动探测最快镜像 |
60
+ |------|------------------|
61
+ | `pnpm dlx @ada-mcp/launcher` | **是** — 拉包前测速(推荐) |
62
+ | `pnpm dlx @ada-mcp/mcp-server` | tarball 仍走本机源;**同次安装的依赖**由 `preinstall` 测速(仅写入官方 Playwright CDN,`0.1.9+`) |
63
+ | 启动后 `install-deps` | 是 — 内置国内镜像测速(**无需配置**) |
64
+
65
+ 默认 npm 探测候选(按优先级,延迟相同取靠前):阿里云 npmmirror → 腾讯云 → 华为云 → npm 官方。
66
+
67
+ | 变量 | 说明 |
68
+ |------|------|
69
+ | `npm_config_registry` | 可选;仅加速 **dlx** 拉包(推荐 `https://registry.npmmirror.com`) |
70
+ | `ADA_REGISTRY_CANDIDATES` | 可选;在默认五镜像**之外**追加候选 |
71
+ | `ADA_NPM_PROXY_REGISTRY` / `ADA_PNPM_PROXY_REGISTRY` | 可选;覆盖探测主候选(默认 npmmirror) |
72
+ | `PLAYWRIGHT_DOWNLOAD_HOST` | 可选;Playwright 浏览器 CDN |
73
+
74
+ 详见 [ADA-MCP-接入手册 §3.9.2](../../docs/ADA-MCP-接入手册.md#392-代理与镜像配置)。
75
+
76
+ **CLI 参数**(写在 MCP `args` 末尾)
77
+
78
+ - `--install-deps=playwright,selenium`
79
+ - `--skip-install-deps`
80
+ - `--install-deps-force`
81
+ - `--geckodriver-version=latest` `--chromedriver-version=match-chrome`
82
+
83
+ 在标准 `args` 后追加,例如安装全部依赖:
84
+
85
+ ```json
86
+ "args": ["dlx", "@ada-mcp/launcher@0.1.12", "--install-deps=all"]
87
+ ```
88
+
89
+ ## Cursor MCP 配置
90
+
91
+ **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.12`
92
+
93
+ **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.12`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "ada-mcp": {
99
+ "command": "npx",
100
+ "args": ["-y", "@ada-mcp/launcher@0.1.12"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.15`(无 launcher 拉包测速)。
107
+
108
+ ## Remote mode
109
+
110
+ Set API key in environment variable first:
111
+
112
+ ```bash
113
+ export ADA_MCP_REMOTE_API_KEY=your_token
114
+ ```
115
+
116
+ Windows PowerShell:
117
+
118
+ ```powershell
119
+ $env:ADA_MCP_REMOTE_API_KEY="your_token"
120
+ ```
121
+
122
+ Then run:
123
+
124
+ ```bash
125
+ pnpm dlx @ada-mcp/mcp-server server --host=127.0.0.1 --port=8787 --allow-risky=true --risky-mode=whitelist --risky-commands=custom
126
+ ```
127
+
128
+ ### Streamable HTTP (`/mcp`)
129
+
130
+ - Endpoint: `http://<host>:<port>/mcp` — same API key headers as below (`x-api-key` or `Authorization: Bearer`).
131
+ - First request: `POST /mcp` with JSON-RPC `initialize` (no `Mcp-Session-Id`); server returns a session id in `Mcp-Session-Id` response header.
132
+ - Follow-up: `POST /mcp` with body + `Mcp-Session-Id`; for server-initiated streaming, open `GET /mcp` with `Accept: text/event-stream` and the same session header.
133
+ - Session teardown: `DELETE /mcp` with `Mcp-Session-Id`.
134
+
135
+ When listening on all interfaces (e.g. `--host=0.0.0.0`), set allowed Host headers to satisfy DNS rebinding checks:
136
+
137
+ ```bash
138
+ pnpm dlx @ada-mcp/mcp-server server --host=0.0.0.0 --port=8787 --api-key=your_token --allowed-hosts=localhost,127.0.0.1
139
+ ```
package/dist/cli.cjs CHANGED
@@ -2887,7 +2887,7 @@ var init_config = __esm({
2887
2887
  ],
2888
2888
  nativeDriversDir: "dirver",
2889
2889
  geckodriverVersion: "latest",
2890
- chromedriverVersion: "latest"
2890
+ chromedriverVersion: "match-chrome"
2891
2891
  },
2892
2892
  appium: {
2893
2893
  serverUrl: "http://127.0.0.1:4723",
@@ -3277,30 +3277,171 @@ async function resolveChromedriverCfTVersion(requested) {
3277
3277
  }
3278
3278
  return hit.version;
3279
3279
  }
3280
- async function detectInstalledChromeMajorVersion() {
3281
- if (process.platform !== "win32") {
3280
+ function parseBrowserVersionString(raw) {
3281
+ const match = raw.match(/(\d+\.\d+(?:\.\d+)*(?:\.\d+)?)/);
3282
+ if (!match) {
3282
3283
  return void 0;
3283
3284
  }
3284
- const candidates = [
3285
- import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
3286
- import_node_path4.default.join(
3287
- process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3288
- "Google",
3289
- "Chrome",
3290
- "Application",
3291
- "chrome.exe"
3292
- )
3293
- ];
3294
- for (const chromePath of candidates) {
3295
- if (!await fileExists(chromePath)) {
3285
+ const version = match[1];
3286
+ return { version, major: version.split(".")[0] };
3287
+ }
3288
+ async function runCommandCapture(command, args) {
3289
+ return new Promise((resolve) => {
3290
+ const child = (0, import_node_child_process.spawn)(command, args, {
3291
+ stdio: ["ignore", "pipe", "ignore"],
3292
+ shell: process.platform === "win32"
3293
+ });
3294
+ let out = "";
3295
+ child.stdout?.on("data", (chunk) => {
3296
+ out += chunk.toString("utf8");
3297
+ });
3298
+ child.on("exit", (code) => resolve(code === 0 ? out.trim() : void 0));
3299
+ child.on("error", () => resolve(void 0));
3300
+ });
3301
+ }
3302
+ async function detectChromeFromExecutable(exePath) {
3303
+ if (!await fileExists(exePath)) {
3304
+ return void 0;
3305
+ }
3306
+ if (process.platform === "win32") {
3307
+ const version = await readWindowsFileVersion(exePath);
3308
+ if (!version) {
3309
+ return void 0;
3310
+ }
3311
+ const parsed2 = parseBrowserVersionString(version);
3312
+ if (!parsed2) {
3313
+ return void 0;
3314
+ }
3315
+ return { path: exePath, version: parsed2.version, major: parsed2.major };
3316
+ }
3317
+ const out = await runCommandCapture(exePath, ["--version"]);
3318
+ if (!out) {
3319
+ return void 0;
3320
+ }
3321
+ const parsed = parseBrowserVersionString(out);
3322
+ if (!parsed) {
3323
+ return void 0;
3324
+ }
3325
+ return { path: exePath, version: parsed.version, major: parsed.major };
3326
+ }
3327
+ async function detectFirefoxFromExecutable(exePath) {
3328
+ if (!await fileExists(exePath)) {
3329
+ return void 0;
3330
+ }
3331
+ if (process.platform === "win32") {
3332
+ const version = await readWindowsFileVersion(exePath);
3333
+ if (!version) {
3334
+ return void 0;
3335
+ }
3336
+ const parsed2 = parseBrowserVersionString(version);
3337
+ if (!parsed2) {
3338
+ return void 0;
3339
+ }
3340
+ return { path: exePath, version: parsed2.version, major: parsed2.major };
3341
+ }
3342
+ const out = await runCommandCapture(exePath, ["--version"]);
3343
+ if (!out) {
3344
+ return void 0;
3345
+ }
3346
+ const parsed = parseBrowserVersionString(out);
3347
+ if (!parsed) {
3348
+ return void 0;
3349
+ }
3350
+ return { path: exePath, version: parsed.version, major: parsed.major };
3351
+ }
3352
+ async function detectLocalBrowsers() {
3353
+ const result = {};
3354
+ if (process.platform === "win32") {
3355
+ const chromeCandidates = [
3356
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
3357
+ import_node_path4.default.join(
3358
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3359
+ "Google",
3360
+ "Chrome",
3361
+ "Application",
3362
+ "chrome.exe"
3363
+ ),
3364
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Chromium", "Application", "chrome.exe")
3365
+ ];
3366
+ for (const p of chromeCandidates) {
3367
+ const hit = await detectChromeFromExecutable(p);
3368
+ if (hit) {
3369
+ result.chrome = hit;
3370
+ break;
3371
+ }
3372
+ }
3373
+ const firefoxCandidates = [
3374
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Mozilla Firefox", "firefox.exe"),
3375
+ import_node_path4.default.join(
3376
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3377
+ "Mozilla Firefox",
3378
+ "firefox.exe"
3379
+ )
3380
+ ];
3381
+ for (const p of firefoxCandidates) {
3382
+ const hit = await detectFirefoxFromExecutable(p);
3383
+ if (hit) {
3384
+ result.firefox = hit;
3385
+ break;
3386
+ }
3387
+ }
3388
+ return result;
3389
+ }
3390
+ if (process.platform === "darwin") {
3391
+ const chromePaths = [
3392
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
3393
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
3394
+ ];
3395
+ for (const p of chromePaths) {
3396
+ const hit = await detectChromeFromExecutable(p);
3397
+ if (hit) {
3398
+ result.chrome = hit;
3399
+ break;
3400
+ }
3401
+ }
3402
+ const firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox";
3403
+ const ff = await detectFirefoxFromExecutable(firefoxPath);
3404
+ if (ff) {
3405
+ result.firefox = ff;
3406
+ }
3407
+ return result;
3408
+ }
3409
+ const chromeCommands = ["google-chrome-stable", "google-chrome", "chromium-browser", "chromium"];
3410
+ for (const cmd of chromeCommands) {
3411
+ if (!await commandOnPath(cmd)) {
3296
3412
  continue;
3297
3413
  }
3298
- const version = await readWindowsFileVersion(chromePath);
3299
- if (version) {
3300
- return version.split(".")[0];
3414
+ const out = await runCommandCapture(cmd, ["--version"]);
3415
+ const parsed = out ? parseBrowserVersionString(out) : void 0;
3416
+ if (parsed) {
3417
+ result.chrome = { path: cmd, version: parsed.version, major: parsed.major };
3418
+ break;
3301
3419
  }
3302
3420
  }
3303
- return void 0;
3421
+ if (await commandOnPath("firefox")) {
3422
+ const out = await runCommandCapture("firefox", ["--version"]);
3423
+ const parsed = out ? parseBrowserVersionString(out) : void 0;
3424
+ if (parsed) {
3425
+ result.firefox = { path: "firefox", version: parsed.version, major: parsed.major };
3426
+ }
3427
+ }
3428
+ return result;
3429
+ }
3430
+ function logSeleniumDriverGuidance(onLogLine) {
3431
+ onLogLine?.(
3432
+ "[selenium] \u5B89\u88C5\u524D\u4F1A\u68C0\u6D4B\u672C\u673A Chrome/Chromium \u4E0E Firefox \u7248\u672C\uFF1B\u9A71\u52A8\u653E\u5165\u9879\u76EE\u76EE\u5F55\u540E\u53EF\u81EA\u884C\u66FF\u6362\u8986\u76D6\u3002"
3433
+ );
3434
+ onLogLine?.("[selenium] \u5404\u6D4F\u89C8\u5668\u9A71\u52A8\u624B\u52A8\u4E0B\u8F7D\u53C2\u8003\uFF1A");
3435
+ for (const ref of SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES) {
3436
+ onLogLine?.(`[selenium] ${ref.browser} (${ref.platforms}) \u2014 ${ref.vendor}: ${ref.url}`);
3437
+ }
3438
+ onLogLine?.(
3439
+ "[selenium] \u81EA\u52A8\u4E0B\u8F7D\u5931\u8D25\u65F6\u5C06\u8DF3\u8FC7\u5BF9\u5E94\u9A71\u52A8\u5E76\u7EE7\u7EED\uFF0C\u8BF7\u6309\u4E0A\u8868\u81EA\u884C\u4E0B\u8F7D\u540E\u653E\u5165\u9A71\u52A8\u76EE\u5F55\u6216\u914D\u7F6E PATH/ADA_GECKODRIVER_PATH/ADA_CHROMEDRIVER_PATH\u3002"
3440
+ );
3441
+ }
3442
+ async function detectInstalledChromeMajorVersion() {
3443
+ const browsers = await detectLocalBrowsers();
3444
+ return browsers.chrome?.major;
3304
3445
  }
3305
3446
  async function readWindowsFileVersion(exePath) {
3306
3447
  return new Promise((resolve) => {
@@ -3343,7 +3484,7 @@ async function downloadGeckodriver(driversDir, versionInput, onLogLine) {
3343
3484
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3344
3485
  const zipPath = import_node_path4.default.join(driversDir, `_download_geckodriver_${version}.zip`);
3345
3486
  const extractDir = import_node_path4.default.join(driversDir, `_extract_geckodriver_${version}`);
3346
- onLogLine?.(`[selenium] \u4E0B\u8F7D geckodriver ${tag} \uFFFD\uFFFD?${driversDir}`);
3487
+ onLogLine?.(`[selenium] \u4E0B\u8F7D geckodriver ${tag} \u2192 ${driversDir}`);
3347
3488
  onLogLine?.(`[selenium] URL: ${url}`);
3348
3489
  await downloadToFile(url, zipPath);
3349
3490
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
@@ -3356,7 +3497,7 @@ async function downloadGeckodriver(driversDir, versionInput, onLogLine) {
3356
3497
  await copyExecutable(found, dest);
3357
3498
  await import_promises4.default.rm(zipPath, { force: true });
3358
3499
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3359
- onLogLine?.(`[selenium] geckodriver \u5DF2\u5199\uFFFD\uFFFD? ${dest}`);
3500
+ onLogLine?.(`[selenium] geckodriver \u5DF2\u5199\u5165: ${dest}`);
3360
3501
  return { path: dest, version: tag };
3361
3502
  }
3362
3503
  async function downloadChromedriver(driversDir, versionInput, onLogLine) {
@@ -3376,7 +3517,7 @@ async function downloadChromedriver(driversDir, versionInput, onLogLine) {
3376
3517
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3377
3518
  const zipPath = import_node_path4.default.join(driversDir, `_download_chromedriver_${major}.zip`);
3378
3519
  const extractDir = import_node_path4.default.join(driversDir, `_extract_chromedriver_${major}`);
3379
- onLogLine?.(`[selenium] \u4E0B\u8F7D chromedriver ${fullVersion} (\u4E3B\u7248\uFFFD\uFFFD?${major}) \uFFFD\uFFFD?${driversDir}`);
3520
+ onLogLine?.(`[selenium] \u4E0B\u8F7D chromedriver ${fullVersion} (\u4E3B\u7248\u672C ${major}) \u2192 ${driversDir}`);
3380
3521
  onLogLine?.(`[selenium] URL: ${url}`);
3381
3522
  await downloadToFile(url, zipPath);
3382
3523
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
@@ -3393,7 +3534,7 @@ async function downloadChromedriver(driversDir, versionInput, onLogLine) {
3393
3534
  }
3394
3535
  await import_promises4.default.rm(zipPath, { force: true });
3395
3536
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3396
- onLogLine?.(`[selenium] chromedriver \u5DF2\u5199\uFFFD\uFFFD? ${destVersioned}`);
3537
+ onLogLine?.(`[selenium] chromedriver \u5DF2\u5199\u5165: ${destVersioned}`);
3397
3538
  return { path: destVersioned, version: fullVersion, major };
3398
3539
  }
3399
3540
  async function saveNativeDriverManifest(manifest, workspaceRoot) {
@@ -3403,38 +3544,91 @@ async function saveNativeDriverManifest(manifest, workspaceRoot) {
3403
3544
  const file = import_node_path4.default.join(dir, "native-drivers.json");
3404
3545
  await import_promises4.default.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
3405
3546
  }
3547
+ function resolveDefaultChromedriverVersion(requested, browsers) {
3548
+ if (requested && requested !== "latest") {
3549
+ return requested;
3550
+ }
3551
+ if (browsers.chrome?.major) {
3552
+ return "match-chrome";
3553
+ }
3554
+ return requested ?? "latest";
3555
+ }
3556
+ function formatDownloadError(error) {
3557
+ return error instanceof Error ? error.message : String(error);
3558
+ }
3406
3559
  async function ensureNativeWebDrivers(options = {}) {
3407
3560
  const root = options.workspaceRoot ?? await resolveWorkspaceRoot3();
3408
3561
  const driversDir = options.driversDir ?? await resolveNativeDriversDir(root);
3409
3562
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3410
3563
  const log2 = options.onLogLine;
3564
+ logSeleniumDriverGuidance(log2);
3411
3565
  log2?.(`[selenium] \u539F\u751F\u9A71\u52A8\u76EE\u5F55: ${driversDir}`);
3566
+ const browsers = await detectLocalBrowsers();
3567
+ if (browsers.chrome) {
3568
+ log2?.(
3569
+ `[selenium] \u68C0\u6D4B\u5230\u672C\u673A Chrome/Chromium: ${browsers.chrome.version} (\u4E3B\u7248\u672C ${browsers.chrome.major}) \u2014 ${browsers.chrome.path}`
3570
+ );
3571
+ } else {
3572
+ log2?.("[selenium] \u672A\u68C0\u6D4B\u5230\u672C\u673A Chrome/Chromium\uFF08chromedriver \u5C06\u4F7F\u7528 latest \u6216\u663E\u5F0F\u6307\u5B9A\u7248\u672C\uFF09");
3573
+ }
3574
+ if (browsers.firefox) {
3575
+ log2?.(
3576
+ `[selenium] \u68C0\u6D4B\u5230\u672C\u673A Firefox: ${browsers.firefox.version} (\u4E3B\u7248\u672C ${browsers.firefox.major}) \u2014 ${browsers.firefox.path}`
3577
+ );
3578
+ } else {
3579
+ log2?.("[selenium] \u672A\u68C0\u6D4B\u5230\u672C\u673A Firefox\uFF08geckodriver \u5C06\u4F7F\u7528 latest \u6216\u663E\u5F0F\u6307\u5B9A\u7248\u672C\uFF09");
3580
+ }
3412
3581
  const localChromeVersions = await listLocalChromedriverVersions(driversDir);
3413
3582
  if (localChromeVersions.length > 0) {
3414
- log2?.(`[selenium] \u76EE\u5F55\u5185\u5DF2\uFFFD\uFFFD?chromedriver \u7248\u672C: ${localChromeVersions.join(", ")}`);
3583
+ log2?.(`[selenium] \u76EE\u5F55\u5185\u5DF2\u6709 chromedriver \u7248\u672C: ${localChromeVersions.join(", ")}`);
3584
+ }
3585
+ const chromedriverRequest = resolveDefaultChromedriverVersion(options.chromedriverVersion, browsers);
3586
+ if (chromedriverRequest === "match-chrome" && browsers.chrome) {
3587
+ log2?.(
3588
+ `[selenium] chromedriver \u5C06\u5C1D\u8BD5\u5339\u914D\u672C\u673A Chrome \u4E3B\u7248\u672C ${browsers.chrome.major}\uFF08chrome-for-testing \u76EE\u5F55\uFF09`
3589
+ );
3415
3590
  }
3416
3591
  const localGecko = await findGeckodriverInDir(driversDir, options.geckodriverVersion);
3417
3592
  const hasLocalGecko = Boolean(localGecko && await fileExists(localGecko));
3418
3593
  let geckoVersion = options.geckodriverVersion;
3419
3594
  if (options.geckodriverVersion !== "skip" && (options.force || !hasLocalGecko)) {
3420
- const downloaded = await downloadGeckodriver(driversDir, options.geckodriverVersion ?? "latest", log2);
3421
- geckoVersion = downloaded.version;
3595
+ try {
3596
+ const downloaded = await downloadGeckodriver(driversDir, options.geckodriverVersion ?? "latest", log2);
3597
+ geckoVersion = downloaded.version;
3598
+ } catch (error) {
3599
+ log2?.(`[selenium][warn] geckodriver \u81EA\u52A8\u4E0B\u8F7D\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7: ${formatDownloadError(error)}`);
3600
+ log2?.(
3601
+ "[selenium][warn] \u8BF7\u5C06 geckodriver \u653E\u5165\u4E0A\u8FF0\u76EE\u5F55\u6216\u914D\u7F6E PATH / ADA_GECKODRIVER_PATH\uFF1B\u53EF\u53C2\u8003 Mozilla \u53D1\u5E03\u9875\u624B\u52A8\u4E0B\u8F7D\u3002"
3602
+ );
3603
+ }
3422
3604
  } else if (hasLocalGecko) {
3423
3605
  log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 geckodriver: ${localGecko}`);
3424
3606
  }
3425
- const localChrome = await findChromedriverInDir(driversDir, options.chromedriverVersion);
3607
+ const localChrome = await findChromedriverInDir(driversDir, chromedriverRequest);
3426
3608
  const hasLocalChrome = Boolean(localChrome && await fileExists(localChrome.path));
3427
- if (options.chromedriverVersion !== "skip" && (options.force || !hasLocalChrome)) {
3428
- await downloadChromedriver(driversDir, options.chromedriverVersion ?? "latest", log2);
3609
+ if (chromedriverRequest !== "skip" && (options.force || !hasLocalChrome)) {
3610
+ try {
3611
+ await downloadChromedriver(driversDir, chromedriverRequest ?? "latest", log2);
3612
+ } catch (error) {
3613
+ log2?.(`[selenium][warn] chromedriver \u81EA\u52A8\u4E0B\u8F7D\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7: ${formatDownloadError(error)}`);
3614
+ if (browsers.chrome) {
3615
+ log2?.(
3616
+ `[selenium][warn] \u672C\u673A Chrome \u4E3B\u7248\u672C\u4E3A ${browsers.chrome.major}\uFF0C\u8BF7\u4E0B\u8F7D\u5339\u914D\u7248\u672C\u7684 chromedriver \u653E\u5165\u76EE\u5F55\u6216\u914D\u7F6E ADA_CHROMEDRIVER_PATH\u3002`
3617
+ );
3618
+ }
3619
+ log2?.(
3620
+ "[selenium][warn] \u53EF\u53C2\u8003 chrome-for-testing / chromedriver.storage \u624B\u52A8\u4E0B\u8F7D\uFF1B\u5DF2\u653E\u5165 dirver/ \u7684\u9A71\u52A8\u4F1A\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u81EA\u52A8\u590D\u7528\u3002"
3621
+ );
3622
+ }
3429
3623
  } else if (hasLocalChrome) {
3430
- log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 chromedriver: ${localChrome.path} (\u4E3B\u7248\uFFFD\uFFFD?${localChrome.version})`);
3624
+ log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 chromedriver: ${localChrome.path} (\u4E3B\u7248\u672C ${localChrome.version})`);
3431
3625
  }
3432
3626
  const resolved = await resolveNativeDrivers({
3433
3627
  driversDir,
3434
3628
  workspaceRoot: root,
3435
3629
  selection: {
3436
3630
  geckodriverVersion: geckoVersion,
3437
- chromedriverVersion: options.chromedriverVersion ?? localChrome?.version
3631
+ chromedriverVersion: chromedriverRequest ?? localChrome?.version
3438
3632
  }
3439
3633
  });
3440
3634
  await saveNativeDriverManifest(
@@ -3449,14 +3643,21 @@ async function ensureNativeWebDrivers(options = {}) {
3449
3643
  if (resolved.geckodriverPath) {
3450
3644
  process.env.ADA_GECKODRIVER_PATH = resolved.geckodriverPath;
3451
3645
  log2?.(`[selenium] \u4F7F\u7528 geckodriver: ${resolved.geckodriverPath}`);
3646
+ } else if (options.geckodriverVersion !== "skip") {
3647
+ log2?.("[selenium][warn] \u672A\u627E\u5230\u53EF\u7528 geckodriver\uFF08\u81EA\u52A8\u4E0B\u8F7D\u5DF2\u8DF3\u8FC7\u6216\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5\uFF09");
3452
3648
  }
3453
3649
  if (resolved.chromedriverPath) {
3454
3650
  process.env.ADA_CHROMEDRIVER_PATH = resolved.chromedriverPath;
3455
3651
  log2?.(`[selenium] \u4F7F\u7528 chromedriver: ${resolved.chromedriverPath}`);
3652
+ } else if (chromedriverRequest !== "skip") {
3653
+ log2?.("[selenium][warn] \u672A\u627E\u5230\u53EF\u7528 chromedriver\uFF08\u81EA\u52A8\u4E0B\u8F7D\u5DF2\u8DF3\u8FC7\u6216\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5\uFF09");
3654
+ }
3655
+ if (!resolved.geckodriverOk && !resolved.chromedriverOk) {
3656
+ log2?.("[selenium][warn] \u5F53\u524D\u65E0\u53EF\u7528\u539F\u751F WebDriver\uFF1BSelenium \u4EFB\u52A1\u53EF\u80FD\u5931\u8D25\uFF0C\u8BF7\u6309\u4E0A\u6587\u53C2\u8003\u5730\u5740\u81EA\u884C\u4E0B\u8F7D\u3002");
3456
3657
  }
3457
3658
  return resolved;
3458
3659
  }
3459
- var import_node_child_process, import_promises4, import_node_path4, DEFAULT_NATIVE_DRIVERS_DIR, CHROME_FOR_TESTING_JSON;
3660
+ var import_node_child_process, import_promises4, import_node_path4, DEFAULT_NATIVE_DRIVERS_DIR, CHROME_FOR_TESTING_JSON, SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES;
3460
3661
  var init_src2 = __esm({
3461
3662
  "../../packages/native-drivers/src/index.ts"() {
3462
3663
  "use strict";
@@ -3465,6 +3666,44 @@ var init_src2 = __esm({
3465
3666
  import_node_path4 = __toESM(require("node:path"), 1);
3466
3667
  DEFAULT_NATIVE_DRIVERS_DIR = "dirver";
3467
3668
  CHROME_FOR_TESTING_JSON = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json";
3669
+ SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES = [
3670
+ {
3671
+ browser: "Chrome/Chromium",
3672
+ platforms: "Windows/Linux/macOS",
3673
+ vendor: "\u8C37\u6B4C",
3674
+ url: "https://chromedriver.storage.googleapis.com/index.html"
3675
+ },
3676
+ {
3677
+ browser: "Firefox",
3678
+ platforms: "Windows/Linux/macOS",
3679
+ vendor: "Mozilla",
3680
+ url: "https://github.com/mozilla/geckodriver/releases"
3681
+ },
3682
+ {
3683
+ browser: "Edge",
3684
+ platforms: "win10",
3685
+ vendor: "\u5FAE\u8F6F",
3686
+ url: "https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/"
3687
+ },
3688
+ {
3689
+ browser: "Internet Explorer",
3690
+ platforms: "Windows",
3691
+ vendor: "Selenium \u9879\u76EE\u7EC4",
3692
+ url: "https://selenium-release.storage.googleapis.com/index.html"
3693
+ },
3694
+ {
3695
+ browser: "Safari",
3696
+ platforms: "macOS El Capitan \u53CA\u66F4\u9AD8\u7248\u672C",
3697
+ vendor: "\u82F9\u679C",
3698
+ url: "\uFF08\u7CFB\u7EDF\u5185\u7F6E\uFF0C\u65E0\u9700\u5355\u72EC\u4E0B\u8F7D\uFF09"
3699
+ },
3700
+ {
3701
+ browser: "Opera",
3702
+ platforms: "Windows/macOS/Linux",
3703
+ vendor: "Opera",
3704
+ url: "https://github.com/operasoftware/operachromiumdriver/releases"
3705
+ }
3706
+ ];
3468
3707
  }
3469
3708
  });
3470
3709
 
@@ -3543,7 +3782,7 @@ function runCommand2(command, args, options) {
3543
3782
  });
3544
3783
  });
3545
3784
  }
3546
- function runCommandCapture(command, args) {
3785
+ function runCommandCapture2(command, args) {
3547
3786
  return new Promise((resolve) => {
3548
3787
  const child = (0, import_node_child_process2.spawn)(command, args, {
3549
3788
  stdio: ["ignore", "pipe", "pipe"],
@@ -3665,7 +3904,7 @@ async function ensureNodeEnvironmentForInstall(onLogLine) {
3665
3904
  const requiredNpmMajor = 10;
3666
3905
  const runtimeNode = process.versions.node;
3667
3906
  const runtimeMajor = majorOf(runtimeNode);
3668
- const nodeVersion = await runCommandCapture("node", ["-v"]);
3907
+ const nodeVersion = await runCommandCapture2("node", ["-v"]);
3669
3908
  if (nodeVersion.code === 0) {
3670
3909
  const nodeMajor = majorOf(nodeVersion.stdout);
3671
3910
  onLogLine?.(`[deps] Node \u7248\u672C\u68C0\u6D4B\uFF1A\u7CFB\u7EDF=${nodeVersion.stdout}\uFF0C\u5185\u7F6E=${runtimeNode}`);
@@ -3684,7 +3923,7 @@ async function ensureNodeEnvironmentForInstall(onLogLine) {
3684
3923
  `[deps][warn] \u672A\u4ECE PATH \u68C0\u6D4B\u5230 node\uFF0C\u5F53\u524D\u8FD0\u884C\u65F6 Node.js=${runtimeNode}\uFF08\u53EF\u6267\u884C\u7A0B\u5E8F\u5185\u7F6E\uFF09\uFF0C\u7EE7\u7EED\u5C1D\u8BD5\u5B89\u88C5\u3002`
3685
3924
  );
3686
3925
  }
3687
- const npmVersion = await runCommandCapture("npm", ["-v"]);
3926
+ const npmVersion = await runCommandCapture2("npm", ["-v"]);
3688
3927
  if (npmVersion.code !== 0) {
3689
3928
  const message = "\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684 npm\uFF08PATH \u4E2D\u4E0D\u53EF\u7528\uFF09\u3002\u8BF7\u5B89\u88C5 Node.js 22+\uFF08\u542B npm\uFF09\u5E76\u91CD\u542F\u7EC8\u7AEF\u540E\u91CD\u8BD5\u3002";
3690
3929
  onLogLine?.(`[deps] ${message}`);
@@ -3737,7 +3976,7 @@ async function resolveCompatibleDriverSpecs(driver) {
3737
3976
  const preferred = driver === "uiautomator2" ? process.env.ADA_APPIUM_DRIVER_SPEC_UIAUTOMATOR2 ?? `${pkg}@2` : process.env.ADA_APPIUM_DRIVER_SPEC_XCUITEST ?? `${pkg}@7`;
3738
3977
  const fallbackRange = driver === "uiautomator2" ? process.env.ADA_APPIUM_DRIVER_RANGE_UIAUTOMATOR2 ?? "<3" : process.env.ADA_APPIUM_DRIVER_RANGE_XCUITEST ?? "<8";
3739
3978
  const specs = [preferred];
3740
- const view = await runCommandCapture("npm", ["view", `${pkg}@${fallbackRange}`, "version"]);
3979
+ const view = await runCommandCapture2("npm", ["view", `${pkg}@${fallbackRange}`, "version"]);
3741
3980
  if (view.code === 0 && view.stdout) {
3742
3981
  const version = view.stdout.trim().split(/\r?\n/).pop()?.trim();
3743
3982
  if (version) {
@@ -4159,7 +4398,7 @@ async function verifyAppiumCommand() {
4159
4398
  }
4160
4399
  }
4161
4400
  async function getInstalledAppiumDrivers() {
4162
- const check = await runCommandCapture("npm", ["exec", "appium", "driver", "list", "--installed", "--json"]);
4401
+ const check = await runCommandCapture2("npm", ["exec", "appium", "driver", "list", "--installed", "--json"]);
4163
4402
  if (check.code === 0 && check.stdout) {
4164
4403
  try {
4165
4404
  const parsed = JSON.parse(check.stdout);
@@ -4168,7 +4407,7 @@ async function getInstalledAppiumDrivers() {
4168
4407
  } catch {
4169
4408
  }
4170
4409
  }
4171
- const fallback = await runCommandCapture("npm", ["exec", "appium", "driver", "list", "--installed"]);
4410
+ const fallback = await runCommandCapture2("npm", ["exec", "appium", "driver", "list", "--installed"]);
4172
4411
  if (fallback.code !== 0) {
4173
4412
  return [];
4174
4413
  }
@@ -4238,6 +4477,7 @@ async function ensureSeleniumNativeDrivers(config, options, onLogLine) {
4238
4477
  }
4239
4478
  const geckoVer = options?.geckodriverVersion ?? config.dependencies.geckodriverVersion ?? "latest";
4240
4479
  const chromeVer = options?.chromedriverVersion ?? config.dependencies.chromedriverVersion ?? "latest";
4480
+ onLogLine?.("[selenium] \u5C06\u68C0\u6D4B\u672C\u673A Chrome/Firefox \u7248\u672C\u5E76\u5C1D\u8BD5\u5339\u914D\u9A71\u52A8\uFF1B\u4E0B\u8F7D\u5931\u8D25\u65F6\u8DF3\u8FC7\u5E76\u63D0\u793A\u624B\u52A8\u4E0B\u8F7D\uFF08\u89C1\u5B89\u88C5\u65E5\u5FD7\u53C2\u8003\u5730\u5740\uFF09\u3002");
4241
4481
  try {
4242
4482
  const catalog = await listChromedriverCfTVersions();
4243
4483
  onLogLine?.(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-mcp/mcp-server",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "ADA MCP server for web/mobile automation (stdio + remote HTTP)",
5
5
  "private": false,
6
6
  "type": "commonjs",