claude-spotter 0.9.0 → 0.10.0
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
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
**project scope `.mcp.json` 対応**。v0.9.0 は `~/.claude/.mcp.json` (user scope) だけを読んでいたため、プロジェクト直下の `.mcp.json` に登録された MCP サーバーの認証情報 (env / headers) を拾えなかった。`<projectRoot>/.mcp.json` も読んで user scope に merge (project 勝ち = Claude Code 本体の precedence と整合) するよう変更。
|
|
6
|
+
|
|
7
|
+
### 変更点
|
|
8
|
+
|
|
9
|
+
- **編集 [src/tool-db/mcp-config.mjs](src/tool-db/mcp-config.mjs)**: `readMcpServers({projectRoot})` シグネチャへ変更。`projectRoot` が渡されれば user scope に project scope を merge して返す。missing file は空として扱う
|
|
10
|
+
- **編集 [src/tool-db/investigate-mcp.mjs](src/tool-db/investigate-mcp.mjs)**: `listMcpServers` / `listMcpToolsAll` が `projectRoot` を受け取って伝搬
|
|
11
|
+
- **編集 [src/tool-db/refresh.mjs](src/tool-db/refresh.mjs)**: `buildInvestigationSnapshot` / `refresh` が `projectRoot` を伝搬 (CLI からは既に渡されている、経路が繋がった)
|
|
12
|
+
- **編集 [test/tool-db.test.mjs](test/tool-db.test.mjs)**: project-scope override + missing-file fallback の 2 ケース追加 (total 99 tests)
|
|
13
|
+
|
|
14
|
+
### 非互換
|
|
15
|
+
|
|
16
|
+
- `readMcpServers()` → `readMcpServers({projectRoot})`: 引数なしも引き続き動く (user scope のみ) ので既存コードは影響なし
|
|
17
|
+
|
|
3
18
|
## 0.9.0
|
|
4
19
|
|
|
5
20
|
**`.mcp.json` を真実源として読み込み、user-registered HTTP/stdio MCP の認証情報を live fetch に活用**。v0.8.0 で HTTP transport を実装したが、`claude mcp list` / `claude mcp get` は bearer token や headers を CLI 出力に含めないため、認証が必要な MCP サーバー (x-api) は依然 401 で落ちていた。`~/.claude/.mcp.json` を直接読んで env / headers を取得、stdio なら spawn 時の env に、HTTP なら fetch request header に渡す。
|
package/package.json
CHANGED
|
@@ -87,7 +87,7 @@ export async function listToolsHttp({ url, serverName, headers: staticHeaders =
|
|
|
87
87
|
params: {
|
|
88
88
|
protocolVersion: PROTOCOL_VERSION,
|
|
89
89
|
capabilities: {},
|
|
90
|
-
clientInfo: { name: 'spotter', version: '0.
|
|
90
|
+
clientInfo: { name: 'spotter', version: '0.10.0' },
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
if (!initResult || initResult.error) {
|
|
@@ -40,8 +40,8 @@ export class McpInvestigationError extends Error {
|
|
|
40
40
|
|
|
41
41
|
// Returns: Map<server-name, Array<{name, description}>>.
|
|
42
42
|
// Skips servers that fail (logs the failure) so one broken server doesn't block the rest.
|
|
43
|
-
export async function listMcpToolsAll({ logFn = () => {}, claudeBin = 'claude' } = {}) {
|
|
44
|
-
const servers = await listMcpServers({ claudeBin });
|
|
43
|
+
export async function listMcpToolsAll({ logFn = () => {}, claudeBin = 'claude', projectRoot } = {}) {
|
|
44
|
+
const servers = await listMcpServers({ claudeBin, projectRoot });
|
|
45
45
|
const out = new Map();
|
|
46
46
|
for (const server of servers) {
|
|
47
47
|
try {
|
|
@@ -64,10 +64,10 @@ export async function listMcpToolsAll({ logFn = () => {}, claudeBin = 'claude' }
|
|
|
64
64
|
// full descriptor (with env/headers). Otherwise we fall back to the parsed CLI line,
|
|
65
65
|
// which at minimum gives us name + transport + url (or triggers `claude mcp get` for
|
|
66
66
|
// stdio command tokenisation).
|
|
67
|
-
export async function listMcpServers({ claudeBin = 'claude' } = {}) {
|
|
67
|
+
export async function listMcpServers({ claudeBin = 'claude', projectRoot } = {}) {
|
|
68
68
|
const [{ stdout }, mcpServers] = await Promise.all([
|
|
69
69
|
execClaude(claudeBin, ['mcp', 'list'], { encoding: 'utf8' }),
|
|
70
|
-
readMcpServers(),
|
|
70
|
+
readMcpServers({ projectRoot }),
|
|
71
71
|
]);
|
|
72
72
|
const cliList = parseMcpListOutput(stdout);
|
|
73
73
|
return cliList.map((cliEntry) => {
|
|
@@ -247,7 +247,7 @@ async function spawnAndQuery({ command, args, env = {} }, serverName) {
|
|
|
247
247
|
await request('initialize', {
|
|
248
248
|
protocolVersion: PROTOCOL_VERSION,
|
|
249
249
|
capabilities: {},
|
|
250
|
-
clientInfo: { name: 'spotter', version: '0.
|
|
250
|
+
clientInfo: { name: 'spotter', version: '0.10.0' },
|
|
251
251
|
});
|
|
252
252
|
send({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
253
253
|
initializedSent = true;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// of `claude mcp list` / `claude mcp get` hides those secrets. Without them, an HTTP
|
|
7
7
|
// MCP server returns 401 and a stdio MCP server spawns without its API key.
|
|
8
8
|
//
|
|
9
|
-
// Scope:
|
|
10
|
-
//
|
|
11
|
-
// (
|
|
12
|
-
//
|
|
9
|
+
// Scope: merges user-level `~/.claude/.mcp.json` with optional project-level
|
|
10
|
+
// `<projectRoot>/.mcp.json`. Project scope overrides user scope on name collision
|
|
11
|
+
// (matches Claude Code's own precedence — more-specific scope wins).
|
|
12
|
+
// `settings.local.json` (local scope) is not yet consulted.
|
|
13
13
|
//
|
|
14
14
|
// This file does NOT read ~/.claude/.credentials.json (Anthropic OAuth token). That
|
|
15
15
|
// remains off-limits per the v0.8.0 design decision. `.mcp.json` is user-authored
|
|
@@ -23,11 +23,13 @@ export function userMcpConfigPath() {
|
|
|
23
23
|
return join(homedir(), '.claude', '.mcp.json');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
export function projectMcpConfigPath(projectRoot) {
|
|
27
|
+
return join(projectRoot, '.mcp.json');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readOne(path) {
|
|
29
31
|
try {
|
|
30
|
-
const text = await readFile(
|
|
32
|
+
const text = await readFile(path, 'utf8');
|
|
31
33
|
const data = JSON.parse(text);
|
|
32
34
|
return data.mcpServers ?? {};
|
|
33
35
|
} catch (err) {
|
|
@@ -36,6 +38,16 @@ export async function readMcpServers() {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// Returns the merged `mcpServers` object: user scope as base, project scope overrides
|
|
42
|
+
// on name collision. Missing files are treated as empty (not an error). If projectRoot
|
|
43
|
+
// is not supplied, only user scope is read.
|
|
44
|
+
export async function readMcpServers({ projectRoot } = {}) {
|
|
45
|
+
const user = await readOne(userMcpConfigPath());
|
|
46
|
+
if (!projectRoot) return user;
|
|
47
|
+
const project = await readOne(projectMcpConfigPath(projectRoot));
|
|
48
|
+
return { ...user, ...project };
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
// Normalise an `.mcp.json` entry into a server descriptor the investigator can use.
|
|
40
52
|
// Returns { name, transport: 'stdio'|'http'|'sse', ...transport-specific fields } or
|
|
41
53
|
// null if the entry is not recognisable.
|
package/src/tool-db/refresh.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { localDbPath, globalDbPath } from './loader.mjs';
|
|
|
17
17
|
// - Deferred: from the hardcoded baseline
|
|
18
18
|
//
|
|
19
19
|
// Returns an in-memory snapshot the caller can use as the investigate() backend.
|
|
20
|
-
export async function buildInvestigationSnapshot({ logFn = () => {}, claudeBin = 'claude' } = {}) {
|
|
20
|
+
export async function buildInvestigationSnapshot({ logFn = () => {}, claudeBin = 'claude', projectRoot } = {}) {
|
|
21
21
|
const snapshot = new Map();
|
|
22
22
|
|
|
23
23
|
// Deferred built-ins (Claude Code deferred tools like WebFetch, TodoWrite, etc.).
|
|
@@ -32,8 +32,9 @@ export async function buildInvestigationSnapshot({ logFn = () => {}, claudeBin =
|
|
|
32
32
|
snapshot.set(name, getClaudeAiDescription(name));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// MCP servers (stdio + user-registered HTTP/SSE).
|
|
36
|
-
|
|
35
|
+
// MCP servers (stdio + user-registered HTTP/SSE). projectRoot forwards for
|
|
36
|
+
// project-scope `.mcp.json` merge (see mcp-config.mjs).
|
|
37
|
+
const mcp = await listMcpToolsAll({ logFn, claudeBin, projectRoot });
|
|
37
38
|
for (const [serverName, tools] of mcp.entries()) {
|
|
38
39
|
for (const tool of tools) {
|
|
39
40
|
if (!tool.description || tool.description.length === 0) continue;
|
|
@@ -47,7 +48,7 @@ export async function buildInvestigationSnapshot({ logFn = () => {}, claudeBin =
|
|
|
47
48
|
// Refresh the tool-db. Discovers all currently available tools, resolves each via the
|
|
48
49
|
// 3-tier lookup, writes through. Returns the resolved Map.
|
|
49
50
|
export async function refresh({ projectRoot, logFn = () => {}, claudeBin = 'claude' }) {
|
|
50
|
-
const snapshot = await buildInvestigationSnapshot({ logFn, claudeBin });
|
|
51
|
+
const snapshot = await buildInvestigationSnapshot({ logFn, claudeBin, projectRoot });
|
|
51
52
|
const toolNames = Array.from(snapshot.keys());
|
|
52
53
|
const investigate = async (name) => snapshot.get(name) ?? null;
|
|
53
54
|
|
package/src/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '0.10.0';
|