Skip to content

Commit a40e93a

Browse files
committed
fix(plugin): auto-create mcp.json for MCP server connectivity
Plugin installation did not create ~/.claude/mcp.json, causing MCP tools (parse_mode, etc.) to be unavailable despite plugin hooks working. - Add createMcpJson() to build.ts — generates .mcp.json for npm package - Add _ensure_mcp_json() to session-start.py — creates or merges codingbuddy entry into ~/.claude/mcp.json at runtime - Preserve existing MCP server configurations (only adds codingbuddy) - Do not overwrite user's custom codingbuddy configuration - Handle corrupted mcp.json gracefully (fallback to empty) - 4 new tests for mcp.json creation/merge/preserve/corruption Closes #1100
1 parent fd1147a commit a40e93a

3 files changed

Lines changed: 136 additions & 0 deletions

File tree

‎packages/claude-code-plugin/hooks/session-start.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,43 @@ def _install_hook_with_lib(
373373
)
374374

375375

376+
CODINGBUDDY_MCP_ENTRY = {
377+
"command": "codingbuddy",
378+
"args": ["mcp"],
379+
}
380+
381+
382+
def _ensure_mcp_json(mcp_json_path: Path) -> None:
383+
"""Ensure ~/.claude/mcp.json contains the codingbuddy MCP server entry (#1100).
384+
385+
Creates the file if missing, or merges the codingbuddy entry into an
386+
existing file while preserving other MCP server configurations.
387+
"""
388+
mcp_json_path.parent.mkdir(parents=True, exist_ok=True)
389+
390+
if mcp_json_path.exists():
391+
try:
392+
with open(mcp_json_path, "r", encoding="utf-8") as f:
393+
if HAS_FCNTL:
394+
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
395+
existing = json.load(f)
396+
except (json.JSONDecodeError, OSError):
397+
existing = {}
398+
else:
399+
existing = {}
400+
401+
servers = existing.setdefault("mcpServers", {})
402+
if "codingbuddy" in servers:
403+
return # Already configured — don't overwrite user customizations
404+
405+
servers["codingbuddy"] = CODINGBUDDY_MCP_ENTRY
406+
407+
with open(mcp_json_path, "w", encoding="utf-8") as f:
408+
if HAS_FCNTL:
409+
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
410+
json.dump(existing, f, indent=2, ensure_ascii=False)
411+
412+
376413
HUD_FILENAME = "codingbuddy-hud.py"
377414

378415
# tmux suggestion messages (i18n)
@@ -698,6 +735,12 @@ def main():
698735
except Exception:
699736
pass # Never block session start
700737

738+
# Step 2.6: Ensure ~/.claude/mcp.json has codingbuddy entry (#1100)
739+
try:
740+
_ensure_mcp_json(home / ".claude" / "mcp.json")
741+
except Exception:
742+
pass # Never block session start
743+
701744
# Step 3: System prompt injection (#828)
702745
# SessionStart uses plain stdout for context injection (NOT JSON)
703746
try:

‎packages/claude-code-plugin/hooks/test_session_start.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,66 @@ def test_finds_agents_from_relative_path(self):
309309
assert len(json_files) > 0
310310

311311

312+
class TestEnsureMcpJson:
313+
"""Tests for _ensure_mcp_json function (#1100)."""
314+
315+
def test_creates_mcp_json_when_missing(self):
316+
"""Test creates mcp.json with codingbuddy entry when file doesn't exist."""
317+
with tempfile.TemporaryDirectory() as tmpdir:
318+
mcp_path = Path(tmpdir) / ".claude" / "mcp.json"
319+
320+
session_hook._ensure_mcp_json(mcp_path)
321+
322+
assert mcp_path.exists()
323+
data = json.loads(mcp_path.read_text())
324+
assert "codingbuddy" in data["mcpServers"]
325+
assert data["mcpServers"]["codingbuddy"]["command"] == "codingbuddy"
326+
assert data["mcpServers"]["codingbuddy"]["args"] == ["mcp"]
327+
328+
def test_merges_into_existing_mcp_json(self):
329+
"""Test adds codingbuddy entry while preserving existing servers."""
330+
with tempfile.TemporaryDirectory() as tmpdir:
331+
mcp_path = Path(tmpdir) / "mcp.json"
332+
mcp_path.write_text(json.dumps({
333+
"mcpServers": {
334+
"other-server": {"command": "other", "args": ["--flag"]}
335+
}
336+
}))
337+
338+
session_hook._ensure_mcp_json(mcp_path)
339+
340+
data = json.loads(mcp_path.read_text())
341+
assert "codingbuddy" in data["mcpServers"]
342+
assert "other-server" in data["mcpServers"]
343+
344+
def test_does_not_overwrite_existing_codingbuddy(self):
345+
"""Test does not overwrite user's custom codingbuddy configuration."""
346+
with tempfile.TemporaryDirectory() as tmpdir:
347+
mcp_path = Path(tmpdir) / "mcp.json"
348+
custom_config = {
349+
"mcpServers": {
350+
"codingbuddy": {"command": "custom-path", "args": ["--custom"]}
351+
}
352+
}
353+
mcp_path.write_text(json.dumps(custom_config))
354+
355+
session_hook._ensure_mcp_json(mcp_path)
356+
357+
data = json.loads(mcp_path.read_text())
358+
assert data["mcpServers"]["codingbuddy"]["command"] == "custom-path"
359+
360+
def test_handles_corrupted_mcp_json(self):
361+
"""Test handles corrupted JSON gracefully."""
362+
with tempfile.TemporaryDirectory() as tmpdir:
363+
mcp_path = Path(tmpdir) / "mcp.json"
364+
mcp_path.write_text("not valid json{{{")
365+
366+
session_hook._ensure_mcp_json(mcp_path)
367+
368+
data = json.loads(mcp_path.read_text())
369+
assert "codingbuddy" in data["mcpServers"]
370+
371+
312372
class TestHookLibCopy:
313373
"""Tests for lib/ directory copying alongside hook file (#1102)."""
314374

‎packages/claude-code-plugin/scripts/build.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,35 @@ MIT
140140
return result;
141141
}
142142

143+
function createMcpJson(): BuildResult {
144+
const result: BuildResult = {
145+
step: 'MCP Configuration',
146+
success: true,
147+
details: [],
148+
errors: [],
149+
};
150+
151+
try {
152+
const mcpConfig = {
153+
mcpServers: {
154+
codingbuddy: {
155+
command: 'codingbuddy',
156+
args: ['mcp'],
157+
},
158+
},
159+
};
160+
161+
const mcpJsonPath = path.join(ROOT_DIR, '.mcp.json');
162+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
163+
result.details.push(`Generated .mcp.json`);
164+
} catch (err: unknown) {
165+
result.success = false;
166+
result.errors.push(`Failed to create .mcp.json: ${getErrorMessage(err)}`);
167+
}
168+
169+
return result;
170+
}
171+
143172
async function main(): Promise<void> {
144173
console.log('╔════════════════════════════════════════════════════════════╗');
145174
console.log('║ CodingBuddy Claude Code Plugin Builder ║');
@@ -155,6 +184,10 @@ async function main(): Promise<void> {
155184
console.log('📖 Step 1: Generating README...');
156185
results.push(createReadme());
157186

187+
// Step 2: Generate .mcp.json
188+
console.log('🔧 Step 2: Generating .mcp.json...');
189+
results.push(createMcpJson());
190+
158191
// Summary
159192
console.log('\n════════════════════════════════════════════════════════════');
160193
console.log('Build Summary');

0 commit comments

Comments
 (0)