#!/usr/bin/env node
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
import { homedir } from 'os';
import { join, dirname } from 'path';
const MCP_CONFIG = {
'mcp-connect': {
command: 'npx',
args: ['-y', '@plaintest/mcp-connect'],
},
};
interface MCPClient {
name: string;
configPath: string;
configKey: string;
useCliInstall?: boolean;
}
const CLIENTS: Record<string, MCPClient> = {
claude: {
name: 'Claude Code',
configPath: join(homedir(), '.claude.json'),
configKey: 'mcpServers',
useCliInstall: true, // Use `claude mcp add` command
},
cursor: {
name: 'Cursor',
configPath: join(homedir(), '.cursor', 'mcp.json'),
configKey: 'mcpServers',
},
windsurf: {
name: 'Windsurf',
configPath: join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
configKey: 'mcpServers',
},
cline: {
name: 'Cline (VS Code)',
configPath: join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'),
configKey: 'mcpServers',
},
copilot: {
name: 'GitHub Copilot (VS Code)',
configPath: join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json'),
configKey: 'github.copilot.chat.experimental.mcp.servers',
},
};
function printUsage() {
console.log(`
@plaintest/mcp-connect - iOS Simulator & Browser Automation
Usage:
npx @plaintest/mcp-connect Start the MCP server (stdio)
npx @plaintest/mcp-connect install <client> Install config for a client
npx @plaintest/mcp-connect uninstall <client> Remove config from a client
npx @plaintest/mcp-connect list List supported clients
npx @plaintest/mcp-connect status Show installation status
Tool Filtering:
--only=browser Enable only browser automation tools
--only=simulator Enable only iOS simulator tools
--only=all Enable all tools (ignore auto-detection)
By default, tools are auto-detected based on available dependencies.
Supported clients:
claude - Claude Code (uses 'claude mcp add')
cursor - Cursor
windsurf - Windsurf
cline - Cline (VS Code extension)
copilot - GitHub Copilot (VS Code)
Examples:
npx @plaintest/mcp-connect install claude
npx @plaintest/mcp-connect install cursor
npx @plaintest/mcp-connect install --all
npx @plaintest/mcp-connect --only=browser
`);
}
function readConfig(configPath: string): any {
if (!existsSync(configPath)) {
return {};
}
try {
const content = readFileSync(configPath, 'utf-8');
return JSON.parse(content);
} catch {
return {};
}
}
function writeConfig(configPath: string, config: any) {
const dir = dirname(configPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
}
function install(clientId: string): boolean {
const client = CLIENTS[clientId];
if (!client) {
console.error(`Unknown client: ${clientId}`);
console.error(`Run 'npx @plaintest/mcp-connect list' to see supported clients`);
return false;
}
// Claude Code uses its own CLI for installation
if (client.useCliInstall) {
try {
console.log(`Installing for ${client.name}...`);
execSync('claude mcp add mcp-connect -- npx -y @plaintest/mcp-connect', {
stdio: 'inherit',
encoding: 'utf-8'
});
console.log(`✓ ${client.name}: Installed successfully`);
console.log(` Restart Claude Code to use the tools`);
return true;
} catch (error) {
console.error(`✗ ${client.name}: Installation failed`);
console.error(` Make sure Claude Code CLI is installed and you're logged in`);
console.error(` Try running manually: claude mcp add mcp-connect -- npx -y @plaintest/mcp-connect`);
return false;
}
}
const config = readConfig(client.configPath);
if (!config[client.configKey]) {
config[client.configKey] = {};
}
if (config[client.configKey]['mcp-connect']) {
console.log(`✓ ${client.name}: Already installed`);
return true;
}
config[client.configKey]['mcp-connect'] = MCP_CONFIG['mcp-connect'];
writeConfig(client.configPath, config);
console.log(`✓ ${client.name}: Installed successfully`);
console.log(` Config: ${client.configPath}`);
return true;
}
function uninstall(clientId: string): boolean {
const client = CLIENTS[clientId];
if (!client) {
console.error(`Unknown client: ${clientId}`);
return false;
}
// Claude Code uses its own CLI for uninstallation
if (client.useCliInstall) {
try {
console.log(`Uninstalling from ${client.name}...`);
execSync('claude mcp remove mcp-connect', {
stdio: 'inherit',
encoding: 'utf-8'
});
console.log(`✓ ${client.name}: Uninstalled successfully`);
return true;
} catch (error) {
console.error(`✗ ${client.name}: Uninstall failed`);
console.error(` Try running manually: claude mcp remove mcp-connect`);
return false;
}
}
const config = readConfig(client.configPath);
if (!config[client.configKey]?.['mcp-connect']) {
console.log(`✓ ${client.name}: Not installed`);
return true;
}
delete config[client.configKey]['mcp-connect'];
writeConfig(client.configPath, config);
console.log(`✓ ${client.name}: Uninstalled successfully`);
return true;
}
function listClients() {
console.log('\nSupported MCP clients:\n');
for (const [id, client] of Object.entries(CLIENTS)) {
console.log(` ${id.padEnd(10)} - ${client.name}`);
console.log(` ${client.configPath}`);
}
console.log('');
}
function showStatus() {
console.log('\n@plaintest/mcp-connect installation status:\n');
for (const [id, client] of Object.entries(CLIENTS)) {
const config = readConfig(client.configPath);
const installed = !!config[client.configKey]?.['mcp-connect'];
const status = installed ? '✓ Installed' : '✗ Not installed';
const configExists = existsSync(client.configPath);
console.log(` ${client.name.padEnd(20)} ${status}`);
if (!configExists) {
console.log(` (config file not found)`);
}
}
console.log('');
}
function installAll(): boolean {
console.log('\nInstalling @plaintest/mcp-connect for all clients...\n');
let success = true;
for (const clientId of Object.keys(CLIENTS)) {
if (!install(clientId)) {
success = false;
}
}
return success;
}
function uninstallAll(): boolean {
console.log('\nUninstalling @plaintest/mcp-connect from all clients...\n');
let success = true;
for (const clientId of Object.keys(CLIENTS)) {
if (!uninstall(clientId)) {
success = false;
}
}
return success;
}
export function runCli(args: string[]): boolean {
const command = args[0];
const target = args[1];
switch (command) {
case 'install':
if (target === '--all' || target === 'all') {
return installAll();
}
if (!target) {
console.error('Error: Please specify a client to install');
console.error('Example: npx @plaintest/mcp-connect install claude');
console.error(' npx @plaintest/mcp-connect install --all');
return false;
}
return install(target);
case 'uninstall':
if (target === '--all' || target === 'all') {
return uninstallAll();
}
if (!target) {
console.error('Error: Please specify a client to uninstall');
return false;
}
return uninstall(target);
case 'list':
listClients();
return true;
case 'status':
showStatus();
return true;
case 'help':
case '--help':
case '-h':
printUsage();
return true;
default:
// Unknown command, return false to indicate server should start
return false;
}
}