claude-spotter 1.2.1 → 1.2.2
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/CHANGELOG.md +39 -0
- package/package.json +1 -1
- package/src/tool-db/investigate-mcp.mjs +29 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.2
|
|
4
|
+
|
|
5
|
+
**Windows で npm-global の `.cmd` 配布 MCP サーバ (例: `claude-mermaid`) が investigate 時に ENOENT で落ちる回帰を修正**。`spotter db refresh` の MCP investigate 経路で `spawn error: spawn claude-mermaid ENOENT` が出て、当該 MCP のツールがカタログに投入されない症状。`.exe` 配布の MCP (例: `openai-image`) や `claude-mermaid.cmd` のように拡張子を明示した登録は影響を受けない、ピンポイントな bug。
|
|
6
|
+
|
|
7
|
+
### 変更点
|
|
8
|
+
|
|
9
|
+
- **編集 [src/tool-db/investigate-mcp.mjs](src/tool-db/investigate-mcp.mjs)**: `buildStdioSpawn` の Windows ブランチ条件を `/\.(cmd|bat)$/i` から「絶対 `.exe` パス以外は `cmd.exe /c` で包む」に拡張。`export` を追加してユニットテスト可能に
|
|
10
|
+
- **編集 [test/tool-db.test.mjs](test/tool-db.test.mjs)**: 4 件追加 — POSIX パススルー / Windows 裸名の wrap (`claude-mermaid` で v1.2.1 の症状を直接再現) / Windows `.cmd`・`.bat` の wrap / Windows 絶対 `.exe` パスは un-wrap (空白入りパスでの cmd.exe quoting リスク回避)
|
|
11
|
+
|
|
12
|
+
### 背景
|
|
13
|
+
|
|
14
|
+
#### 何が起きていたか
|
|
15
|
+
|
|
16
|
+
Windows の npm global は CLI を `<name>.cmd` バッチラッパーとして配布する (npm 標準仕様)。Node.js の `child_process.spawn(name, args)` は `shell: true` 無しでは Windows `CreateProcess` をそのまま呼び、`CreateProcess` は `.exe` を直接実行するが PATHEXT で `.cmd` を解決しない。結果、`spawn('claude-mermaid', [])` は `claude-mermaid.cmd` が PATH 上にあっても ENOENT で即落ちる。これは Spotter v0.7.0 → v0.8.0 で claude CLI 起動時に踏んで自分で直した bug の再来 (own caveat: `windows-node-spawn-claude-fails-with-enoent-because-claude-is-a-cmd-wrapper`) で、修正パターン (Windows なら `cmd.exe /c <command>` で包む) は既に Spotter 内に存在していた (`execClaude` / haiku-caller の `buildSpawnArgs`)。**穴は MCP サーバ起動経路にこのパターンが横展開されていなかったこと**。
|
|
17
|
+
|
|
18
|
+
#### v1.2.1 までのコード
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
function buildStdioSpawn(command, args) {
|
|
22
|
+
if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(command)) {
|
|
23
|
+
return { cmd: 'cmd.exe', cmdArgs: ['/c', command, ...args] };
|
|
24
|
+
}
|
|
25
|
+
return { cmd: command, cmdArgs: args };
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`/\.(cmd|bat)$/i` は「コマンド名が `.cmd`/`.bat` で**終わっている**」場合のみ wrap する。ところが MCP サーバの登録名 (`claude mcp add` から `~/.claude.json` 直下 `mcpServers` に書かれる、または `.mcp.json` に書かれる) は通常**拡張子を付けない裸名** (`claude-mermaid`)。そのため Windows ブランチが発火せず、wrap 抜きで `spawn('claude-mermaid')` してENOENT。`.exe` 配布だと `CreateProcess` が直接実行できるので症状が出ない。`.cmd` を明示的に書いた登録 (`claude-mermaid.cmd`) も既存ロジックでセーフ。**裸名 + `.cmd` 実体の組合せだけが silent に脱落していた**。
|
|
30
|
+
|
|
31
|
+
### 設計判断
|
|
32
|
+
|
|
33
|
+
- **絶対 `.exe` パスは un-wrap のまま**: `C:\Program Files\nodejs\node.exe` のような空白入りパスを `cmd.exe /c` で包むと cmd.exe の `/c` 引数解釈ルール (最初の char が `"` のときの outer-quote strip 等) のリスクに晒される。`.exe` は `CreateProcess` が直接実行できるので包む必要がないし、包まないことで quoting リスクをゼロにできる。`.cmd` パスはどのみち cmd.exe 経由でしか走らないので包む必要があり、quoting リスクは織り込み済み (既存挙動から退化なし)
|
|
34
|
+
- **`shell: true` を使わない理由**: Node 24+ で DEP0190 が出るし、引数 quoting が cmd.exe の rules に丸投げされて `&` `|` `>` 等の metacharacter 処理リスクが復活する。caveat の Resolution と既存 (`execClaude` / `buildSpawnArgs`) の選択を踏襲
|
|
35
|
+
- **裸名を全部 wrap する選択 (= 「`.exe` 以外は wrap」)**: `node` のような既知の `.exe` 配布も Windows では cmd.exe 経由になるが、cmd.exe が PATHEXT で正しく解決するので動作影響なし。コードのシンプルさ (extension の case sensitivity / 配布形態の事前知識を不要にする) を優先
|
|
36
|
+
- **patch bump (fix)**: API 変更なし、`buildStdioSpawn` の export 追加は純粋に additive。Windows での挙動が「ENOENT で死ぬ」→「正しく動く」方向の修正なので破壊変更なし
|
|
37
|
+
|
|
38
|
+
### 自動追従の経路
|
|
39
|
+
|
|
40
|
+
既に Spotter を導入済みのプロジェクトは npm global update (`npm i -g claude-spotter@1.2.2`) 後、次の Claude Code SessionStart で v1.1.0 機構の `spawnRefreshDetached` が走り、新 `buildStdioSpawn` で MCP investigate を再実行。これまで wrap 抜きで spawn して即 ENOENT だった `.cmd` 配布 MCP が live fetch に成功し、**次の次のセッション**から該当ツールがカタログに復活する (detached の仕様)。即時反映したい場合は `spotter db refresh` を手動実行 (環境によっては既にカタログに残っている古いエントリは prune される / live fetch 成功で description が上書きされる)。
|
|
41
|
+
|
|
3
42
|
## 1.2.1
|
|
4
43
|
|
|
5
44
|
**Claude Code 公式の MCP scope 3 段 (User / Project / Local) に完全対応**。v1.2.0 までの `readMcpServers` は project スコープ (`<projectRoot>/.mcp.json`) と非公式の legacy `~/.claude/.mcp.json` しか読んでおらず、公式 3 スコープのうち 2 つ (User: `~/.claude.json` 直下 `mcpServers` / Local: `~/.claude.json` `projects[<root>].mcpServers`) を読み損ねていた。結果、`claude mcp add -s user -e KEY=val -- ...` で登録した MCP サーバーは `claude mcp list` で発見されるが env が拾えず、HTTP 系は 401、stdio 系は API キー無しで spawn → tools/list が空 → `resolveAll` の prune ループでカタログから削除、という silent な脱落が発生していた。
|
package/package.json
CHANGED
|
@@ -170,13 +170,36 @@ function splitArgs(s) {
|
|
|
170
170
|
return s.split(/\s+/).filter((t) => t.length > 0);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
// On Windows,
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
// On Windows, npm-global CLI tools (e.g. `claude-mermaid`) ship as `<name>.cmd`
|
|
174
|
+
// batch wrappers. Node's `child_process.spawn` without `shell: true` calls Windows
|
|
175
|
+
// `CreateProcess`, which only directly executes `.exe` files — it does NOT search
|
|
176
|
+
// PATHEXT for `.cmd`/`.bat` shims when given a bare command name. So
|
|
177
|
+
// `spawn('claude-mermaid', ...)` fails with ENOENT even though `claude-mermaid.cmd`
|
|
178
|
+
// is on PATH.
|
|
179
|
+
//
|
|
180
|
+
// Until v1.2.1 this function only wrapped commands whose name literally ended in
|
|
181
|
+
// `.cmd`/`.bat`, which missed the common case where the registered command is a
|
|
182
|
+
// bare name (the CLI as installed). v1.2.2 routes any non-`.exe` command through
|
|
183
|
+
// `cmd.exe /c` on Windows, which makes PATHEXT lookup apply and runs both `.cmd`
|
|
184
|
+
// shims and bare names transparently.
|
|
185
|
+
//
|
|
186
|
+
// We keep absolute `.exe` paths un-wrapped because (a) they spawn correctly as-is
|
|
187
|
+
// and (b) wrapping them through `cmd.exe /c "<path with spaces>" args` runs into
|
|
188
|
+
// cmd.exe's quoting rules for paths containing spaces, which add risk for zero
|
|
189
|
+
// benefit.
|
|
190
|
+
//
|
|
191
|
+
// We use `cmd.exe /c` explicitly rather than `spawn({ shell: true })` because the
|
|
192
|
+
// latter triggers DEP0190 on Node 24+ and re-introduces argument-quoting risks
|
|
193
|
+
// (matches the rationale in haiku-caller's buildSpawnArgs and the caveat
|
|
194
|
+
// `windows-node-spawn-claude-fails-with-enoent-because-claude-is-a-cmd-wrapper`).
|
|
195
|
+
export function buildStdioSpawn(command, args) {
|
|
196
|
+
if (process.platform !== 'win32') {
|
|
197
|
+
return { cmd: command, cmdArgs: args };
|
|
198
|
+
}
|
|
199
|
+
if (/\.exe$/i.test(command)) {
|
|
200
|
+
return { cmd: command, cmdArgs: args };
|
|
178
201
|
}
|
|
179
|
-
return { cmd:
|
|
202
|
+
return { cmd: 'cmd.exe', cmdArgs: ['/c', command, ...args] };
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
async function spawnAndQuery({ command, args, env = {} }, serverName) {
|