#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import {
getEnvironmentInfo,
checkIdbAvailability,
isIdbAvailable,
} from './utils/index.js';
import {
BROWSER_TOOLS,
IOS_SIMULATOR_TOOLS,
handleBrowserTool,
handleIOSSimulatorTool,
isBrowserTool,
isIOSSimulatorTool,
closeBrowser,
} from './tools/index.js';
import { runCli } from './cli.js';
// Check for CLI commands first
const CLI_COMMANDS = ['install', 'uninstall', 'list', 'status', 'help', '--help', '-h'];
const args = process.argv.slice(2);
if (args.length > 0 && CLI_COMMANDS.includes(args[0])) {
const success = runCli(args);
process.exit(success ? 0 : 1);
}
// Parse --only flag for tool filtering
const onlyArg = args.find(a => a.startsWith('--only='));
const onlyMode = onlyArg?.split('=')[1] as 'browser' | 'simulator' | 'all' | undefined;
// Start the server
async function main() {
// Get environment information
const env = await getEnvironmentInfo();
await checkIdbAvailability();
// Determine which tools to enable
const enableBrowser = onlyMode === 'all' || onlyMode === 'browser' || (!onlyMode && !!env.chrome);
const enableSimulator = onlyMode === 'all' || onlyMode === 'simulator' || (!onlyMode && !!env.xcrun);
// Build available tools list
const availableTools = [
...(enableBrowser ? BROWSER_TOOLS : []),
...(enableSimulator ? IOS_SIMULATOR_TOOLS : []),
];
// Create MCP server
const server = new Server(
{
name: '@plaintest/mcp-connect',
version: '0.0.8',
},
{
capabilities: {
tools: {},
},
}
);
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: availableTools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: toolArgs } = request.params;
try {
// Try browser tools first
if (enableBrowser && isBrowserTool(name)) {
const result = await handleBrowserTool(name, toolArgs);
if (result) return result;
}
// Try iOS simulator tools
if (enableSimulator && isIOSSimulatorTool(name)) {
const result = await handleIOSSimulatorTool(name, toolArgs);
if (result) return result;
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
// Print startup info
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('@plaintest/mcp-connect');
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('');
if (onlyMode) {
console.error(`Mode: --only=${onlyMode}`);
} else {
console.error('Mode: auto-detect (use --only=browser|simulator|all to override)');
}
console.error('');
console.error('Tools enabled:');
if (enableSimulator) {
console.error(` ✓ iOS Simulator (${IOS_SIMULATOR_TOOLS.length} tools)`);
}
if (enableBrowser) {
console.error(` ✓ Browser Automation (${BROWSER_TOOLS.length} tools)`);
}
const disabledReasons: string[] = [];
if (!enableSimulator) {
const reason = onlyMode ? '--only flag' : 'xcrun not found';
disabledReasons.push(` ✗ iOS Simulator (${reason})`);
}
if (!enableBrowser) {
const reason = onlyMode ? '--only flag' : 'Chrome not found';
disabledReasons.push(` ✗ Browser Automation (${reason})`);
}
if (disabledReasons.length > 0) {
console.error('');
console.error('Tools disabled:');
disabledReasons.forEach(r => console.error(r));
}
console.error('');
console.error(`Total: ${availableTools.length} tools available`);
if (!isIdbAvailable() && enableSimulator) {
console.error('');
console.error('Tip: Install fb-idb for full iOS gesture support:');
console.error(' brew tap facebook/fb && brew install idb-companion');
}
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('');
// Graceful shutdown
const shutdown = async () => {
console.error('Shutting down...');
await closeBrowser();
process.exit(0);
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
// Start stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP server running on stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});