import express from 'express'; import { spawn } from 'child_process'; import fs from 'fs'; import cors from 'cors'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; const API_TOKEN = 'FFG-Trinity-2026-Core-Access'; const LOG_FILE = '/home/claude_executor/mcp-server/command.log'; const PORT = 3000; const BASE_URL = 'https://mcp.firefrostgaming.com'; const SERVERS = { 'command-center': { host: '63.143.34.217', user: 'root' }, 'tx1-dallas': { host: '38.68.14.26', user: 'root' }, 'nc1-charlotte': { host: '216.239.104.130', user: 'root' }, 'panel-vps': { host: '45.94.168.138', user: 'root' }, 'dev-panel': { host: '64.50.188.128', user: 'root' }, 'wiki-vps': { host: '64.50.188.14', user: 'architect' }, 'services-vps': { host: '38.68.14.188', user: 'root' }, 'trinity-core': { host: 'localhost', user: 'claude_executor', local: true } }; function log(msg) { const line = `[${new Date().toISOString()}] ${msg}\n`; fs.appendFileSync(LOG_FILE, line); console.log(line.trim()); } const app = express(); app.use(cors({ origin: '*', credentials: true })); app.use(express.json()); app.use(express.urlencoded({ extended: true })); function auth(req, res, next) { if (req.method === 'OPTIONS') return next(); const token = req.headers.authorization?.replace('Bearer ', ''); if (token !== API_TOKEN) { log(`AUTH FAILED from ${req.ip}`); res.setHeader('WWW-Authenticate', 'Bearer realm="TrinityCore"'); return res.status(401).json({ error: 'Unauthorized' }); } next(); } app.get('/.well-known/oauth-protected-resource', (req, res) => { log(`OAuth discovery`); res.json({ authorization_server: BASE_URL, authorization_endpoint: `${BASE_URL}/authorize`, token_endpoint: `${BASE_URL}/token` }); }); app.get('/authorize', (req, res) => { const { redirect_uri, state } = req.query; log(`OAUTH authorize -> ${redirect_uri}`); res.redirect(`${redirect_uri}?code=trinity-auth-code-123&state=${state}`); }); app.post('/token', (req, res) => { log(`OAUTH token issued`); res.json({ access_token: API_TOKEN, token_type: 'Bearer', expires_in: 31536000 }); }); function executeLocal(command) { return new Promise((resolve) => { log(`EXEC [trinity-core] (local) ${command}`); const proc = spawn('bash', ['-c', command]); let stdout = '', stderr = ''; proc.stdout.on('data', (d) => stdout += d.toString()); proc.stderr.on('data', (d) => stderr += d.toString()); const timeout = setTimeout(() => { proc.kill(); resolve({ success: false, stdout: stdout.trim(), stderr: 'Timeout', error: 'Timeout' }); }, 30000); proc.on('close', (code) => { clearTimeout(timeout); resolve({ success: code === 0, stdout: stdout.trim(), stderr: stderr.trim(), error: code === 0 ? null : `Exit ${code}` }); }); proc.on('error', (err) => { clearTimeout(timeout); resolve({ success: false, stdout: '', stderr: err.message, error: err.message }); }); }); } function executeSSH(server, command) { return new Promise((resolve) => { const target = SERVERS[server]; log(`EXEC [${server}] ${command}`); const ssh = spawn('ssh', ['-o', 'ConnectTimeout=10', '-o', 'StrictHostKeyChecking=no', `${target.user}@${target.host}`, command]); let stdout = '', stderr = ''; ssh.stdout.on('data', (data) => stdout += data.toString()); ssh.stderr.on('data', (data) => stderr += data.toString()); const timeout = setTimeout(() => { ssh.kill(); resolve({ success: false, stdout: stdout.trim(), stderr: 'Timeout', error: 'Timeout' }); }, 30000); ssh.on('close', (code) => { clearTimeout(timeout); resolve({ success: code === 0, stdout: stdout.trim(), stderr: stderr.trim(), error: code === 0 ? null : `Exit ${code}` }); }); ssh.on('error', (err) => { clearTimeout(timeout); resolve({ success: false, stdout: '', stderr: err.message, error: err.message }); }); }); } function setupToolHandlers(mcpServer) { mcpServer.setRequestHandler(ListToolsRequestSchema, async () => { log(`>>> ListTools request`); return { tools: [ { name: "list_servers", description: "Get available Firefrost servers.", inputSchema: { type: "object", properties: {} } }, { name: "run_command", description: "Execute SSH command on a Firefrost server.", inputSchema: { type: "object", properties: { server: { type: "string" }, command: { type: "string" } }, required: ["server", "command"] }} ]}; }); mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => { log(`>>> CallTool: ${request.params.name}`); if (request.params.name === "list_servers") { return { content: [{ type: "text", text: `Available: ${Object.keys(SERVERS).join(", ")}` }] }; } if (request.params.name === "run_command") { const { server, command } = request.params.arguments; if (!SERVERS[server]) return { content: [{ type: "text", text: `Unknown server ${server}` }], isError: true }; const target = SERVERS[server]; const result = target.local ? await executeLocal(command) : await executeSSH(server, command); let output = result.stdout || '(no output)'; if (result.stderr) output += `\nSTDERR: ${result.stderr}`; return { content: [{ type: "text", text: output }], isError: !result.success }; } }); } const activeSessions = new Map(); // ─── REST API (for Arbiter / internal services) ─── app.get('/servers', auth, (req, res) => { log(`REST /servers`); res.json({ servers: Object.keys(SERVERS) }); }); app.post('/exec', auth, async (req, res) => { const { server, command } = req.body; if (!server || !command) { return res.status(400).json({ error: 'Missing server or command' }); } if (!SERVERS[server]) { return res.status(400).json({ error: `Unknown server: ${server}` }); } const target = SERVERS[server]; const result = target.local ? await executeLocal(command) : await executeSSH(server, command); log(`REST /exec [${server}] -> ${result.success ? 'OK' : 'FAIL'}`); res.json({ success: result.success, output: result.stdout || '', stderr: result.stderr || '', error: result.error }); }); // ─── MCP Protocol (for Claude.ai connector) ─── app.get('/mcp', auth, async (req, res) => { log(`SSE connection from ${req.ip}`); const mcpServer = new Server({ name: "trinity-core", version: "2.2.0" }, { capabilities: { tools: {} } }); setupToolHandlers(mcpServer); mcpServer.onmessage = (msg) => log(`SERVER MSG: ${JSON.stringify(msg)}`); const transport = new SSEServerTransport(`${BASE_URL}/mcp/messages`, res); await mcpServer.connect(transport); activeSessions.set(transport.sessionId, transport); log(`Session ${transport.sessionId} ready`); res.on('close', () => { log(`SSE closed: ${transport.sessionId}`); activeSessions.delete(transport.sessionId); }); }); app.post('/mcp/messages', auth, async (req, res) => { const sessionId = req.query.sessionId; const method = req.body?.method || 'unknown'; log(`POST ${method} for ${sessionId}`); const transport = activeSessions.get(sessionId); if (!transport) { log(`Session not found: ${sessionId}`); return res.status(404).json({ error: "Session not found" }); } try { await transport.handlePostMessage(req, res, req.body); log(`POST ${method} handled OK`); } catch (err) { log(`POST ${method} ERROR: ${err.message}`); console.error(err); } }); app.listen(PORT, () => log(`Trinity Core MCP v2.2.0 started on port ${PORT}`));