From 03974d1f139c91c83c73a73fa473e097309958ac Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #78)" Date: Sat, 11 Apr 2026 11:55:22 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Task=20#109=20=E2=80=94=20MCP=20Logging?= =?UTF-8?q?=20in=20Trinity=20Console=20(v2.3.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arbiter changes: - POST /api/internal/mcp/log endpoint in api.js - MCP Logs admin route (/admin/mcp-logs) with filters, stats, pagination - EJS view with expandable detail rows, server color badges - Sidebar link under System group Trinity Core v2.3.0: - logToArbiter() function POSTs to Arbiter after every command - Both MCP (Claude.ai) and REST (/exec) paths log with execution timing - Async logging — doesn't block command response Database: - mcp_logs table created on Command Center (indexes on server, time, success) Architecture: Trinity Core → command → response + async POST → Arbiter → PostgreSQL → Trinity Console Chronicler #78 | firefrost-services --- .../arbiter-3.0/src/routes/admin/index.js | 2 + .../arbiter-3.0/src/routes/admin/mcp-logs.js | 98 +++++++++++ services/arbiter-3.0/src/routes/api.js | 27 +++ .../src/views/admin/mcp-logs/index.ejs | 161 ++++++++++++++++++ services/arbiter-3.0/src/views/layout.ejs | 3 + services/trinity-core/index.js | 29 +++- services/trinity-core/package.json | 2 +- 7 files changed, 318 insertions(+), 4 deletions(-) create mode 100644 services/arbiter-3.0/src/routes/admin/mcp-logs.js create mode 100644 services/arbiter-3.0/src/views/admin/mcp-logs/index.ejs diff --git a/services/arbiter-3.0/src/routes/admin/index.js b/services/arbiter-3.0/src/routes/admin/index.js index 435a772..9b86102 100644 --- a/services/arbiter-3.0/src/routes/admin/index.js +++ b/services/arbiter-3.0/src/routes/admin/index.js @@ -17,6 +17,7 @@ const systemRouter = require('./system'); const socialRouter = require('./social'); const infrastructureRouter = require('./infrastructure'); const aboutRouter = require('./about'); +const mcpLogsRouter = require('./mcp-logs'); router.use(requireTrinityAccess); @@ -121,5 +122,6 @@ router.use('/system', systemRouter); router.use('/social', socialRouter); router.use('/infrastructure', infrastructureRouter); router.use('/about', aboutRouter); +router.use('/mcp-logs', mcpLogsRouter); module.exports = router; diff --git a/services/arbiter-3.0/src/routes/admin/mcp-logs.js b/services/arbiter-3.0/src/routes/admin/mcp-logs.js new file mode 100644 index 0000000..07e0b20 --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/mcp-logs.js @@ -0,0 +1,98 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../../database'); + +/** + * MCP Logs Module — Trinity Console + * + * View and filter command execution logs from Trinity Core. + * + * GET /admin/mcp-logs — Main logs page with filters + * + * Chronicler #78 | April 11, 2026 + */ + +const SERVERS = [ + 'command-center', 'tx1-dallas', 'nc1-charlotte', + 'panel-vps', 'dev-panel', 'wiki-vps', 'services-vps', 'trinity-core' +]; + +router.get('/', async (req, res) => { + try { + const { server, success, limit = 50, offset = 0 } = req.query; + + // Build filtered query + let where = 'WHERE 1=1'; + const params = []; + let p = 0; + + if (server) { + p++; + where += ` AND server = $${p}`; + params.push(server); + } + + if (success !== undefined && success !== '') { + p++; + where += ` AND success = $${p}`; + params.push(success === 'true'); + } + + // Get logs + const logsResult = await db.query( + `SELECT * FROM mcp_logs ${where} ORDER BY executed_at DESC LIMIT $${p + 1} OFFSET $${p + 2}`, + [...params, parseInt(limit), parseInt(offset)] + ); + + // Get total count + const countResult = await db.query( + `SELECT COUNT(*) FROM mcp_logs ${where}`, + params + ); + const total = parseInt(countResult.rows[0].count); + + // Get stats + const statsResult = await db.query(` + SELECT + COUNT(*) as total, + COUNT(*) FILTER (WHERE success = true) as success_count, + COUNT(*) FILTER (WHERE success = false) as fail_count, + COALESCE(ROUND(AVG(execution_time_ms)), 0) as avg_time + FROM mcp_logs + `); + + const stats = statsResult.rows[0]; + + res.render('admin/mcp-logs/index', { + title: 'MCP Logs', + currentPath: '/mcp-logs', + logs: logsResult.rows, + total, + limit: parseInt(limit), + offset: parseInt(offset), + query: req.query, + stats, + servers: SERVERS, + adminUser: req.user, + layout: 'layout' + }); + } catch (err) { + console.error('[MCP Logs] Route error:', err); + res.render('admin/mcp-logs/index', { + title: 'MCP Logs', + currentPath: '/mcp-logs', + logs: [], + total: 0, + limit: 50, + offset: 0, + query: {}, + stats: { total: 0, success_count: 0, fail_count: 0, avg_time: 0 }, + servers: SERVERS, + error: err.message, + adminUser: req.user, + layout: 'layout' + }); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/routes/api.js b/services/arbiter-3.0/src/routes/api.js index 03a72e4..da20b66 100644 --- a/services/arbiter-3.0/src/routes/api.js +++ b/services/arbiter-3.0/src/routes/api.js @@ -368,4 +368,31 @@ router.get('/social/digest', async (req, res) => { } }); +// ============================================================================= +// POST /api/internal/mcp/log +// Log a command execution from Trinity Core +// ============================================================================= + +router.post('/mcp/log', async (req, res) => { + try { + const { server, command, success, stdout, stderr, error, execution_time_ms } = req.body; + + if (!server || !command || success === undefined) { + return res.status(400).json({ error: 'Missing required fields: server, command, success' }); + } + + const result = await db.query( + `INSERT INTO mcp_logs (server, command, success, stdout, stderr, error, execution_time_ms) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id`, + [server, command, success, stdout || '', stderr || '', error || null, execution_time_ms || null] + ); + + res.json({ success: true, id: result.rows[0].id }); + } catch (err) { + console.error('❌ [MCP Log] Error:', err); + res.status(500).json({ error: 'Failed to log command' }); + } +}); + module.exports = router; diff --git a/services/arbiter-3.0/src/views/admin/mcp-logs/index.ejs b/services/arbiter-3.0/src/views/admin/mcp-logs/index.ejs new file mode 100644 index 0000000..ebd847e --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/mcp-logs/index.ejs @@ -0,0 +1,161 @@ + + + + +
+
+
Total Commands
+
<%= stats.total %>
+
+
+
Successful
+
<%= stats.success_count %>
+
+
+
Failed
+
<%= stats.fail_count %>
+
+
+
Avg Execution
+
<%= stats.avg_time %>ms
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + Reset +
+
+
+ + +
+ + + + + + + + + + + + + <% if (logs.length === 0) { %> + + + + <% } %> + <% logs.forEach(log => { + const time = new Date(log.executed_at); + const timeStr = time.toLocaleString('en-US', { month:'short', day:'numeric', hour:'numeric', minute:'2-digit', second:'2-digit', hour12:true }); + const cmdShort = log.command.length > 60 ? log.command.substring(0, 60) + '...' : log.command; + %> + + + + + + + + + + + + + <% }); %> + +
TimeServerCommandStatusDuration
+ <% if (stats.total == 0) { %> + No commands logged yet. Commands executed via Trinity Core will appear here. + <% } else { %> + No logs match your filters. + <% } %> +
<%= timeStr %> + <%= log.server %> + <%= cmdShort %> + <% if (log.success) { %> + ✓ OK + <% } else { %> + ✗ Fail + <% } %> + <%= log.execution_time_ms || '—' %>ms
+
+ + +<% if (total > limit) { %> +
+ <% const totalPages = Math.ceil(total / limit); %> + <% const currentPage = Math.floor(offset / limit) + 1; %> + <% for (let i = 1; i <= totalPages && i <= 10; i++) { + const pageOffset = (i - 1) * limit; + const params = new URLSearchParams({...query, offset: pageOffset}); + params.delete('offset'); + if (pageOffset > 0) params.set('offset', pageOffset); + %> + + <%= i %> + + <% } %> +
+<% } %> + + diff --git a/services/arbiter-3.0/src/views/layout.ejs b/services/arbiter-3.0/src/views/layout.ejs index 3e68e36..6f6c88e 100644 --- a/services/arbiter-3.0/src/views/layout.ejs +++ b/services/arbiter-3.0/src/views/layout.ejs @@ -118,6 +118,9 @@ 🔍 Role Audit + + 🖥️ MCP Logs +
diff --git a/services/trinity-core/index.js b/services/trinity-core/index.js index b8bed40..f36ae60 100644 --- a/services/trinity-core/index.js +++ b/services/trinity-core/index.js @@ -10,6 +10,8 @@ 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 ARBITER_LOG_URL = 'https://discord-bot.firefrostgaming.com/api/internal/mcp/log'; +const ARBITER_TOKEN = '6fYF1akCRW6pM2F8n3S3RxeIod4YgRniUJNEQurvBP4='; const SERVERS = { 'command-center': { host: '63.143.34.217', user: 'root' }, @@ -28,6 +30,21 @@ function log(msg) { console.log(line.trim()); } +async function logToArbiter(data) { + try { + await fetch(ARBITER_LOG_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${ARBITER_TOKEN}` + }, + body: JSON.stringify(data) + }); + } catch (err) { + log(`Arbiter log failed: ${err.message}`); + } +} + const app = express(); app.use(cors({ origin: '*', credentials: true })); app.use(express.json()); @@ -119,9 +136,12 @@ function setupToolHandlers(mcpServer) { const { server, command } = request.params.arguments; if (!SERVERS[server]) return { content: [{ type: "text", text: `Unknown server ${server}` }], isError: true }; const target = SERVERS[server]; + const startTime = Date.now(); const result = target.local ? await executeLocal(command) : await executeSSH(server, command); + const executionTime = Date.now() - startTime; + logToArbiter({ server, command, success: result.success, stdout: result.stdout, stderr: result.stderr, error: result.error, execution_time_ms: executionTime }); let output = result.stdout || '(no output)'; if (result.stderr) output += `\nSTDERR: ${result.stderr}`; return { content: [{ type: "text", text: output }], isError: !result.success }; @@ -147,10 +167,13 @@ app.post('/exec', auth, async (req, res) => { return res.status(400).json({ error: `Unknown server: ${server}` }); } const target = SERVERS[server]; + const startTime = Date.now(); const result = target.local ? await executeLocal(command) : await executeSSH(server, command); - log(`REST /exec [${server}] -> ${result.success ? 'OK' : 'FAIL'}`); + const executionTime = Date.now() - startTime; + log(`REST /exec [${server}] -> ${result.success ? 'OK' : 'FAIL'} (${executionTime}ms)`); + logToArbiter({ server, command, success: result.success, stdout: result.stdout, stderr: result.stderr, error: result.error, execution_time_ms: executionTime }); res.json({ success: result.success, output: result.stdout || '', @@ -164,7 +187,7 @@ app.post('/exec', auth, async (req, res) => { 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: {} } }); + const mcpServer = new Server({ name: "trinity-core", version: "2.3.0" }, { capabilities: { tools: {} } }); setupToolHandlers(mcpServer); mcpServer.onmessage = (msg) => log(`SERVER MSG: ${JSON.stringify(msg)}`); @@ -201,4 +224,4 @@ app.post('/mcp/messages', auth, async (req, res) => { } }); -app.listen(PORT, () => log(`Trinity Core MCP v2.2.0 started on port ${PORT}`)); +app.listen(PORT, () => log(`Trinity Core MCP v2.3.0 started on port ${PORT}`)); diff --git a/services/trinity-core/package.json b/services/trinity-core/package.json index fa12a6d..59e4005 100644 --- a/services/trinity-core/package.json +++ b/services/trinity-core/package.json @@ -1,6 +1,6 @@ { "name": "trinity-core", - "version": "2.2.0", + "version": "2.3.0", "type": "module", "main": "index.js", "dependencies": {