mech.app
Dev Tools

Cross-Client MCP Config Installers: Why Server Discovery Needs More Than JSON Files

How automated config installers solve MCP's missing package-manager problem across Claude Desktop, Cline, and other clients with cross-platform path res...

Source: dev.to
Cross-Client MCP Config Installers: Why Server Discovery Needs More Than JSON Files

Anthropic’s Model Context Protocol shipped without installation tooling. Every MCP server author writes manual config instructions for multiple clients. Every user hand-edits JSON files in platform-specific directories. The @incultnitollc/mcpr package solves this with a cross-client installer that handles path resolution, config merging, and atomic writes.

The Manual Config Problem

Installing an MCP server today means:

  1. Find the server on GitHub
  2. Read the README for launch commands
  3. Copy a JSON fragment
  4. Locate the correct config file for your client
  5. Paste and restart

Step 4 is the pain point. Claude Desktop reads from ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. Cline (VS Code extension) uses ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json. Cursor, Continue, and Zed each have their own paths.

The schemas overlap but differ in structure. No official registry exists. No npm install equivalent.

Architecture of mcpr install

The mcpr tool provides two commands:

npx -y @incultnitollc/mcpr search filesystem
npx -y @incultnitollc/mcpr install npm-agent-infra-mcp-server-filesystem --client claude-desktop

The install command executes five operations:

  1. npm resolution from registry: Resolves the slug to a package name and version
  2. Cross-OS path detection: Locates the client’s config file
  3. Deep merge without clobber: Adds the server without removing existing entries
  4. Atomic write with backup: Writes to a temp file, validates, then renames
  5. File mode preservation: Maintains original permissions

Path Resolution Matrix

ClientmacOS PathWindows PathLinux Path
Claude Desktop~/Library/Application Support/Claude/%APPDATA%\Claude\~/.config/Claude/
Cline~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/
Cursor~/Library/Application Support/Cursor/User/globalStorage/%APPDATA%\Cursor\User\globalStorage\~/.config/Cursor/User/globalStorage/

The installer probes these paths in order, checking for file existence before attempting writes.

Config Merge Strategy

The deep merge preserves sibling servers. Given an existing config:

{
  "mcpServers": {
    "existing-server": {
      "command": "node",
      "args": ["/path/to/server.js"]
    }
  }
}

Adding a new server produces:

{
  "mcpServers": {
    "existing-server": {
      "command": "node",
      "args": ["/path/to/server.js"]
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"]
    }
  }
}

The merge logic uses object spread with conflict detection. If a server with the same key exists, the installer prompts for overwrite or abort. No silent replacements.

Atomic Write Pattern

The write sequence:

  1. Read current config into memory
  2. Perform merge operation
  3. Write merged JSON to config.json.tmp
  4. Validate JSON parse on temp file
  5. Read original file mode with fs.stat
  6. Rename temp file to config.json (atomic on POSIX, near-atomic on Windows)
  7. Apply original file mode with fs.chmod

Step 7 was initially missing. Early versions wrote configs with 0644 permissions, breaking clients that expected 0600. The fix reads the original mode before writing and restores it after the rename.

Failure Modes

Client not installed: The path probe returns null. The installer exits with a list of supported clients and their expected paths.

Malformed existing config: JSON parse fails. The installer backs up the broken file to config.json.backup and prompts to create a fresh config or abort.

Permission denied: The write fails at the rename step. The installer leaves the temp file in place and prints the manual merge instructions.

Registry resolution fails: The slug doesn’t map to a package. The installer suggests running mcpr search again or checking the slug spelling.

Server binary missing after install: The config is written but the server command fails at runtime. This is a lazy failure. The installer doesn’t validate that npx can resolve the package or that dependencies are installed. It trusts the registry metadata.

Implementation Details

The TypeScript implementation uses:

  • os.homedir() and process.env for base path resolution
  • path.join() for cross-platform path construction
  • fs.promises for async file operations
  • JSON.parse and JSON.stringify with 2-space indent for config handling

The registry is a JSON file mapping slugs to package names and default args. No authentication. No versioning beyond npm’s semver. The installer always pulls the latest version unless you specify otherwise.

const configPath = path.join(
  os.homedir(),
  'Library/Application Support/Claude',
  'claude_desktop_config.json'
);

const existing = JSON.parse(await fs.readFile(configPath, 'utf8'));
const merged = {
  ...existing,
  mcpServers: {
    ...existing.mcpServers,
    [serverKey]: serverConfig
  }
};

const tempPath = `${configPath}.tmp`;
await fs.writeFile(tempPath, JSON.stringify(merged, null, 2));
const stats = await fs.stat(configPath);
await fs.rename(tempPath, configPath);
await fs.chmod(configPath, stats.mode);

What’s Still Missing

Schema validation: The installer doesn’t validate that the merged config matches the client’s expected schema. It assumes the registry provides correct structures.

Dependency checks: No preflight check that Node.js, Python, or other runtimes are installed before writing a config that depends on them.

Rollback on client crash: If the client fails to start after install, the user must manually restore from backup. No automatic rollback.

Multi-server batch install: You can only install one server per command. No mcpr install server1 server2 server3.

Version pinning: The registry doesn’t track versions. You get whatever npm resolves as latest.

Technical Verdict

Use this pattern when you’re shipping an MCP server and want to reduce support burden. The installer eliminates the “where do I put this JSON” question and handles the cross-platform path mess for Claude Desktop, Cline, and Cursor users.

Avoid it if your server has complex runtime dependencies that need validation before config writes. The lazy failure model means users won’t know the server is broken until they try to use it. Also avoid if you need version pinning or rollback guarantees. The current implementation is optimistic and assumes the registry is correct.

The path resolution matrix and atomic write pattern are reusable even if you don’t adopt the full registry model. The file mode preservation bug is a reminder that config files often have security implications beyond JSON structure.

Tags

agentic-ai orchestration infrastructure

Primary Source

dev.to