How to Debug MCP Installation Failures When Merging with an Existing .mcp.json
MCP installation failures during merging typically stem from malformed JSON in either the remote template or local .mcp.json, file permission errors, or silent key overwrites in the mcpServers object. Run the command with DEBUG=cli* to expose the exact merge step and file paths, validate both JSON files with jq, and check for duplicate server names that cause silent overwrites.
The claude-code-templates CLI automates MCP server setup by downloading remote .mcp.json configurations and merging them into your existing project files. When the merge logic in cli-tool/src/index.js encounters invalid syntax, permission conflicts, or duplicate server keys, the installation can fail silently or throw cryptic parsing errors. This guide walks through the exact debugging workflow using the source code from the davila7/claude-code-templates repository.
Understanding the MCP Merge Process
When you run npx claude-code-templates … --mcp <name>, the CLI executes a specific sequence in cli-tool/src/index.js (lines 682‑735):
- Download the remote
.mcp.jsonfrom a raw GitHub URL. - Parse the JSON into
mcpConfig. - Strip descriptions to persist only essential command/args/env (lines 684‑89):
// cli-tool/src/index.js – lines 684‑89
if (mcpConfig.mcpServers) {
for (const serverName in mcpConfig.mcpServers) {
delete mcpConfig.mcpServers[serverName].description;
}
}
- Detect an existing local
.mcp.json(targetMcpFile). - Merge configurations using shallow spread for top‑level properties and object spread for
mcpServers(lines 692‑713):
// cli-tool/src/index.js – lines 692‑713
const mergedConfig = { ...existingConfig, ...mcpConfig };
if (existingConfig.mcpServers && mcpConfig.mcpServers) {
mergedConfig.mcpServers = {
...existingConfig.mcpServers,
...mcpConfig.mcpServers
};
}
- Write the merged JSON with 2‑space indentation:
await fs.writeJson(targetMcpFile, mergedConfig, { spaces: 2 });
- Report success/failure via
trackingService.trackInstallationOutcome.
Errors are caught in a single catch block that logs the message and records a failure outcome.
Common Failure Symptoms and Root Causes
| Symptom | Typical Cause |
|---|---|
| Error installing MCP: Unexpected token … | Invalid JSON syntax (trailing commas or comments) in the downloaded file or local .mcp.json. |
| Error installing MCP: ENOENT: no such file or directory | The target directory is missing, or the CLI lacks write permissions. |
| Existing .mcp.json found, merging configurations… followed by a silent failure | Conflict in merge logic (duplicate server keys) or a runtime exception while writing the file. |
| MCP installed successfully but the new server is missing | The deep‑merge step skipped the entry because mcpServers was undefined or the key was silently overwritten. |
Step-by-Step Debugging Checklist
Validate Remote and Local JSON
Corrupt JSON is the most common failure point. Verify the remote template first by opening the raw URL directly, then validate your local file:
# Validate local JSON syntax
node -e "JSON.parse(require('fs').readFileSync('.mcp.json','utf8'))"
If this throws, the parser in cli-tool/src/mcp-stats.js (line 19) will also fail.
Check File Permissions and Paths
The targetMcpFile path is built with path.join(targetDir, '.mcp.json'). If you mistakenly pass --global or an incorrect --target, the file may be written elsewhere or fail with EACCES.
# Verify target location
ls -l .mcp.json
# Or temporarily patch the CLI to log the path
console.log('🔧 Target:', targetMcpFile)
Inspect for Server Key Collisions
The merge uses object spread (...), which silently overwrites duplicate keys. Compare server names before merging:
node - <<'EOF'
const fs = require('fs');
const local = JSON.parse(fs.readFileSync('.mcp.json','utf8'));
console.log('Local server keys:', Object.keys(local.mcpServers || {}));
EOF
If a remote server shares a key with an existing entry, the new configuration replaces the old without warning.
Verify the mcpServers Property Exists
An empty result often indicates the remote file lacks the top‑level mcpServers property. Ensure the source file follows the canonical format:
{
"mcpServers": {
"server-name": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-name"]
}
}
}
Disable Telemetry to Isolate Network Errors
The trackingService call inside the try block can throw network errors that mask file-system issues. Set DISABLE_TRACKING=1 to bypass telemetry during debugging:
DISABLE_TRACKING=1 DEBUG=cli* npx claude-code-templates --mcp my-mcp-name
Practical Debug Commands
Run the installation with maximum verbosity to see exact URLs, file paths, and the intermediate merged object:
DEBUG=cli* npx claude-code-templates --mcp my-mcp-name
Fetch and inspect the remote configuration manually:
node - <<'EOF'
const fetch = require('node-fetch');
(async () => {
const url = 'https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/mcps/my-mcp-name/.mcp.json';
const txt = await (await fetch(url)).text();
console.log('Remote .mcp.json →', JSON.stringify(JSON.parse(txt), null, 2));
})();
EOF
Compare local versus remote server keys to spot collisions:
node - <<'EOF'
const fs = require('fs');
const local = JSON.parse(fs.readFileSync('.mcp.json','utf8'));
const remote = JSON.parse(fs.readFileSync('tmp-remote.json','utf8'));
console.log('Local keys :', Object.keys(local.mcpServers||{}));
console.log('Remote keys:', Object.keys(remote.mcpServers||{}));
EOF
Inspect the final merged file with jq:
cat .mcp.json | jq '.mcpServers'
Resolving Common Merge Issues
| Issue | Why it happens | Fix |
|---|---|---|
| Unexpected token on merge | Remote file contains comments or trailing commas. | Clean the file or use a JSON‑with‑comments parser. |
| Duplicate server names silently overwritten | Object spread overwrites by key. | Rename either server before merging, or manually edit .mcp.json post-install. |
Empty mcpServers in result |
Remote file missed the top‑level mcpServers property. |
Ensure the source uses { "mcpServers": { … } } structure. |
Permissions denied (EACCES) |
Running CLI in a system directory without write access. | Use a user‑writable target directory or chown the project folder. |
| Telemetry failure masks real error | Network error in trackingService propagates to outer catch. |
Set DISABLE_TRACKING=1 while troubleshooting. |
Summary
- Debug MCP installation failures by running
DEBUG=cli*to expose the merge logic incli-tool/src/index.js(lines 682‑735). - Validate JSON syntax on both remote templates and local
.mcp.jsonfiles before merging. - Check for key collisions in
mcpServers, as the object spread implementation silently overwrites duplicates. - Verify file permissions on the target directory to prevent
ENOENTandEACCESerrors. - Disable telemetry with
DISABLE_TRACKING=1to prevent network errors from obscuring file-system issues.
Frequently Asked Questions
Why is my new MCP server missing after a "successful" installation?
This occurs when the deep‑merge step skips the entry, typically because the remote file lacked a properly defined mcpServers object or the server key was silently overwritten by an existing entry with the same name. Inspect the final .mcp.json with jq '.mcpServers' to confirm the new server appears under a unique key.
How do I fix "Unexpected token" errors during the merge?
This error indicates invalid JSON syntax—often trailing commas or comments—in either the downloaded remote file or your local .mcp.json. Validate both files using node -e "JSON.parse(require('fs').readFileSync('.mcp.json'))" or an online linter. The parser at line 19 of cli-tool/src/mcp-stats.js strictly requires standard JSON.
Why did my existing server configuration get overwritten?
The merge implementation uses object spread syntax (...existingConfig.mcpServers, ...mcpConfig.mcpServers), which overwrites properties with identical keys. If the remote server name matches an existing one, the new configuration replaces the old. Rename one of the servers before installation to preserve both configurations.
How can I disable telemetry while debugging MCP installation issues?
Set the environment variable DISABLE_TRACKING=1 before running the command. The trackingService in cli-tool/src/tracking-service.js checks this variable; disabling it prevents network errors from being caught in the main try block and reported as installation failures, allowing you to see the actual file-system or parsing error.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →