@ada-mcp/launcher 0.1.41 → 0.1.43
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 +33 -22
- package/download-probe.mjs +113 -8
- package/mirror-candidates.mjs +47 -8
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @ada-mcp/launcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
启动器:**零 npm 依赖**;探测最快 npm 镜像后,经 **pnpm dlx** 或 **npx -y** 拉起 `@ada-mcp/mcp-server`。
|
|
4
|
+
|
|
4
5
|
## 标准 MCP 配置
|
|
5
6
|
|
|
6
7
|
```json
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
"mcpServers": {
|
|
9
10
|
"ada-mcp": {
|
|
10
11
|
"command": "pnpm",
|
|
11
|
-
"args": ["dlx", "@ada-mcp/launcher@0.1.
|
|
12
|
+
"args": ["dlx", "@ada-mcp/launcher@0.1.43"]
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
}
|
|
@@ -17,41 +18,51 @@
|
|
|
17
18
|
命令行:
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
|
-
pnpm dlx @ada-mcp/launcher@0.1.
|
|
21
|
+
pnpm dlx @ada-mcp/launcher@0.1.43
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
npx
|
|
24
|
+
npx 等价:
|
|
25
|
+
|
|
24
26
|
```bash
|
|
25
|
-
npx -y @ada-mcp/launcher@0.1.
|
|
27
|
+
npx -y @ada-mcp/launcher@0.1.43
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
**npx
|
|
30
|
+
**npx 示例**:
|
|
31
|
+
|
|
29
32
|
```json
|
|
30
33
|
{
|
|
31
34
|
"mcpServers": {
|
|
32
35
|
"ada-mcp": {
|
|
33
36
|
"command": "npx",
|
|
34
|
-
"args": ["-y", "@ada-mcp/launcher@0.1.
|
|
37
|
+
"args": ["-y", "@ada-mcp/launcher@0.1.43"]
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
## 版本
|
|
44
|
+
|
|
45
|
+
- **使用**:`pnpm dlx @ada-mcp/launcher` 或 `@ada-mcp/launcher@0.1.43` 均可;不写 `@x.y.z` 时拉 npm **latest**(推荐生产环境钉版本)。
|
|
46
|
+
- **发布**:每次 `npm publish` 前必须在 `package.json` **递增 version**;不能重复发布同一版本。
|
|
47
|
+
- **0.1.43**:修复 0.1.41 错误依赖未发布的 `@ada/download-probe`;探测逻辑已内联到本包。
|
|
48
|
+
|
|
49
|
+
## 可选环境变量
|
|
50
|
+
|
|
43
51
|
| 变量 | 说明 |
|
|
44
52
|
|------|------|
|
|
45
|
-
| `ADA_MCP_PACKAGE_RUNNER` | `pnpm` \| `npx` \| `auto
|
|
46
|
-
| `ADA_MCP_REGISTRY` | 强制 npm registry(跳过测速),如 `https://registry.
|
|
47
|
-
| `ADA_MCP_SERVER_VERSION` | 覆盖 mcp-server
|
|
48
|
-
| `ADA_MCP_SKIP_REGISTRY_PROBE` | `1`
|
|
49
|
-
| `ADA_MCP_INSTALL_DEPS`
|
|
50
|
-
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
+
| `ADA_MCP_PACKAGE_RUNNER` | `pnpm` \| `npx` \| `auto`(默认):拉取 mcp-server 用的包管理器;`auto` 时与外层一致 |
|
|
54
|
+
| `ADA_MCP_REGISTRY` | 强制 npm registry(跳过测速),如 `https://registry.npmmirror.com/`;**勿**使用 `pnpm dlx --registry`(pnpm 不支持) |
|
|
55
|
+
| `ADA_MCP_SERVER_VERSION` | 覆盖 mcp-server 版本;**未设置时**从所选 registry 读取 **`latest`**。低于 launcher 包版本会被抬高 |
|
|
56
|
+
| `ADA_MCP_SKIP_REGISTRY_PROBE` | `1` 时跳过测速 |
|
|
57
|
+
| `ADA_MCP_INSTALL_DEPS` 等 | 写在 `args` 末尾,传给 mcp-server(见 `@ada-mcp/mcp-server` README) |
|
|
58
|
+
|
|
59
|
+
## 与直接 `dlx @ada-mcp/mcp-server` 的区别
|
|
60
|
+
|
|
61
|
+
| 方式 | 拉 MCP 包 | 安装 playwright 等 |
|
|
53
62
|
|------|-----------|-------------------|
|
|
54
|
-
| **`pnpm dlx @ada-mcp/launcher@0.1.
|
|
55
|
-
| **`npx -y @ada-mcp/launcher@0.1.
|
|
56
|
-
| `pnpm dlx @ada-mcp/mcp-server@0.1.
|
|
57
|
-
| `npx -y @ada-mcp/mcp-server@0.1.
|
|
63
|
+
| **`pnpm dlx @ada-mcp/launcher@0.1.43`**(标准) | launcher 测速 + `.npmrc` → `pnpm dlx` mcp-server | mcp-server preinstall + bootstrap |
|
|
64
|
+
| **`npx -y @ada-mcp/launcher@0.1.43`** | 同上 → **`npx -y` mcp-server** | 同上 |
|
|
65
|
+
| `pnpm dlx @ada-mcp/mcp-server@0.1.43` | 本机默认源 | 有 preinstall / bootstrap 测速 |
|
|
66
|
+
| `npx -y @ada-mcp/mcp-server@0.1.43` | 无 launcher 测速 | 有 preinstall / bootstrap 测速 |
|
|
67
|
+
|
|
68
|
+
内置 registry 测速候选顺序(相同时靠前优先):**npmmirror(阿里)** → npmjs → 腾讯 → 上海交大 → 中科大 → 华为云。
|
package/download-probe.mjs
CHANGED
|
@@ -1,8 +1,113 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 内联 @ada/download-probe(零 npm 依赖;与 packages/download-probe 保持同步)
|
|
3
|
+
* 同步:node ../../scripts/sync-download-probe-vendor.mjs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 通过 Range 拉取固定字节样本,按实际下载速度(KB/s)排序镜像。
|
|
8
|
+
*/
|
|
9
|
+
function parsePositiveInt(raw, fallback) {
|
|
10
|
+
const parsed = raw ? Number(raw) : fallback;
|
|
11
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
12
|
+
}
|
|
13
|
+
export function probeSampleBytes() {
|
|
14
|
+
return parsePositiveInt(process.env.ADA_PROBE_DOWNLOAD_BYTES, 512 * 1024);
|
|
15
|
+
}
|
|
16
|
+
export function probeDownloadTimeoutMs() {
|
|
17
|
+
return parsePositiveInt(process.env.ADA_PROBE_DOWNLOAD_TIMEOUT_MS, 20_000);
|
|
18
|
+
}
|
|
19
|
+
/** 拉取 url 的前 sampleBytes 字节,返回吞吐;失败返回 null */
|
|
20
|
+
export async function probeDownloadSample(url, options) {
|
|
21
|
+
const sampleBytes = options?.sampleBytes ?? probeSampleBytes();
|
|
22
|
+
const timeoutMs = options?.timeoutMs ?? probeDownloadTimeoutMs();
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
25
|
+
const started = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(url, {
|
|
28
|
+
method: "GET",
|
|
29
|
+
headers: { Range: `bytes=0-${sampleBytes - 1}`, Accept: "*/*" },
|
|
30
|
+
redirect: "follow",
|
|
31
|
+
signal: controller.signal
|
|
32
|
+
});
|
|
33
|
+
if (response.status !== 200 && response.status !== 206) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const body = response.body;
|
|
37
|
+
if (!body) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const reader = body.getReader();
|
|
41
|
+
let bytesRead = 0;
|
|
42
|
+
try {
|
|
43
|
+
while (bytesRead < sampleBytes) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done || !value?.length) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
bytesRead += value.length;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
try {
|
|
53
|
+
await reader.cancel();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (bytesRead < Math.min(sampleBytes / 8, 32 * 1024)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const durationMs = Math.max(1, Date.now() - started);
|
|
63
|
+
const speedKBps = bytesRead / 1024 / (durationMs / 1000);
|
|
64
|
+
return { durationMs, bytesRead, speedKBps };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** 按 speedKBps 降序;相同时样本耗时更短优先 */
|
|
74
|
+
export function pickBestDownloadProbe(rows, priorityIndex) {
|
|
75
|
+
const ok = rows.filter((r) => r.probe !== null);
|
|
76
|
+
if (ok.length === 0) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
ok.sort((a, b) => {
|
|
80
|
+
if (b.probe.speedKBps !== a.probe.speedKBps) {
|
|
81
|
+
return b.probe.speedKBps - a.probe.speedKBps;
|
|
82
|
+
}
|
|
83
|
+
if (a.probe.durationMs !== b.probe.durationMs) {
|
|
84
|
+
return a.probe.durationMs - b.probe.durationMs;
|
|
85
|
+
}
|
|
86
|
+
return priorityIndex(a.candidate) - priorityIndex(b.candidate);
|
|
87
|
+
});
|
|
88
|
+
return ok[0] ?? null;
|
|
89
|
+
}
|
|
90
|
+
export function formatDownloadProbeLine(prefix, candidate, probe) {
|
|
91
|
+
if (!probe) {
|
|
92
|
+
return `${prefix} ${candidate} -> fail`;
|
|
93
|
+
}
|
|
94
|
+
const mib = (probe.bytesRead / (1024 * 1024)).toFixed(2);
|
|
95
|
+
return `${prefix} ${candidate} -> ${probe.speedKBps.toFixed(0)} KB/s(${mib} MiB / ${probe.durationMs}ms)`;
|
|
96
|
+
}
|
|
97
|
+
/** 对 URL 列表测速,返回最快项 */
|
|
98
|
+
export async function pickFastestProbeUrl(urls, onLogLine) {
|
|
99
|
+
let best = null;
|
|
100
|
+
for (const url of urls) {
|
|
101
|
+
onLogLine?.(`[probe] 探测下载速度: ${url}`);
|
|
102
|
+
const probe = await probeDownloadSample(url);
|
|
103
|
+
if (!probe) {
|
|
104
|
+
onLogLine?.(`[probe] ${url} -> fail`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
onLogLine?.(`[probe] ${url} -> ${probe.speedKBps.toFixed(0)} KB/s(${(probe.bytesRead / (1024 * 1024)).toFixed(2)} MiB / ${probe.durationMs}ms)`);
|
|
108
|
+
if (!best || probe.speedKBps > best.probe.speedKBps) {
|
|
109
|
+
best = { url, probe };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return best;
|
|
113
|
+
}
|
package/mirror-candidates.mjs
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 内联 @ada/download-probe 镜像候选(零 npm 依赖)
|
|
3
|
+
* 同步:node ../../scripts/sync-download-probe-vendor.mjs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 常用下载源候选(npm registry / Playwright CDN / geckodriver 镜像)
|
|
8
|
+
*/
|
|
9
|
+
/** 国内优先 npmmirror;测速相同时列表靠前者优先 */
|
|
10
|
+
/** 国内优先 npmmirror,其次官方;测速相同时列表靠前者优先 */
|
|
11
|
+
export const DEFAULT_NPM_REGISTRY_CANDIDATES = [
|
|
12
|
+
"https://registry.npmmirror.com",
|
|
13
|
+
"https://registry.npmjs.org",
|
|
14
|
+
"https://mirrors.cloud.tencent.com/npm",
|
|
15
|
+
"https://mirrors.sjtug.sjtu.edu.cn/npm-registry",
|
|
16
|
+
"https://npmreg.proxy.ustclug.org",
|
|
17
|
+
"https://repo.huaweicloud.com/repository/npm"
|
|
18
|
+
];
|
|
19
|
+
export const CHINA_NPM_REGISTRY_HINTS = [
|
|
20
|
+
"npmmirror",
|
|
21
|
+
"sjtug",
|
|
22
|
+
"sjtu",
|
|
23
|
+
"ustc",
|
|
24
|
+
"ustclug",
|
|
25
|
+
"tencent",
|
|
26
|
+
"huaweicloud",
|
|
27
|
+
"huawei.com"
|
|
28
|
+
];
|
|
29
|
+
export const DEFAULT_PLAYWRIGHT_HOST_CANDIDATES = [
|
|
30
|
+
"https://cdn.playwright.dev",
|
|
31
|
+
"https://playwright.azureedge.net",
|
|
32
|
+
"https://cdn.npmmirror.com/binaries/playwright",
|
|
33
|
+
"https://npmmirror.com/mirrors/playwright"
|
|
34
|
+
];
|
|
35
|
+
export const CHINA_PLAYWRIGHT_HOST_PRIORITY = [
|
|
36
|
+
"https://cdn.npmmirror.com/binaries/playwright",
|
|
37
|
+
"https://npmmirror.com/mirrors/playwright"
|
|
38
|
+
];
|
|
39
|
+
export const DEFAULT_GECKODRIVER_MIRROR_CANDIDATES = [
|
|
40
|
+
"https://cdn.npmmirror.com/binaries/geckodriver",
|
|
41
|
+
"https://npmmirror.com/mirrors/geckodriver",
|
|
42
|
+
"https://mirrors.huaweicloud.com/geckodriver"
|
|
43
|
+
];
|
|
44
|
+
export function isChinaFriendlyNpmRegistry(registry) {
|
|
45
|
+
const r = registry.toLowerCase();
|
|
46
|
+
return CHINA_NPM_REGISTRY_HINTS.some((hint) => r.includes(hint));
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ada-mcp/launcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Probe fastest npm registry then pnpm dlx @ada-mcp/mcp-server (zero deps)",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ada-mcp": "./launcher.mjs"
|
|
9
9
|
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@ada/download-probe": "0.1.0"
|
|
12
|
-
},
|
|
13
10
|
"files": [
|
|
14
11
|
"launcher.mjs",
|
|
15
12
|
"mirror-candidates.mjs",
|
|
@@ -23,5 +20,8 @@
|
|
|
23
20
|
},
|
|
24
21
|
"engines": {
|
|
25
22
|
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"typecheck": "node --check launcher.mjs && node --check registry-probe.mjs && node --check download-probe.mjs && node --check mirror-candidates.mjs && node --check playwright-probe.mjs"
|
|
26
26
|
}
|
|
27
27
|
}
|