@ada-mcp/mcp-server 0.1.18 → 0.1.20

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 CHANGED
@@ -8,27 +8,27 @@ ADA MCP server package that supports:
8
8
 
9
9
  ## 标准安装(Cursor / MCP)
10
10
 
11
- 请使用 **`@ada-mcp/launcher@0.1.17`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
11
+ 请使用 **`@ada-mcp/launcher@0.1.19`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
12
12
 
13
13
  ```json
14
14
  {
15
15
  "mcpServers": {
16
16
  "ada-mcp": {
17
17
  "command": "pnpm",
18
- "args": ["dlx", "@ada-mcp/launcher@0.1.17"]
18
+ "args": ["dlx", "@ada-mcp/launcher@0.1.19"]
19
19
  }
20
20
  }
21
21
  }
22
22
  ```
23
23
 
24
- 本包版本:**`@ada-mcp/mcp-server@0.1.18`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
24
+ 本包版本:**`@ada-mcp/mcp-server@0.1.19`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
25
25
 
26
26
  直接调试本包(无 launcher 拉包前测速):
27
27
 
28
28
  ```bash
29
- pnpm dlx @ada-mcp/mcp-server@0.1.18
29
+ pnpm dlx @ada-mcp/mcp-server@0.1.19
30
30
  # npx 等价:
31
- npx -y @ada-mcp/mcp-server@0.1.18
31
+ npx -y @ada-mcp/mcp-server@0.1.19
32
32
  ```
33
33
 
34
34
  ## 启动时自动安装依赖(默认仅 Playwright)
@@ -83,27 +83,27 @@ npx -y @ada-mcp/mcp-server@0.1.18
83
83
  在标准 `args` 后追加,例如安装全部依赖:
84
84
 
85
85
  ```json
86
- "args": ["dlx", "@ada-mcp/launcher@0.1.17", "--install-deps=all"]
86
+ "args": ["dlx", "@ada-mcp/launcher@0.1.19", "--install-deps=all"]
87
87
  ```
88
88
 
89
89
  ## Cursor MCP 配置
90
90
 
91
- **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.17`
91
+ **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.19`
92
92
 
93
- **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.17`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
93
+ **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.19`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
94
94
 
95
95
  ```json
96
96
  {
97
97
  "mcpServers": {
98
98
  "ada-mcp": {
99
99
  "command": "npx",
100
- "args": ["-y", "@ada-mcp/launcher@0.1.17"]
100
+ "args": ["-y", "@ada-mcp/launcher@0.1.19"]
101
101
  }
102
102
  }
103
103
  }
104
104
  ```
105
105
 
106
- Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.18`(无 launcher 拉包测速)。
106
+ Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.19`(无 launcher 拉包测速)。
107
107
 
108
108
  ## Remote mode
109
109
 
package/dist/cli.cjs CHANGED
@@ -3005,14 +3005,55 @@ async function resolveNativeDriversDir(workspaceRoot) {
3005
3005
  }
3006
3006
  return import_node_path4.default.join(root, DEFAULT_NATIVE_DRIVERS_DIR);
3007
3007
  }
3008
- function platformArchiveSuffix() {
3008
+ function geckodriverMirrorBase() {
3009
+ const fromEnv = process.env.ADA_GECKODRIVER_MIRROR?.trim();
3010
+ return (fromEnv || DEFAULT_GECKODRIVER_MIRROR).replace(/\/$/, "");
3011
+ }
3012
+ function geckodriverPlatformAsset(tag) {
3013
+ const t = tag.startsWith("v") ? tag : `v${tag}`;
3009
3014
  if (process.platform === "win32") {
3010
- return "win64.zip";
3015
+ const winSuffix = process.arch === "arm64" ? "win-aarch64" : process.arch === "ia32" ? "win32" : "win64";
3016
+ return { fileName: `geckodriver-${t}-${winSuffix}.zip`, archiveKind: "zip" };
3011
3017
  }
3012
3018
  if (process.platform === "darwin") {
3013
- return process.arch === "arm64" ? "macos-aarch64.zip" : "macos.zip";
3019
+ const macSuffix = process.arch === "arm64" ? "macos-aarch64" : "macos";
3020
+ return { fileName: `geckodriver-${t}-${macSuffix}.tar.gz`, archiveKind: "tar.gz" };
3021
+ }
3022
+ const linuxSuffix = process.arch === "arm64" ? "linux-aarch64" : process.arch === "ia32" ? "linux32" : "linux64";
3023
+ return { fileName: `geckodriver-${t}-${linuxSuffix}.tar.gz`, archiveKind: "tar.gz" };
3024
+ }
3025
+ function parseSemverTuple(version2) {
3026
+ const m = version2.replace(/^v/i, "").match(/^(\d+)\.(\d+)\.(\d+)/);
3027
+ if (!m) {
3028
+ return null;
3029
+ }
3030
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
3031
+ }
3032
+ function compareSemverDesc(a, b) {
3033
+ const pa = parseSemverTuple(a);
3034
+ const pb = parseSemverTuple(b);
3035
+ if (!pa || !pb) {
3036
+ return 0;
3037
+ }
3038
+ for (let i = 0; i < 3; i += 1) {
3039
+ if (pa[i] !== pb[i]) {
3040
+ return pb[i] - pa[i];
3041
+ }
3042
+ }
3043
+ return 0;
3044
+ }
3045
+ async function fetchGeckodriverLatestFromMirror(mirrorBase) {
3046
+ const res = await fetch(`${mirrorBase}/`);
3047
+ if (!res.ok) {
3048
+ return null;
3049
+ }
3050
+ const html = await res.text();
3051
+ const tags = /* @__PURE__ */ new Set();
3052
+ for (const m of html.matchAll(/href="v(\d+\.\d+\.\d+)\//gi)) {
3053
+ tags.add(`v${m[1]}`);
3014
3054
  }
3015
- return "linux64.zip";
3055
+ const sorted = Array.from(tags).sort(compareSemverDesc);
3056
+ return sorted[0] ?? null;
3016
3057
  }
3017
3058
  function platformChromeLabel() {
3018
3059
  if (process.platform === "win32") {
@@ -3197,6 +3238,21 @@ async function extractZip(zipPath, destDir) {
3197
3238
  }
3198
3239
  await runCommand("unzip", ["-o", zipPath, "-d", destDir]);
3199
3240
  }
3241
+ async function extractTarGz(archivePath, destDir) {
3242
+ await import_promises4.default.mkdir(destDir, { recursive: true });
3243
+ if (process.platform === "win32") {
3244
+ await runCommand("tar", ["-xzf", archivePath, "-C", destDir]);
3245
+ return;
3246
+ }
3247
+ await runCommand("tar", ["-xzf", archivePath, "-C", destDir]);
3248
+ }
3249
+ async function extractDriverArchive(archivePath, destDir, kind) {
3250
+ if (kind === "zip") {
3251
+ await extractZip(archivePath, destDir);
3252
+ return;
3253
+ }
3254
+ await extractTarGz(archivePath, destDir);
3255
+ }
3200
3256
  async function findFileRecursive(dir, fileName) {
3201
3257
  const entries = await import_promises4.default.readdir(dir, { withFileTypes: true });
3202
3258
  for (const ent of entries) {
@@ -3224,13 +3280,26 @@ async function fetchGeckodriverReleaseVersion(requested) {
3224
3280
  if (requested && requested !== "latest") {
3225
3281
  return requested.startsWith("v") ? requested : `v${requested}`;
3226
3282
  }
3227
- const res = await fetch("https://api.github.com/repos/mozilla/geckodriver/releases/latest");
3228
- if (!res.ok) {
3229
- throw new Error(`Failed to fetch geckodriver latest release: HTTP ${res.status}`);
3283
+ const mirrorLatest = await fetchGeckodriverLatestFromMirror(geckodriverMirrorBase());
3284
+ if (mirrorLatest) {
3285
+ return mirrorLatest;
3230
3286
  }
3231
- const json3 = await res.json();
3232
- const tag = json3.tag_name ?? "v0.36.0";
3233
- return tag.startsWith("v") ? tag : `v${tag}`;
3287
+ try {
3288
+ const res = await fetch("https://api.github.com/repos/mozilla/geckodriver/releases/latest");
3289
+ if (res.ok) {
3290
+ const json3 = await res.json();
3291
+ const tag = json3.tag_name ?? "v0.36.0";
3292
+ return tag.startsWith("v") ? tag : `v${tag}`;
3293
+ }
3294
+ } catch {
3295
+ }
3296
+ return "v0.36.0";
3297
+ }
3298
+ function buildGeckodriverDownloadUrls(tag) {
3299
+ const { fileName, archiveKind } = geckodriverPlatformAsset(tag);
3300
+ const mirror = `${geckodriverMirrorBase()}/${tag}/${fileName}`;
3301
+ const github = `${GITHUB_GECKODRIVER_RELEASES}/download/${tag}/${fileName}`;
3302
+ return { mirror, github, fileName, archiveKind };
3234
3303
  }
3235
3304
  async function listChromedriverCfTVersions() {
3236
3305
  const res = await fetch(CHROME_FOR_TESTING_JSON);
@@ -3474,26 +3543,39 @@ async function downloadToFile(url2, destPath) {
3474
3543
  async function downloadGeckodriver(driversDir, versionInput, onLogLine) {
3475
3544
  const tag = await fetchGeckodriverReleaseVersion(versionInput);
3476
3545
  const version2 = tag.replace(/^v/, "");
3477
- const suffix = platformArchiveSuffix();
3478
- const assetName = `geckodriver-${tag}-${suffix}`.replace("vv", "v");
3479
- const url2 = `https://github.com/mozilla/geckodriver/releases/download/${tag}/geckodriver-${tag}-${suffix}`;
3546
+ const { mirror, github, fileName, archiveKind } = buildGeckodriverDownloadUrls(tag);
3480
3547
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3481
- const zipPath = import_node_path4.default.join(driversDir, `_download_geckodriver_${version2}.zip`);
3548
+ const archiveExt = archiveKind === "zip" ? "zip" : "tar.gz";
3549
+ const archivePath = import_node_path4.default.join(driversDir, `_download_geckodriver_${version2}.${archiveExt}`);
3482
3550
  const extractDir = import_node_path4.default.join(driversDir, `_extract_geckodriver_${version2}`);
3483
- onLogLine?.(`[selenium] \u4E0B\u8F7D geckodriver ${tag} \u2192 ${driversDir}`);
3484
- onLogLine?.(`[selenium] URL: ${url2}`);
3485
- await downloadToFile(url2, zipPath);
3551
+ onLogLine?.(`[selenium] ?? geckodriver ${tag}?${fileName}?? ${driversDir}`);
3552
+ onLogLine?.(`[selenium] ??: ${mirror}`);
3553
+ let downloaded = false;
3554
+ try {
3555
+ await downloadToFile(mirror, archivePath);
3556
+ downloaded = true;
3557
+ } catch (mirrorError) {
3558
+ onLogLine?.(
3559
+ `[selenium][warn] ?????????: ${mirrorError instanceof Error ? mirrorError.message : String(mirrorError)}`
3560
+ );
3561
+ onLogLine?.(`[selenium] ?? GitHub: ${github}`);
3562
+ await downloadToFile(github, archivePath);
3563
+ downloaded = true;
3564
+ }
3565
+ if (!downloaded) {
3566
+ throw new Error(`geckodriver download failed for ${tag}`);
3567
+ }
3486
3568
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3487
- await extractZip(zipPath, extractDir);
3569
+ await extractDriverArchive(archivePath, extractDir, archiveKind);
3488
3570
  const found = await findFileRecursive(extractDir, geckodriverExeName());
3489
3571
  if (!found) {
3490
- throw new Error(`geckodriver binary not found after extracting ${assetName}`);
3572
+ throw new Error(`geckodriver binary not found after extracting ${fileName}`);
3491
3573
  }
3492
3574
  const dest = import_node_path4.default.join(driversDir, geckodriverExeName());
3493
3575
  await copyExecutable(found, dest);
3494
- await import_promises4.default.rm(zipPath, { force: true });
3576
+ await import_promises4.default.rm(archivePath, { force: true });
3495
3577
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3496
- onLogLine?.(`[selenium] geckodriver \u5DF2\u5199\u5165: ${dest}`);
3578
+ onLogLine?.(`[selenium] geckodriver ???: ${dest}`);
3497
3579
  return { path: dest, version: tag };
3498
3580
  }
3499
3581
  async function downloadChromedriver(driversDir, versionInput, onLogLine) {
@@ -3653,7 +3735,7 @@ async function ensureNativeWebDrivers(options = {}) {
3653
3735
  }
3654
3736
  return resolved;
3655
3737
  }
3656
- var import_node_child_process, import_promises4, import_node_path4, DEFAULT_NATIVE_DRIVERS_DIR, CHROME_FOR_TESTING_JSON;
3738
+ var import_node_child_process, import_promises4, import_node_path4, DEFAULT_NATIVE_DRIVERS_DIR, CHROME_FOR_TESTING_JSON, DEFAULT_GECKODRIVER_MIRROR, GITHUB_GECKODRIVER_RELEASES, SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES;
3657
3739
  var init_src2 = __esm({
3658
3740
  "../../packages/native-drivers/src/index.ts"() {
3659
3741
  "use strict";
@@ -3662,6 +3744,46 @@ var init_src2 = __esm({
3662
3744
  import_node_path4 = __toESM(require("node:path"), 1);
3663
3745
  DEFAULT_NATIVE_DRIVERS_DIR = "dirver";
3664
3746
  CHROME_FOR_TESTING_JSON = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json";
3747
+ DEFAULT_GECKODRIVER_MIRROR = "https://mirrors.huaweicloud.com/geckodriver";
3748
+ GITHUB_GECKODRIVER_RELEASES = "https://github.com/mozilla/geckodriver/releases";
3749
+ SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES = [
3750
+ {
3751
+ browser: "Chrome/Chromium",
3752
+ platforms: "Windows/Linux/macOS",
3753
+ vendor: "\u8C37\u6B4C",
3754
+ url: "https://chromedriver.storage.googleapis.com/index.html"
3755
+ },
3756
+ {
3757
+ browser: "Firefox",
3758
+ platforms: "Windows/Linux/macOS",
3759
+ vendor: "Mozilla / ?????",
3760
+ url: `${DEFAULT_GECKODRIVER_MIRROR}/v0.36.0/?????????????? ${GITHUB_GECKODRIVER_RELEASES}?`
3761
+ },
3762
+ {
3763
+ browser: "Edge",
3764
+ platforms: "win10",
3765
+ vendor: "\u5FAE\u8F6F",
3766
+ url: "https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/"
3767
+ },
3768
+ {
3769
+ browser: "Internet Explorer",
3770
+ platforms: "Windows",
3771
+ vendor: "Selenium \u9879\u76EE\u7EC4",
3772
+ url: "https://selenium-release.storage.googleapis.com/index.html"
3773
+ },
3774
+ {
3775
+ browser: "Safari",
3776
+ platforms: "macOS El Capitan \u53CA\u66F4\u9AD8\u7248\u672C",
3777
+ vendor: "\u82F9\u679C",
3778
+ url: "\uFF08\u7CFB\u7EDF\u5185\u7F6E\uFF0C\u65E0\u9700\u5355\u72EC\u4E0B\u8F7D\uFF09"
3779
+ },
3780
+ {
3781
+ browser: "Opera",
3782
+ platforms: "Windows/macOS/Linux",
3783
+ vendor: "Opera",
3784
+ url: "https://github.com/operasoftware/operachromiumdriver/releases"
3785
+ }
3786
+ ];
3665
3787
  }
3666
3788
  });
3667
3789
 
@@ -3818,7 +3940,14 @@ function briefErrorMessage(error2) {
3818
3940
  }
3819
3941
  return String(error2).split(/\r?\n/)[0]?.trim() || String(error2);
3820
3942
  }
3943
+ function depsVerboseEnabled() {
3944
+ return isTruthyEnv("ADA_DEPS_VERBOSE");
3945
+ }
3821
3946
  function shouldEmitPlaywrightCliLine(line) {
3947
+ if (depsVerboseEnabled()) {
3948
+ const t2 = line.trim();
3949
+ return t2.length > 0 && !/^\s*at\s/.test(t2) && !t2.includes("processTicksAndRejections");
3950
+ }
3822
3951
  const t = line.trim();
3823
3952
  if (!t) {
3824
3953
  return false;
@@ -3848,7 +3977,8 @@ function summarizePlaywrightCliLine(line) {
3848
3977
  return "[playwright][warn] \u5F53\u524D\u955C\u50CF\u672A\u5B89\u88C5\u6210\u529F";
3849
3978
  }
3850
3979
  if (/^Progress:/i.test(t)) {
3851
- return null;
3980
+ const pct = t.match(/(\d{1,3})%/)?.[1];
3981
+ return pct ? `[playwright] \u4E0B\u8F7D\u8FDB\u5EA6 ${pct}%` : "[playwright] \u4E0B\u8F7D\u4E2D\u2026";
3852
3982
  }
3853
3983
  if (t.length > 200) {
3854
3984
  return `[playwright] ${t.slice(0, 120)}\u2026`;
@@ -3860,15 +3990,27 @@ function createPlaywrightInstallLogSink(onLogLine) {
3860
3990
  return void 0;
3861
3991
  }
3862
3992
  const seen = /* @__PURE__ */ new Set();
3993
+ let lastProgressEmitAt = 0;
3863
3994
  return (line) => {
3864
3995
  if (!shouldEmitPlaywrightCliLine(line)) {
3865
3996
  return;
3866
3997
  }
3998
+ if (depsVerboseEnabled()) {
3999
+ onLogLine(line.trimEnd());
4000
+ return;
4001
+ }
3867
4002
  const summary = summarizePlaywrightCliLine(line);
3868
4003
  if (!summary) {
3869
4004
  return;
3870
4005
  }
3871
- if (seen.has(summary)) {
4006
+ const isProgress = /^Progress:/i.test(line.trim());
4007
+ if (isProgress) {
4008
+ const now = Date.now();
4009
+ if (now - lastProgressEmitAt < 12e3) {
4010
+ return;
4011
+ }
4012
+ lastProgressEmitAt = now;
4013
+ } else if (seen.has(summary)) {
3872
4014
  return;
3873
4015
  }
3874
4016
  seen.add(summary);
@@ -4045,7 +4187,13 @@ function installStrategyTimeoutMs() {
4045
4187
  return parsePositiveMs(process.env.ADA_INSTALL_STRATEGY_TIMEOUT_MS, 12e4);
4046
4188
  }
4047
4189
  function playwrightInstallTimeoutMs() {
4048
- return parsePositiveMs(process.env.ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS, 9e5);
4190
+ return parsePositiveMs(process.env.ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS, 18e5);
4191
+ }
4192
+ function playwrightInstallAttemptTimeoutMs(attemptIndex, baseTimeoutMs) {
4193
+ if (attemptIndex <= 0) {
4194
+ return baseTimeoutMs;
4195
+ }
4196
+ return Math.min(baseTimeoutMs, 6e5);
4049
4197
  }
4050
4198
  function majorOf(versionLike) {
4051
4199
  const text = versionLike.trim().replace(/^v/i, "");
@@ -4102,6 +4250,32 @@ function appiumDriverPackageName(driver) {
4102
4250
  }
4103
4251
  return null;
4104
4252
  }
4253
+ function resolveAppiumDriverInstallTargets(driver, appiumMajor, compatibleSpecs) {
4254
+ const pkg = appiumDriverPackageName(driver);
4255
+ if (appiumMajor !== null && appiumMajor >= 3) {
4256
+ if (!APPIUM3_BUILTIN_DRIVER_NAMES.has(driver)) {
4257
+ const envHarmony = process.env.ADA_APPIUM_DRIVER_SPEC_HARMONYOS?.trim();
4258
+ if (driver === "harmonyos" && (envHarmony || pkg)) {
4259
+ return [envHarmony || pkg];
4260
+ }
4261
+ return pkg ? [pkg] : [driver];
4262
+ }
4263
+ const envOverride = driver === "uiautomator2" ? process.env.ADA_APPIUM_DRIVER_SPEC_UIAUTOMATOR2?.trim() : driver === "xcuitest" ? process.env.ADA_APPIUM_DRIVER_SPEC_XCUITEST?.trim() : "";
4264
+ if (envOverride) {
4265
+ return [envOverride, driver];
4266
+ }
4267
+ return [driver];
4268
+ }
4269
+ const baseTarget = pkg ?? driver;
4270
+ return compatibleSpecs.length > 0 ? compatibleSpecs : [baseTarget];
4271
+ }
4272
+ function buildAppiumDriverInstallArgs(target, appiumMajor) {
4273
+ const useNpmPackageSpec = appiumMajor === null || appiumMajor < 3 || target.includes("@") || target.startsWith("appium-") || target.includes("/");
4274
+ if (useNpmPackageSpec) {
4275
+ return ["exec", "appium", "driver", "install", "--source=npm", target];
4276
+ }
4277
+ return ["exec", "appium", "driver", "install", target];
4278
+ }
4105
4279
  async function getAppiumMajorVersion() {
4106
4280
  let version2 = "";
4107
4281
  try {
@@ -4149,12 +4323,73 @@ function stepMeta(stage) {
4149
4323
  stepTotal: PROGRESS_STEPS.length
4150
4324
  };
4151
4325
  }
4326
+ function formatProgressDetail(stage, details) {
4327
+ if (!details) {
4328
+ return null;
4329
+ }
4330
+ if (stage === "registry.probe.result") {
4331
+ const candidate = String(details.candidate ?? "");
4332
+ const latency = details.latencyMs;
4333
+ return `[deps] npm \u955C\u50CF ${candidate} -> ${latency === null || latency === void 0 ? "fail" : `${latency}ms`}`;
4334
+ }
4335
+ if (stage === "playwright.host.probe.result") {
4336
+ const candidate = String(details.candidate ?? "");
4337
+ const latency = details.latencyMs;
4338
+ const artifactOk = details.artifactOk === true;
4339
+ const tail = artifactOk ? "\uFF0C\u6D4F\u89C8\u5668\u5305\u53EF\u4E0B\u8F7D" : latency === null || latency === void 0 ? "" : "\uFF0C\u4EC5\u8FDE\u901A";
4340
+ return `[playwright] CDN ${candidate} -> ${latency === null || latency === void 0 ? "fail" : `${latency}ms`}${tail}`;
4341
+ }
4342
+ if (stage === "packages.install.done") {
4343
+ const strategy = String(details.strategy ?? "");
4344
+ return strategy ? `[deps] \u5305\u5B89\u88C5\u5B8C\u6210\uFF08${strategy}\uFF09` : null;
4345
+ }
4346
+ if (stage === "playwright.browser.install.done") {
4347
+ const host = String(details.selectedHost ?? "");
4348
+ const attempt = details.attempt;
4349
+ return host ? `[playwright] \u6D4F\u89C8\u5668\u5B89\u88C5\u5B8C\u6210\uFF08${host}${attempt ? `\uFF0C\u7B2C ${attempt} \u4E2A\u955C\u50CF` : ""}\uFF09` : null;
4350
+ }
4351
+ return null;
4352
+ }
4353
+ function structuredLogToHuman(level, payload) {
4354
+ const { event, details } = payload;
4355
+ const d = details && typeof details === "object" ? details : {};
4356
+ if (event === "deps.playwright.browser.install.host.fail") {
4357
+ return `[playwright][warn] \u955C\u50CF ${d.host} \u5931\u8D25 (${d.attempt}): ${d.message}`;
4358
+ }
4359
+ if (event === "deps.install.strategy.try") {
4360
+ return `[deps] \u5C1D\u8BD5\u5B89\u88C5\u7B56\u7565: ${d.strategy}`;
4361
+ }
4362
+ if (event === "deps.install.strategy.ok") {
4363
+ return `[deps] \u5305\u5B89\u88C5\u6210\u529F: ${d.strategy}`;
4364
+ }
4365
+ if (event === "deps.install.strategy.fail") {
4366
+ return `[deps][warn] \u5B89\u88C5\u7B56\u7565\u5931\u8D25 (${d.strategy}): ${d.message}`;
4367
+ }
4368
+ if (event === "deps.registry.auto-selected") {
4369
+ return `[deps] \u9009\u7528 npm \u955C\u50CF: ${d.selected}`;
4370
+ }
4371
+ if (event === "deps.playwright.host.auto-selected") {
4372
+ return `[playwright] CDN \u6D4B\u901F\u6392\u5E8F: ${Array.isArray(d.ranked) ? d.ranked.join(" -> ") : d.selected}`;
4373
+ }
4374
+ if (level === "warn" || level === "error") {
4375
+ const msg = d.message ?? d.detail;
4376
+ return `[deps][${level}] ${event}${msg ? `: ${msg}` : ""}`;
4377
+ }
4378
+ if (depsVerboseEnabled()) {
4379
+ return `[deps] ${event}${Object.keys(d).length > 0 ? ` ${JSON.stringify(d)}` : ""}`;
4380
+ }
4381
+ return null;
4382
+ }
4152
4383
  function progress(stage, details) {
4153
4384
  if (depsHumanLog) {
4154
4385
  const label = PROGRESS_HUMAN_LABELS[stage];
4155
4386
  if (label) {
4156
4387
  depsHumanLog(label);
4157
4388
  }
4389
+ const detail = formatProgressDetail(stage, details);
4390
+ if (detail) {
4391
+ depsHumanLog(detail);
4392
+ }
4158
4393
  return;
4159
4394
  }
4160
4395
  const meta3 = stepMeta(stage);
@@ -4169,6 +4404,10 @@ function progress(stage, details) {
4169
4404
  }
4170
4405
  function depsStructuredLog(level, payload) {
4171
4406
  if (depsHumanLog) {
4407
+ const human = structuredLogToHuman(level, payload);
4408
+ if (human) {
4409
+ depsHumanLog(human);
4410
+ }
4172
4411
  return;
4173
4412
  }
4174
4413
  log(level, payload);
@@ -4267,12 +4506,42 @@ function playwrightProbeUrls(host) {
4267
4506
  }
4268
4507
  return [h];
4269
4508
  }
4509
+ function isChinaFriendlyNpmRegistry(registry2) {
4510
+ const r = registry2.toLowerCase();
4511
+ return CHINA_NPM_REGISTRY_HINTS.some((hint) => r.includes(hint));
4512
+ }
4513
+ function isTruthyEnv(name) {
4514
+ const s = String(process.env[name] ?? "").trim().toLowerCase();
4515
+ return s === "1" || s === "true" || s === "yes" || s === "on";
4516
+ }
4517
+ function playwrightHostConfiguredByUser(config2) {
4518
+ if (process.env.ADA_PLAYWRIGHT_HOST_FROM_PREINSTALL === "1") {
4519
+ return "";
4520
+ }
4521
+ const raw = process.env.PLAYWRIGHT_DOWNLOAD_HOST?.trim() || playwrightDownloadHost(config2).trim();
4522
+ return raw ? normalizeHostUrl(raw) : "";
4523
+ }
4524
+ function preferPlaywrightHostsForNpmRegistry(ranked, npmRegistry) {
4525
+ if (!isTruthyEnv("ADA_PLAYWRIGHT_PREFER_CN_MIRROR") && !isChinaFriendlyNpmRegistry(npmRegistry)) {
4526
+ return ranked;
4527
+ }
4528
+ const seen = /* @__PURE__ */ new Set();
4529
+ const out = [];
4530
+ for (const url2 of [...CHINA_PLAYWRIGHT_HOST_PRIORITY, ...ranked]) {
4531
+ const n = normalizeHostUrl(url2);
4532
+ if (!seen.has(n)) {
4533
+ seen.add(n);
4534
+ out.push(n);
4535
+ }
4536
+ }
4537
+ return out;
4538
+ }
4270
4539
  function playwrightHostCandidates(config2) {
4271
- const configured = normalizeHostUrl(playwrightDownloadHost(config2));
4540
+ const configured = playwrightHostConfiguredByUser(config2);
4272
4541
  const fromConfig = Array.isArray(config2.dependencies.playwrightHostCandidates) ? config2.dependencies.playwrightHostCandidates.map((x) => normalizeHostUrl(String(x).trim())).filter(Boolean) : [];
4273
4542
  const configuredList = fromConfig.length > 0 ? fromConfig : [...DEFAULT_PLAYWRIGHT_HOST_CANDIDATES];
4274
4543
  const extra = process.env.ADA_PLAYWRIGHT_HOST_CANDIDATES ? process.env.ADA_PLAYWRIGHT_HOST_CANDIDATES.split(",").map((x) => normalizeHostUrl(x.trim())).filter(Boolean) : [];
4275
- const ordered = [configured, ...configuredList, ...extra];
4544
+ const ordered = configured ? [configured, ...configuredList, ...extra] : [...configuredList, ...extra];
4276
4545
  const seen = /* @__PURE__ */ new Set();
4277
4546
  const out = [];
4278
4547
  for (const url2 of ordered) {
@@ -4408,6 +4677,7 @@ async function runInstallWithPriority(config2, packages, onLogLine) {
4408
4677
  let lastError = void 0;
4409
4678
  for (const strategy of strategies) {
4410
4679
  try {
4680
+ onLogLine?.(`[deps] \u5C1D\u8BD5 ${strategy.name} \u5B89\u88C5\u5305\u2026`);
4411
4681
  depsStructuredLog("info", { event: "deps.install.strategy.try", details: { strategy: strategy.name, packages } });
4412
4682
  await strategy.run();
4413
4683
  depsStructuredLog("info", { event: "deps.install.strategy.ok", details: { strategy: strategy.name } });
@@ -4431,13 +4701,14 @@ async function runAppiumDriverInstallWithPriority(config2, driver, onLogLine) {
4431
4701
  onLogLine?.(`[appium] \u5B89\u88C5\u9A71\u52A8: ${driver}`);
4432
4702
  const compatibleSpecs = await resolveCompatibleDriverSpecs(driver);
4433
4703
  const appiumMajor = await getAppiumMajorVersion();
4434
- const directPackage = appiumDriverPackageName(driver);
4435
- const baseTarget = directPackage ?? driver;
4436
- const targets = appiumMajor !== null && appiumMajor < 3 ? compatibleSpecs.length > 0 ? compatibleSpecs : [baseTarget] : [baseTarget, ...compatibleSpecs];
4437
- const uniqueTargets = Array.from(new Set(targets.filter(Boolean)));
4704
+ if (appiumMajor !== null && appiumMajor >= 3) {
4705
+ onLogLine?.(`[appium] \u68C0\u6D4B\u5230 Appium ${appiumMajor}.x\uFF0C\u4F7F\u7528\u5B98\u65B9\u9A71\u52A8\u540D\u5B89\u88C5\uFF08\u5982 uiautomator2\uFF09`);
4706
+ }
4707
+ const uniqueTargets = resolveAppiumDriverInstallTargets(driver, appiumMajor, compatibleSpecs);
4438
4708
  let lastError = void 0;
4439
4709
  for (const target of uniqueTargets) {
4440
- const installArgs = ["exec", "appium", "driver", "install", "--source=npm", target];
4710
+ const installArgs = buildAppiumDriverInstallArgs(target, appiumMajor);
4711
+ onLogLine?.(`[appium] \u6267\u884C: npm ${installArgs.join(" ")}`);
4441
4712
  const strategies = [
4442
4713
  {
4443
4714
  name: "npm",
@@ -4524,24 +4795,32 @@ async function installPlaywrightBrowser(config2, onLogLine, options) {
4524
4795
  if (targets.length > 0) {
4525
4796
  installArgs.push(...targets);
4526
4797
  }
4527
- const rankedHosts = await rankPlaywrightHosts(config2);
4528
- const timeoutMs = playwrightInstallTimeoutMs();
4529
- onLogLine?.(`[playwright] \u5B89\u88C5\u8D85\u65F6\u4E0A\u9650 ${Math.round(timeoutMs / 1e3)}s\uFF08\u53EF\u7528 ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS \u8C03\u6574\uFF09`);
4798
+ const npmRegistry = await detectBestRegistry(config2, npmProxyRegistry());
4799
+ let rankedHosts = await rankPlaywrightHosts(config2);
4800
+ rankedHosts = preferPlaywrightHostsForNpmRegistry(rankedHosts, npmRegistry);
4801
+ const baseTimeoutMs = playwrightInstallTimeoutMs();
4802
+ onLogLine?.(
4803
+ `[playwright] \u5B89\u88C5\u8D85\u65F6\uFF1A\u9996\u4E2A\u955C\u50CF ${Math.round(baseTimeoutMs / 1e3)}s\uFF0C\u540E\u7EED\u955C\u50CF\u81F3\u591A 600s\uFF08ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS \u53EF\u8C03\uFF09`
4804
+ );
4805
+ if (isChinaFriendlyNpmRegistry(npmRegistry) || isTruthyEnv("ADA_PLAYWRIGHT_PREFER_CN_MIRROR")) {
4806
+ onLogLine?.(`[playwright] \u56FD\u5185 npm \u6E90 ${npmRegistry}\uFF0C\u4F18\u5148 npmmirror Playwright CDN`);
4807
+ }
4530
4808
  let lastHost = "";
4531
4809
  for (let i = 0; i < rankedHosts.length; i++) {
4532
4810
  const host = rankedHosts[i];
4811
+ const attemptTimeoutMs = playwrightInstallAttemptTimeoutMs(i, baseTimeoutMs);
4533
4812
  lastHost = host;
4534
4813
  if (i > 0) {
4535
- onLogLine?.(`[playwright] \u6362\u955C\u50CF: ${host}`);
4814
+ onLogLine?.(`[playwright] \u6362\u955C\u50CF (${i + 1}/${rankedHosts.length}): ${host}`);
4536
4815
  } else {
4537
4816
  onLogLine?.(
4538
- `[playwright] playwright@${version2}\uFF0CCDN ${host}\uFF0C\u76EE\u6807 ${targets.length ? targets.join(",") : "chromium"}`
4817
+ `[playwright] playwright@${version2}\uFF0CCDN ${host}\uFF08${i + 1}/${rankedHosts.length}\uFF09\uFF0C\u76EE\u6807 ${targets.length ? targets.join(",") : "chromium"}`
4539
4818
  );
4540
4819
  }
4541
4820
  try {
4542
4821
  await runCommand2(command, installArgs, {
4543
4822
  env: { PLAYWRIGHT_DOWNLOAD_HOST: host },
4544
- timeoutMs,
4823
+ timeoutMs: attemptTimeoutMs,
4545
4824
  onLogLine: createPlaywrightInstallLogSink(onLogLine)
4546
4825
  });
4547
4826
  progress("playwright.browser.install.done", { selectedHost: host, attempt: i + 1 });
@@ -4560,7 +4839,7 @@ async function installPlaywrightBrowser(config2, onLogLine, options) {
4560
4839
  }
4561
4840
  }
4562
4841
  onLogLine?.(
4563
- `[playwright][warn] \u6D4F\u89C8\u5668\u672A\u5B89\u88C5\u5B8C\u6210\uFF08\u5DF2\u5C1D\u8BD5 ${rankedHosts.length} \u4E2A CDN\uFF09\u3002\u53EF\u8BBE\u7F6E PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.playwright.dev \u540E\u91CD\u8BD5\uFF0C\u6216\u6267\u884C: npx playwright@${PINNED_PLAYWRIGHT_VERSION} install chromium`
4842
+ `[playwright][warn] \u6D4F\u89C8\u5668\u672A\u5B89\u88C5\u5B8C\u6210\uFF08\u5DF2\u5C1D\u8BD5 ${rankedHosts.length} \u4E2A CDN\uFF09\u3002\u56FD\u5185\u5EFA\u8BAE: set PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright \u5E76\u589E\u5927 ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS=1800000\uFF1B\u6216\u624B\u52A8: npx playwright@${PINNED_PLAYWRIGHT_VERSION} install chromium`
4564
4843
  );
4565
4844
  if (lastHost) {
4566
4845
  onLogLine?.(`[playwright][warn] \u6700\u540E\u5C1D\u8BD5: ${lastHost}`);
@@ -5052,7 +5331,7 @@ async function getDependencyHealth(config2) {
5052
5331
  missingAppiumDrivers
5053
5332
  };
5054
5333
  }
5055
- var import_node_module, import_node_fs, import_node_child_process2, import_promises5, import_node_path5, require2, PINNED_PLAYWRIGHT_VERSION, PINNED_ZOD_VERSION, depsHumanLog, DEFAULT_NPM_REGISTRY_CANDIDATES, detectedBestRegistryByKey, PROGRESS_STEPS, PROGRESS_HUMAN_LABELS, DEFAULT_PLAYWRIGHT_HOST_CANDIDATES, PLAYWRIGHT_HOST_FALLBACK, PW_INSTALL_TARGETS;
5334
+ var import_node_module, import_node_fs, import_node_child_process2, import_promises5, import_node_path5, require2, PINNED_PLAYWRIGHT_VERSION, PINNED_ZOD_VERSION, depsHumanLog, DEFAULT_NPM_REGISTRY_CANDIDATES, APPIUM3_BUILTIN_DRIVER_NAMES, detectedBestRegistryByKey, PROGRESS_STEPS, PROGRESS_HUMAN_LABELS, DEFAULT_PLAYWRIGHT_HOST_CANDIDATES, CHINA_NPM_REGISTRY_HINTS, CHINA_PLAYWRIGHT_HOST_PRIORITY, PLAYWRIGHT_HOST_FALLBACK, PW_INSTALL_TARGETS;
5056
5335
  var init_dependency_installer = __esm({
5057
5336
  "../ada-agent/src/dependency-installer.ts"() {
5058
5337
  import_node_module = require("node:module");
@@ -5072,6 +5351,16 @@ var init_dependency_installer = __esm({
5072
5351
  "https://repo.huaweicloud.com/repository/npm",
5073
5352
  "https://registry.npmjs.org"
5074
5353
  ];
5354
+ APPIUM3_BUILTIN_DRIVER_NAMES = /* @__PURE__ */ new Set([
5355
+ "uiautomator2",
5356
+ "xcuitest",
5357
+ "espresso",
5358
+ "mac2",
5359
+ "windows",
5360
+ "safari",
5361
+ "gecko",
5362
+ "chromium"
5363
+ ]);
5075
5364
  detectedBestRegistryByKey = /* @__PURE__ */ new Map();
5076
5365
  PROGRESS_STEPS = [
5077
5366
  "deps.ensure.start",
@@ -5086,9 +5375,16 @@ var init_dependency_installer = __esm({
5086
5375
  "deps.ensure.start": "[deps] \u5F00\u59CB\u68C0\u6D4B\u4F9D\u8D56",
5087
5376
  "registry.probe.start": "[deps] \u63A2\u6D4B npm \u955C\u50CF\u2026",
5088
5377
  "packages.install.start": "[deps] \u5B89\u88C5 npm \u5305\u2026",
5378
+ "packages.install.done": "[deps] npm \u5305\u5B89\u88C5\u5B8C\u6210",
5089
5379
  "playwright.host.probe.start": "[playwright] \u63A2\u6D4B\u6D4F\u89C8\u5668 CDN\u2026",
5090
5380
  "playwright.browser.install.start": "[playwright] \u5B89\u88C5\u6D4F\u89C8\u5668\u2026",
5381
+ "playwright.browser.install.done": "[playwright] \u6D4F\u89C8\u5668\u5B89\u88C5\u5B8C\u6210",
5382
+ "playwright.selfcheck.start": "[playwright] \u81EA\u68C0\u2026",
5383
+ "playwright.selfcheck.done": "[playwright] \u81EA\u68C0\u5B8C\u6210",
5091
5384
  "appium.driver.ensure.start": "[appium] \u5B89\u88C5\u9A71\u52A8\u2026",
5385
+ "appium.driver.ensure.done": "[appium] \u9A71\u52A8\u5B89\u88C5\u5B8C\u6210",
5386
+ "selenium.check.start": "[selenium] \u68C0\u6D4B\u539F\u751F\u9A71\u52A8\u2026",
5387
+ "selenium.check.done": "[selenium] \u68C0\u6D4B\u5B8C\u6210",
5092
5388
  "deps.ensure.done": "[deps] \u4F9D\u8D56\u68C0\u6D4B\u5B8C\u6210"
5093
5389
  };
5094
5390
  DEFAULT_PLAYWRIGHT_HOST_CANDIDATES = [
@@ -5097,6 +5393,11 @@ var init_dependency_installer = __esm({
5097
5393
  "https://npmmirror.com/mirrors/playwright",
5098
5394
  "https://cdn.npmmirror.com/binaries/playwright"
5099
5395
  ];
5396
+ CHINA_NPM_REGISTRY_HINTS = ["npmmirror.com", "tencent", "huaweicloud", "huawei.com"];
5397
+ CHINA_PLAYWRIGHT_HOST_PRIORITY = [
5398
+ "https://cdn.npmmirror.com/binaries/playwright",
5399
+ "https://npmmirror.com/mirrors/playwright"
5400
+ ];
5100
5401
  PLAYWRIGHT_HOST_FALLBACK = DEFAULT_PLAYWRIGHT_HOST_CANDIDATES[0];
5101
5402
  PW_INSTALL_TARGETS = /* @__PURE__ */ new Set([
5102
5403
  "chromium",
@@ -32976,7 +33277,9 @@ function resolveBootstrapInstallDeps(argv2) {
32976
33277
  }
32977
33278
  var PREINSTALL_PLAYWRIGHT_HOST_ALLOW = /* @__PURE__ */ new Set([
32978
33279
  "https://cdn.playwright.dev",
32979
- "https://playwright.azureedge.net"
33280
+ "https://playwright.azureedge.net",
33281
+ "https://cdn.npmmirror.com/binaries/playwright",
33282
+ "https://npmmirror.com/mirrors/playwright"
32980
33283
  ]);
32981
33284
  function normalizePlaywrightHost(url2) {
32982
33285
  return url2.replace(/\/$/, "");
@@ -32994,7 +33297,8 @@ function applyPreinstallPlaywrightHostFile() {
32994
33297
  const host = normalizePlaywrightHost(import_node_fs3.default.readFileSync(file2, "utf8").trim());
32995
33298
  if (host.length > 0 && PREINSTALL_PLAYWRIGHT_HOST_ALLOW.has(host)) {
32996
33299
  process.env.PLAYWRIGHT_DOWNLOAD_HOST = host;
32997
- console.error(`[ADA-MCP] using playwright CDN from ${file2}: ${host}`);
33300
+ process.env.ADA_PLAYWRIGHT_HOST_FROM_PREINSTALL = "1";
33301
+ console.error(`[ADA-MCP] using playwright CDN from ${file2}: ${host} (install-deps \u5C06\u91CD\u65B0\u6D4B\u901F\u6392\u5E8F)`);
32998
33302
  return;
32999
33303
  }
33000
33304
  if (host.length > 0) {
@@ -33012,7 +33316,7 @@ function ensureDefaultInstallTimeouts() {
33012
33316
  process.env.ADA_INSTALL_STRATEGY_TIMEOUT_MS = "120000";
33013
33317
  }
33014
33318
  if (!process.env.ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS?.trim()) {
33015
- process.env.ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS = "900000";
33319
+ process.env.ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS = "1800000";
33016
33320
  }
33017
33321
  }
33018
33322
  async function runBootstrapInstallDeps(argv2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-mcp/mcp-server",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "ADA MCP server for web/mobile automation (stdio + remote HTTP)",
5
5
  "private": false,
6
6
  "type": "commonjs",
@@ -232,6 +232,46 @@ var import_node_child_process = require("node:child_process");
232
232
  var import_promises = __toESM(require("node:fs/promises"), 1);
233
233
  var import_node_path = __toESM(require("node:path"), 1);
234
234
  var DEFAULT_NATIVE_DRIVERS_DIR = "dirver";
235
+ var DEFAULT_GECKODRIVER_MIRROR = "https://mirrors.huaweicloud.com/geckodriver";
236
+ var GITHUB_GECKODRIVER_RELEASES = "https://github.com/mozilla/geckodriver/releases";
237
+ var SELENIUM_DRIVER_MANUAL_DOWNLOAD_REFERENCES = [
238
+ {
239
+ browser: "Chrome/Chromium",
240
+ platforms: "Windows/Linux/macOS",
241
+ vendor: "\u8C37\u6B4C",
242
+ url: "https://chromedriver.storage.googleapis.com/index.html"
243
+ },
244
+ {
245
+ browser: "Firefox",
246
+ platforms: "Windows/Linux/macOS",
247
+ vendor: "Mozilla / ?????",
248
+ url: `${DEFAULT_GECKODRIVER_MIRROR}/v0.36.0/?????????????? ${GITHUB_GECKODRIVER_RELEASES}?`
249
+ },
250
+ {
251
+ browser: "Edge",
252
+ platforms: "win10",
253
+ vendor: "\u5FAE\u8F6F",
254
+ url: "https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/"
255
+ },
256
+ {
257
+ browser: "Internet Explorer",
258
+ platforms: "Windows",
259
+ vendor: "Selenium \u9879\u76EE\u7EC4",
260
+ url: "https://selenium-release.storage.googleapis.com/index.html"
261
+ },
262
+ {
263
+ browser: "Safari",
264
+ platforms: "macOS El Capitan \u53CA\u66F4\u9AD8\u7248\u672C",
265
+ vendor: "\u82F9\u679C",
266
+ url: "\uFF08\u7CFB\u7EDF\u5185\u7F6E\uFF0C\u65E0\u9700\u5355\u72EC\u4E0B\u8F7D\uFF09"
267
+ },
268
+ {
269
+ browser: "Opera",
270
+ platforms: "Windows/macOS/Linux",
271
+ vendor: "Opera",
272
+ url: "https://github.com/operasoftware/operachromiumdriver/releases"
273
+ }
274
+ ];
235
275
  async function fileExists(filePath) {
236
276
  try {
237
277
  await import_promises.default.access(filePath);
@@ -62,11 +62,16 @@ async function main() {
62
62
  console.error(`[ada-mcp preinstall] registry ${candidate} -> ${latency === null ? "fail" : `${latency}ms`}`);
63
63
  }
64
64
 
65
- /** preinstall 阶段依赖尚未安装,无法校验浏览器包是否存在;仅测官方 CDN,避免写入国内镜像后 404 */
66
- const pwCandidates = [
67
- "https://cdn.playwright.dev",
68
- "https://playwright.azureedge.net"
69
- ];
65
+ const chinaRegistry = /npmmirror|tencent|huaweicloud|huawei\.com/i.test(reg.best);
66
+ /** preinstall playwright 包时无法 HEAD 浏览器包;国内 registry 时优先写入 npmmirror CDN */
67
+ const pwCandidates = chinaRegistry
68
+ ? [
69
+ "https://cdn.npmmirror.com/binaries/playwright",
70
+ "https://npmmirror.com/mirrors/playwright",
71
+ "https://cdn.playwright.dev",
72
+ "https://playwright.azureedge.net"
73
+ ]
74
+ : ["https://cdn.playwright.dev", "https://playwright.azureedge.net"];
70
75
  const pw = await detectBestPlaywrightHost(pwCandidates);
71
76
  const hostFile = path.join(root, ".ada-mcp-playwright-host");
72
77
  fs.writeFileSync(hostFile, `${pw.best}\n`, "utf8");