@harusame64/desktop-touch-mcp 1.10.2 → 1.10.3
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.ja.md +15 -0
- package/README.md +17 -0
- package/bin/launcher.js +75 -18
- package/package.json +2 -2
package/README.ja.md
CHANGED
|
@@ -59,6 +59,21 @@ npm ランチャーは npm package version に厳密に対応する runtime だ
|
|
|
59
59
|
|
|
60
60
|
キャッシュの保存先は `DESKTOP_TOUCH_MCP_HOME` で変更できます。
|
|
61
61
|
|
|
62
|
+
> **共有ネットワークや CI 環境の場合:** 初回起動時に GitHub Releases API を参照して
|
|
63
|
+
> runtime zip を探します。匿名アクセスの上限は IP あたり 60 回/時で、共有グローバル IP
|
|
64
|
+
> (CI ランナー、オフィスの NAT など)ではダウンロード開始前に枯渇することがあります。
|
|
65
|
+
> 環境変数に `GITHUB_TOKEN`(または `GH_TOKEN`)を設定すると API 呼び出しが認証され、
|
|
66
|
+
> 上限が 5,000 回/時に上がります。通常の家庭回線ではトークンは不要です。
|
|
67
|
+
|
|
68
|
+
> **ソースチェックアウトから launcher を実行する場合:** ソースビルドの
|
|
69
|
+
> `bin/launcher.js` は確定済みの整合性ハッシュではなくプレースホルダ
|
|
70
|
+
> (`sha256: "PENDING"`)を持ちます。検証できない runtime をダウンロードして実行する
|
|
71
|
+
> 代わりに launcher は fail-closed で停止し、誤って publish された/未確定の launcher が
|
|
72
|
+
> 検証されていないコードを黙って起動するのを防ぎます。publish 済みの npm リリースは
|
|
73
|
+
> 常に本物の SHA256 を同梱するため、利用者がこの状態に遭遇することはありません。
|
|
74
|
+
> 意図的にソースから launcher を実行する場合は `DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED=1`
|
|
75
|
+
> を設定すると整合性検証をスキップできます(開発用途のみ)。
|
|
76
|
+
|
|
62
77
|
### Claude CLI への登録
|
|
63
78
|
|
|
64
79
|
`~/.claude.json` の `mcpServers` に追加:
|
package/README.md
CHANGED
|
@@ -60,6 +60,23 @@ The npm launcher resolves runtime strictly by npm package version. For package `
|
|
|
60
60
|
|
|
61
61
|
Set `DESKTOP_TOUCH_MCP_HOME` to override the cache root directory.
|
|
62
62
|
|
|
63
|
+
> **On a shared or CI network?** The first run reads the GitHub Releases API to
|
|
64
|
+
> locate the runtime zip. The anonymous limit is 60 requests/hour per IP, which a
|
|
65
|
+
> shared public address (CI runners, office NAT) can exhaust before your download
|
|
66
|
+
> even starts. Set `GITHUB_TOKEN` (or `GH_TOKEN`) in the environment and the
|
|
67
|
+
> launcher authenticates the request, raising the limit to 5,000 requests/hour.
|
|
68
|
+
> No token is needed on an ordinary home connection.
|
|
69
|
+
|
|
70
|
+
> **Running the launcher from a source checkout?** A source build's
|
|
71
|
+
> `bin/launcher.js` carries a placeholder integrity hash (`sha256: "PENDING"`)
|
|
72
|
+
> instead of a finalized one. Rather than download and run an unverified runtime,
|
|
73
|
+
> the launcher fails closed — this guard stops an accidentally published or
|
|
74
|
+
> unfinalized launcher from silently starting unverified code. Published npm
|
|
75
|
+
> releases always ship a real SHA256, so end users never see this. If you are
|
|
76
|
+
> intentionally running the launcher from source, set
|
|
77
|
+
> `DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED=1` to skip integrity verification
|
|
78
|
+
> (development only).
|
|
79
|
+
|
|
63
80
|
### Register with Claude CLI
|
|
64
81
|
|
|
65
82
|
Add to `~/.claude.json` under `mcpServers`:
|
package/bin/launcher.js
CHANGED
|
@@ -18,15 +18,15 @@ import path from "node:path";
|
|
|
18
18
|
import { Readable } from "node:stream";
|
|
19
19
|
import { pipeline } from "node:stream/promises";
|
|
20
20
|
|
|
21
|
-
const PACKAGE_VERSION = "1.10.
|
|
21
|
+
const PACKAGE_VERSION = "1.10.3";
|
|
22
22
|
const RELEASE_TAG = `v${PACKAGE_VERSION}`;
|
|
23
23
|
const REPO_API_URL = `https://api.github.com/repos/Harusame64/desktop-touch-mcp/releases/tags/${RELEASE_TAG}`;
|
|
24
24
|
const ASSET_NAME = "desktop-touch-mcp-windows.zip";
|
|
25
25
|
const RELEASE_METADATA_FILE = ".desktop-touch-release.json";
|
|
26
26
|
const RELEASE_MANIFEST = {
|
|
27
|
-
tagName: "v1.10.
|
|
27
|
+
tagName: "v1.10.3",
|
|
28
28
|
assetName: ASSET_NAME,
|
|
29
|
-
sha256: "
|
|
29
|
+
sha256: "940b518d92f7678d02289560bddd68f34e8813e76ab5d051feb0da31d6865cf1",
|
|
30
30
|
};
|
|
31
31
|
const CACHE_ROOT = process.env.DESKTOP_TOUCH_MCP_HOME
|
|
32
32
|
? path.resolve(process.env.DESKTOP_TOUCH_MCP_HOME)
|
|
@@ -38,11 +38,49 @@ function log(message) {
|
|
|
38
38
|
console.error(`[desktop-touch-mcp] ${message}`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function warn(message) {
|
|
42
|
+
console.error(`[desktop-touch-mcp] WARNING: ${message}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
function fail(message) {
|
|
42
46
|
console.error(`[desktop-touch-mcp] ${message}`);
|
|
43
47
|
process.exit(1);
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Opt-in escape hatch for running the launcher from a source tree whose
|
|
52
|
+
* RELEASE_MANIFEST.sha256 is still the "PENDING" placeholder (i.e. the
|
|
53
|
+
* release workflow has not finalized the manifest). Published npm packages
|
|
54
|
+
* always ship a real SHA256, so end users never need this.
|
|
55
|
+
*/
|
|
56
|
+
function allowUnverifiedRelease() {
|
|
57
|
+
return process.env.DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED === "1";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Reads the GitHub token from the environment.
|
|
62
|
+
* Supports both GITHUB_TOKEN (GitHub Actions standard) and GH_TOKEN (gh CLI).
|
|
63
|
+
*/
|
|
64
|
+
function getGitHubToken() {
|
|
65
|
+
return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns headers for GitHub API and release download requests.
|
|
70
|
+
* Includes Authorization when a token is available to avoid rate limits.
|
|
71
|
+
*/
|
|
72
|
+
function getGitHubHeaders(extra = {}) {
|
|
73
|
+
const headers = {
|
|
74
|
+
"User-Agent": "desktop-touch-mcp-launcher",
|
|
75
|
+
...extra,
|
|
76
|
+
};
|
|
77
|
+
const token = getGitHubToken();
|
|
78
|
+
if (token) {
|
|
79
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
80
|
+
}
|
|
81
|
+
return headers;
|
|
82
|
+
}
|
|
83
|
+
|
|
46
84
|
export function isDisconnectError(error) {
|
|
47
85
|
return error?.code === "EPIPE" || error?.code === "ERR_STREAM_DESTROYED";
|
|
48
86
|
}
|
|
@@ -130,13 +168,29 @@ function expectedReleaseSpec() {
|
|
|
130
168
|
if (!RELEASE_MANIFEST.sha256 || RELEASE_MANIFEST.assetName !== ASSET_NAME) {
|
|
131
169
|
throw new Error(`Missing release manifest for ${RELEASE_TAG}`);
|
|
132
170
|
}
|
|
133
|
-
|
|
171
|
+
// "PENDING" is the pre-release placeholder set in source. The release
|
|
172
|
+
// workflow (scripts/update-sha.mjs) replaces it with the real zip SHA256
|
|
173
|
+
// before npm publish, so a PENDING manifest at runtime means this launcher
|
|
174
|
+
// was not finalized — fail closed by default so an accidentally published
|
|
175
|
+
// launcher can never silently run an unverified runtime zip. Developers
|
|
176
|
+
// running straight from source can opt in to skipping verification with
|
|
177
|
+
// DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED=1.
|
|
178
|
+
const isPending = RELEASE_MANIFEST.sha256 === "PENDING";
|
|
179
|
+
if (isPending && !allowUnverifiedRelease()) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Release SHA256 manifest for ${RELEASE_TAG} is PENDING — this launcher was not finalized by the release workflow. ` +
|
|
182
|
+
`Published npm packages always ship a real SHA256. If you are intentionally running the launcher from source, ` +
|
|
183
|
+
`set DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED=1 to skip integrity verification (development only).`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (!isPending && !/^[a-f0-9]{64}$/i.test(RELEASE_MANIFEST.sha256)) {
|
|
134
187
|
throw new Error(`Invalid release SHA256 manifest for ${RELEASE_TAG}`);
|
|
135
188
|
}
|
|
136
189
|
return {
|
|
137
190
|
tagName: RELEASE_MANIFEST.tagName,
|
|
138
191
|
assetName: RELEASE_MANIFEST.assetName,
|
|
139
|
-
sha256: String(RELEASE_MANIFEST.sha256).toLowerCase(),
|
|
192
|
+
sha256: isPending ? null : String(RELEASE_MANIFEST.sha256).toLowerCase(),
|
|
193
|
+
sha256Pending: isPending,
|
|
140
194
|
};
|
|
141
195
|
}
|
|
142
196
|
|
|
@@ -153,11 +207,12 @@ async function isInstalled(releaseDir, expected) {
|
|
|
153
207
|
if (!existsSync(path.join(releaseDir, "dist", "index.js"))) return false;
|
|
154
208
|
const metadata = await readReleaseMetadata(releaseDir);
|
|
155
209
|
if (!metadata) return false;
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
String(metadata.sha256 || "").toLowerCase()
|
|
160
|
-
|
|
210
|
+
if (metadata.tagName !== expected.tagName || metadata.assetName !== expected.assetName) return false;
|
|
211
|
+
// Skip SHA256 check when the manifest is still PENDING.
|
|
212
|
+
if (expected.sha256 !== null) {
|
|
213
|
+
if (String(metadata.sha256 || "").toLowerCase() !== expected.sha256) return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
161
216
|
}
|
|
162
217
|
|
|
163
218
|
async function readCurrentRelease(expected) {
|
|
@@ -167,7 +222,7 @@ async function readCurrentRelease(expected) {
|
|
|
167
222
|
if (typeof parsed?.tagName !== "string") return null;
|
|
168
223
|
if (parsed.tagName !== expected.tagName) return null;
|
|
169
224
|
if (parsed.assetName !== expected.assetName) return null;
|
|
170
|
-
if (String(parsed.sha256 || "").toLowerCase() !== expected.sha256) return null;
|
|
225
|
+
if (expected.sha256 !== null && String(parsed.sha256 || "").toLowerCase() !== expected.sha256) return null;
|
|
171
226
|
const releaseDir = releaseDirForTag(parsed.tagName);
|
|
172
227
|
if (!(await isInstalled(releaseDir, expected))) return null;
|
|
173
228
|
return { tagName: parsed.tagName, releaseDir };
|
|
@@ -195,10 +250,9 @@ async function writeCurrentRelease(expected) {
|
|
|
195
250
|
|
|
196
251
|
async function fetchReleaseByTag(expected) {
|
|
197
252
|
const response = await fetch(REPO_API_URL, {
|
|
198
|
-
headers: {
|
|
253
|
+
headers: getGitHubHeaders({
|
|
199
254
|
"Accept": "application/vnd.github+json",
|
|
200
|
-
|
|
201
|
-
},
|
|
255
|
+
}),
|
|
202
256
|
});
|
|
203
257
|
|
|
204
258
|
if (!response.ok) {
|
|
@@ -249,9 +303,7 @@ async function verifySha256(filePath, expectedSha256) {
|
|
|
249
303
|
|
|
250
304
|
async function downloadFile(url, destination) {
|
|
251
305
|
const response = await fetch(url, {
|
|
252
|
-
headers:
|
|
253
|
-
"User-Agent": "desktop-touch-mcp-launcher",
|
|
254
|
-
},
|
|
306
|
+
headers: getGitHubHeaders(),
|
|
255
307
|
});
|
|
256
308
|
|
|
257
309
|
if (!response.ok) {
|
|
@@ -314,7 +366,12 @@ async function installRelease(release, expected) {
|
|
|
314
366
|
try {
|
|
315
367
|
log(`Downloading ${ASSET_NAME} from ${release.tagName}`);
|
|
316
368
|
await downloadFile(release.assetUrl, zipPath);
|
|
317
|
-
|
|
369
|
+
if (expected.sha256 !== null) {
|
|
370
|
+
await verifySha256(zipPath, expected.sha256);
|
|
371
|
+
} else {
|
|
372
|
+
warn("SHA256 manifest is PENDING and DESKTOP_TOUCH_MCP_ALLOW_UNVERIFIED=1 is set — " +
|
|
373
|
+
"skipping integrity verification of the downloaded zip. Development use only.");
|
|
374
|
+
}
|
|
318
375
|
await mkdir(extractDir, { recursive: true });
|
|
319
376
|
await expandZip(zipPath, extractDir);
|
|
320
377
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harusame64/desktop-touch-mcp",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.3",
|
|
4
4
|
"mcpName": "io.github.Harusame64/desktop-touch-mcp",
|
|
5
5
|
"description": "Let Claude, Cursor, or any MCP client see and operate your Windows 10/11 desktop. 29 tools for screenshots, UI Automation, Chrome CDP, keyboard/mouse, terminal, with semantic discover-then-act targeting and per-action perception guards that avoid wrong-window typing and stale-coordinate clicks.",
|
|
6
6
|
"keywords": [
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
108
108
|
"@napi-rs/cli": "^3.7.0",
|
|
109
109
|
"@nut-tree-fork/nut-js": "^4.2.6",
|
|
110
|
-
"@types/node": "^25.9.
|
|
110
|
+
"@types/node": "^25.9.2",
|
|
111
111
|
"@types/ws": "^8.18.1",
|
|
112
112
|
"eslint": "^10.4.1",
|
|
113
113
|
"fast-check": "^4.8.0",
|