Skip to main content
Glama
node_mcp_server.js14.3 kB
#!/usr/bin/env node /** * Remote Terminal MCP Server - Pure Node.js Implementation * 完整的远程终端 MCP 服务器 */ const readline = require('readline'); const fs = require('fs'); const path = require('path'); const os = require('os'); const { spawn } = require('child_process'); class RemoteTerminalMCPServer { constructor() { this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); this.configDir = path.join(os.homedir(), '.remote-terminal'); this.configFile = path.join(this.configDir, 'config.yaml'); this.setupHandlers(); this.log('Remote Terminal MCP Server starting...'); this.ensureConfigDir(); } log(message) { console.error(`[RT-MCP] ${new Date().toISOString()} ${message}`); } ensureConfigDir() { if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { recursive: true }); this.log(`Created config directory: ${this.configDir}`); } } setupHandlers() { this.rl.on('line', (line) => { try { const message = JSON.parse(line); this.handleMessage(message); } catch (error) { this.log(`Error parsing message: ${error.message}`); } }); this.rl.on('close', () => { this.log('Input stream closed'); process.exit(0); }); process.on('SIGINT', () => { this.log('Received SIGINT, shutting down...'); process.exit(0); }); } handleMessage(message) { switch (message.method) { case 'initialize': this.handleInitialize(message); break; case 'tools/list': this.handleToolsList(message); break; case 'tools/call': this.handleToolCall(message); break; default: this.sendError(message.id, -32601, 'Method not found'); } } handleInitialize(message) { const response = { jsonrpc: '2.0', id: message.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {}, logging: {} }, serverInfo: { name: 'remote-terminal-mcp', version: '0.13.3' } } }; this.sendResponse(response); } handleToolsList(message) { const tools = [ { name: 'list_servers', description: 'List all configured remote servers', inputSchema: { type: 'object', properties: {} } }, { name: 'interactive_config_wizard', description: 'Launch interactive configuration wizard to set up a new server', inputSchema: { type: 'object', properties: { server_type: { type: 'string', enum: ['ssh', 'relay', 'docker', 'custom'], description: 'Type of server to configure' }, quick_mode: { type: 'boolean', default: true, description: 'Use quick configuration mode' } } } }, { name: 'create_server_config', description: 'Create a new server configuration', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Server name (unique identifier)' }, host: { type: 'string', description: 'Server hostname or IP address' }, username: { type: 'string', description: 'Username for SSH connection' }, port: { type: 'integer', default: 22, description: 'SSH port' }, description: { type: 'string', description: 'Server description' } }, required: ['name', 'host', 'username'] } }, { name: 'connect_server', description: 'Connect to a remote server', inputSchema: { type: 'object', properties: { server_name: { type: 'string', description: 'Name of the server to connect to' } }, required: ['server_name'] } }, { name: 'execute_command', description: 'Execute a command on a server', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'Command to execute' }, server: { type: 'string', description: 'Server name (optional, uses default if not specified)' } }, required: ['command'] } } ]; const response = { jsonrpc: '2.0', id: message.id, result: { tools } }; this.sendResponse(response); } async handleToolCall(message) { const { name, arguments: args } = message.params; try { let result; switch (name) { case 'list_servers': result = await this.listServers(); break; case 'interactive_config_wizard': result = await this.interactiveConfigWizard(args); break; case 'create_server_config': result = await this.createServerConfig(args); break; case 'connect_server': result = await this.connectServer(args); break; case 'execute_command': result = await this.executeCommand(args); break; default: throw new Error(`Unknown tool: ${name}`); } this.sendResponse({ jsonrpc: '2.0', id: message.id, result: { content: [ { type: 'text', text: result } ] } }); } catch (error) { this.sendError(message.id, -32000, error.message); } } async listServers() { if (!fs.existsSync(this.configFile)) { return 'No servers configured yet. Use the interactive_config_wizard to set up your first server.'; } try { const configContent = fs.readFileSync(this.configFile, 'utf8'); // 简单的 YAML 解析(实际项目中应该使用专门的 YAML 库) const servers = this.parseSimpleYaml(configContent); if (Object.keys(servers).length === 0) { return 'No servers configured yet.'; } let result = 'Configured servers:\n\n'; for (const [name, config] of Object.entries(servers)) { result += `📡 ${name}\n`; result += ` Host: ${config.host || 'N/A'}\n`; result += ` User: ${config.username || 'N/A'}\n`; result += ` Port: ${config.port || 22}\n`; if (config.description) { result += ` Description: ${config.description}\n`; } result += '\n'; } return result; } catch (error) { return `Error reading server configurations: ${error.message}`; } } async interactiveConfigWizard(args) { const serverType = args.server_type || 'ssh'; const quickMode = args.quick_mode !== false; return `🚀 Interactive Configuration Wizard Server Type: ${serverType} Quick Mode: ${quickMode ? 'Enabled' : 'Disabled'} This is a Node.js implementation of the configuration wizard. In a full implementation, this would guide you through: 1. Server connection details (host, username, port) 2. Authentication method (SSH key, password) 3. Optional Docker configuration 4. Connection testing 5. Saving the configuration For now, please use the create_server_config tool to manually create a server configuration. Example: - Name: my-server - Host: example.com - Username: myuser - Port: 22`; } async createServerConfig(args) { const { name, host, username, port = 22, description } = args; if (!name || !host || !username) { throw new Error('Missing required parameters: name, host, username'); } // 创建服务器配置 const serverConfig = { [name]: { host, username, port, description: description || `Server ${name}`, connection_type: 'ssh', created_at: new Date().toISOString() } }; // 读取现有配置 let existingConfig = {}; if (fs.existsSync(this.configFile)) { try { const content = fs.readFileSync(this.configFile, 'utf8'); existingConfig = this.parseSimpleYaml(content); } catch (error) { this.log(`Warning: Could not parse existing config: ${error.message}`); } } // 合并配置 const newConfig = { ...existingConfig, ...serverConfig }; // 保存配置 const yamlContent = this.generateSimpleYaml(newConfig); fs.writeFileSync(this.configFile, yamlContent, 'utf8'); return `✅ Server configuration created successfully! Server: ${name} Host: ${host} Username: ${username} Port: ${port} ${description ? `Description: ${description}` : ''} Configuration saved to: ${this.configFile} You can now use connect_server to connect to this server.`; } async connectServer(args) { const { server_name } = args; if (!server_name) { throw new Error('Server name is required'); } // 这里应该实现实际的连接逻辑 return `🔌 Connecting to server: ${server_name} Note: This is a Node.js implementation demo. In a full implementation, this would: 1. Read server configuration from ${this.configFile} 2. Establish SSH connection 3. Set up terminal session 4. Return connection status For now, this is a placeholder response.`; } async executeCommand(args) { const { command, server } = args; if (!command) { throw new Error('Command is required'); } // 这里应该实现实际的命令执行逻辑 return `💻 Executing command: ${command} ${server ? `On server: ${server}` : 'On default server'} Note: This is a Node.js implementation demo. In a full implementation, this would: 1. Connect to the specified server (or default) 2. Execute the command remotely 3. Return the command output 4. Handle errors and timeouts For now, this is a placeholder response.`; } // 简单的 YAML 解析器(仅用于演示) parseSimpleYaml(content) { const result = {}; const lines = content.split('\n'); let currentServer = null; for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; if (!trimmed.startsWith(' ') && trimmed.endsWith(':')) { currentServer = trimmed.slice(0, -1); result[currentServer] = {}; } else if (currentServer && trimmed.includes(':')) { const [key, ...valueParts] = trimmed.split(':'); const value = valueParts.join(':').trim(); result[currentServer][key.trim()] = value; } } return result; } // 简单的 YAML 生成器(仅用于演示) generateSimpleYaml(config) { let yaml = '# Remote Terminal MCP Configuration\n'; yaml += `# Generated at: ${new Date().toISOString()}\n\n`; for (const [serverName, serverConfig] of Object.entries(config)) { yaml += `${serverName}:\n`; for (const [key, value] of Object.entries(serverConfig)) { yaml += ` ${key}: ${value}\n`; } yaml += '\n'; } return yaml; } sendResponse(response) { console.log(JSON.stringify(response)); } sendError(id, code, message) { const error = { jsonrpc: '2.0', id: id, error: { code: code, message: message } }; console.log(JSON.stringify(error)); } } // 启动服务器 if (require.main === module) { new RemoteTerminalMCPServer(); } module.exports = RemoteTerminalMCPServer;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/maricoxu/remote-terminal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server