Files
firefrost-operations-manual/docs/consultations/gemini-mcp-web-implementation-2026-04-11.md
Claude 0956ea7d15 Add Task #112: Trinity Core Security Hardening
Gemini identified 5 concerns:
1. Command injection (CRITICAL) - Fix: use spawn instead of exec
2. Race conditions (MEDIUM) - Future: server locking
3. SD card wear (HIGH) - FIXED: cron log rotation
4. Cloudflare Access (MEDIUM) - Future enhancement
5. System prompts (LOW) - Document for Catalyst/Orb

Updated MCP implementation spec with secure spawn-based SSH execution.

Task #112 is a BLOCKER for Task #111 (web MCP deployment).

Chronicler #76
2026-04-11 07:57:11 +00:00

13 KiB

Gemini Consultation: Trinity Core Web MCP — Implementation Request

Date: April 11, 2026 @ 3:00 AM CT
From: Michael (The Wizard) + Claude (Chronicler #76)
To: Gemini (Architectural Partner)
Re: Complete implementation code for native web MCP connector


Hey Gemini!

Option B it is — we're going for the native web connector. The RV dream demands it.

We need the complete implementation code so we (or the next Chronicler) can upgrade Trinity Core from REST API to native Claude.ai web connector.


What We Have Now

Current Trinity Core (/home/claude_executor/mcp-server/index.js):

const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const app = express();

app.use(express.json());

const API_TOKEN = 'FFG-Trinity-2026-Core-Access';
const LOG_FILE = '/home/claude_executor/mcp-server/command.log';

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' }
};

function log(msg) {
  const line = `[${new Date().toISOString()}] ${msg}\n`;
  fs.appendFileSync(LOG_FILE, line);
  console.log(line.trim());
}

function auth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (token !== API_TOKEN) {
    log(`AUTH FAILED from ${req.ip}`);
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

app.get('/', (req, res) => {
  res.json({ status: 'Trinity Core Online', timestamp: new Date().toISOString() });
});

app.get('/servers', auth, (req, res) => {
  res.json({ servers: Object.entries(SERVERS).map(([name, info]) => ({ name, ...info })) });
});

app.post('/exec', auth, (req, res) => {
  const { command, server } = req.body;
  
  if (!command || !server) {
    return res.status(400).json({ error: 'Missing command or server' });
  }
  
  const target = SERVERS[server];
  if (!target) {
    return res.status(400).json({ error: `Unknown server: ${server}` });
  }
  
  log(`EXEC [${server}] ${command}`);
  
  const sshCmd = `ssh -o ConnectTimeout=10 ${target.user}@${target.host} "${command.replace(/"/g, '\\"')}"`;
  
  exec(sshCmd, { timeout: 30000 }, (error, stdout, stderr) => {
    const success = !error;
    log(`RESULT [${server}] success=${success}`);
    res.json({
      server,
      command,
      success,
      stdout: stdout.trim(),
      stderr: stderr.trim(),
      error: error ? error.message : null
    });
  });
});

const PORT = 3000;
app.listen(PORT, () => {
  log('Trinity Core MCP Server started');
});

Environment:

  • Raspberry Pi 4B running Raspberry Pi OS Lite (64-bit)
  • Node.js 20.x
  • Cloudflare Tunnel exposing https://mcp.firefrostgaming.com
  • Express.js

What We Need

Please provide the complete, production-ready code for:

1. Updated index.js with MCP SDK

  • MCP HTTP/SSE transport (replacing our REST endpoints)
  • Keep our existing SERVERS object and SSH execution logic
  • Keep our logging
  • Define two tools:
    • list_servers — Returns available servers
    • run_command — Executes command on specified server

2. OAuth Shim Endpoints

  • GET /.well-known/oauth-protected-resource — Discovery endpoint
  • GET /authorize — Renders a simple "Authorize" button/auto-redirect
  • POST /token — Returns our Bearer token

Since Michael is the only user, this can be as simple as possible — no real user database needed.

3. Package.json Updates

  • Any new dependencies we need to install

4. Registration Steps

  • Exact steps to register in Claude.ai once deployed

Constraints

  • Must work on Raspberry Pi (ARM64, limited resources)
  • Must work behind Cloudflare Tunnel (HTTPS termination at Cloudflare)
  • Single user (Michael only, no need for real OAuth user management)
  • Keep existing SSH logic — it works perfectly

Bonus Questions

  1. CORS — Do we need any special CORS headers for Claude.ai to connect?

  2. Error handling — What happens if the SSE connection drops mid-command?

  3. Security — Any additional hardening beyond the OAuth shim?

  4. Testing — How can we test the MCP endpoint locally before registering with Claude.ai?


Thanks for being our architect on this late-night mission! 🔥❄️

— Michael + Claude (Chronicler #76)


Gemini's Response (April 11, 2026 @ 3:15 AM CT)

Summary: Complete production-ready implementation provided. Uses ES Modules, MCP SDK with SSE transport, and a dummy OAuth shim.


1. Package Updates

cd /home/claude_executor/mcp-server/
npm install @modelcontextprotocol/sdk cors express

package.json:

{
  "name": "trinity-core",
  "version": "2.0.0",
  "type": "module",
  "main": "index.js",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.6.0",
    "cors": "^2.8.5",
    "express": "^4.19.2"
  }
}

2. Complete index.js Implementation

Block A: Imports & Configuration

import express from 'express';
import { exec } 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' }
};

function log(msg) {
  const line = `[${new Date().toISOString()}] ${msg}\n`;
  fs.appendFileSync(LOG_FILE, line);
  console.log(line.trim());
}

Block B: Express Setup & Middleware

const app = express();

// CORS is required for Claude.ai to make browser-based requests to your tunnel
app.use(cors({ origin: 'https://claude.ai', credentials: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Auth Middleware (Applied only to the active message endpoint)
function auth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (token !== API_TOKEN) {
    log(`AUTH FAILED from ${req.ip}`);
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

Block C: The "Dummy" OAuth Shim

// 1. Discovery Endpoint
app.get('/.well-known/oauth-protected-resource', (req, res) => {
  res.json({
    authorization_server: BASE_URL,
    authorization_endpoint: `${BASE_URL}/authorize`,
    token_endpoint: `${BASE_URL}/token`
  });
});

// 2. Authorization Endpoint (Auto-approves and redirects back to Claude)
app.get('/authorize', (req, res) => {
  const { redirect_uri, state } = req.query;
  log(`OAUTH Authorization requested. Redirecting...`);
  // Redirect back to Claude with a dummy auth code and the required state
  res.redirect(`${redirect_uri}?code=trinity-auth-code-123&state=${state}`);
});

// 3. Token Endpoint (Trades the dummy code for your actual API token)
app.post('/token', (req, res) => {
  log(`OAUTH Token generated.`);
  res.json({
    access_token: API_TOKEN,
    token_type: 'Bearer',
    expires_in: 31536000 // 1 year expiration
  });
});

Block D: MCP Server & Tool Definitions

const mcpServer = new Server({
  name: "trinity-core-web",
  version: "2.0.0",
}, { capabilities: { tools: {} } });

mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "list_servers",
      description: "Get a list of all available Firefrost infrastructure servers.",
      inputSchema: { type: "object", properties: {} }
    },
    {
      name: "run_command",
      description: "Execute a shell command via SSH on a specified Firefrost server.",
      inputSchema: {
        type: "object",
        properties: {
          server: { type: "string", description: "The target server name (e.g., command-center)" },
          command: { type: "string", description: "The bash command to execute" }
        },
        required: ["server", "command"]
      }
    }
  ]
}));

Block E: Execution Logic & SSH Wrapper (SECURE VERSION)

IMPORTANT: Uses spawn instead of exec to prevent command injection attacks.

import { spawn } from 'child_process';

function executeSSH(server, command) {
  return new Promise((resolve) => {
    const target = SERVERS[server];
    
    log(`EXEC [${server}] ${command}`);
    
    // spawn with array args prevents shell injection
    const ssh = spawn('ssh', [
      '-o', 'ConnectTimeout=10',
      '-o', 'StrictHostKeyChecking=no',
      `${target.user}@${target.host}`,
      command
    ]);
    
    let stdout = '';
    let stderr = '';
    
    ssh.stdout.on('data', (data) => stdout += data.toString());
    ssh.stderr.on('data', (data) => stderr += data.toString());
    
    ssh.on('close', (code) => {
      const success = code === 0;
      log(`RESULT [${server}] success=${success}`);
      resolve({
        success,
        stdout: stdout.trim(),
        stderr: stderr.trim(),
        error: success ? null : `Exit code ${code}`
      });
    });
    
    ssh.on('error', (err) => {
      log(`ERROR [${server}] ${err.message}`);
      resolve({
        success: false,
        stdout: '',
        stderr: err.message,
        error: err.message
      });
    });
    
    // Timeout after 30 seconds
    setTimeout(() => {
      ssh.kill();
      resolve({
        success: false,
        stdout: stdout.trim(),
        stderr: 'Command timed out after 30 seconds',
        error: 'Timeout'
      });
    }, 30000);
  });
}

mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "list_servers") {
    const serverList = Object.keys(SERVERS).join(", ");
    return { content: [{ type: "text", text: `Available servers: ${serverList}` }] };
  }

  if (request.params.name === "run_command") {
    const { server, command } = request.params.arguments;
    const target = SERVERS[server];
    
    if (!target) {
      return { content: [{ type: "text", text: `Error: Unknown server ${server}` }], isError: true };
    }

    const result = await executeSSH(server, command);
    
    let output = result.stdout || '(no output)';
    if (result.stderr) output += `\nSTDERR: ${result.stderr}`;
    if (result.error && !result.success) output += `\nError: ${result.error}`;
    
    return { content: [{ type: "text", text: output }], isError: !result.success };
  }
});

Block F: SSE Transport & Server Start

let transport;

// Endpoint to establish the Server-Sent Events stream
app.get('/mcp', async (req, res) => {
  log(`Establishing new MCP SSE Connection from ${req.ip}`);
  transport = new SSEServerTransport('/mcp/messages', res);
  await mcpServer.connect(transport);
});

// Endpoint to receive incoming tool calls (Protected by Bearer Auth)
app.post('/mcp/messages', auth, async (req, res) => {
  if (!transport) {
    return res.status(503).json({ error: "MCP Transport not initialized. Call /mcp first." });
  }
  await transport.handlePostMessage(req, res);
});

app.listen(PORT, () => {
  log(`Trinity Core Web MCP started on port ${PORT}`);
});

3. Claude.ai Registration Steps

  1. Open Claude.ai in your browser
  2. Click profile → SettingsConnectors
  3. Scroll to bottom → Add custom connector
  4. Name: Trinity Core
  5. Connector URL: https://mcp.firefrostgaming.com/mcp
  6. Click Connect → OAuth auto-approves → Done!

4. Bonus Answers

Question Answer
CORS Required. The cors package in Block B allows https://claude.ai
SSE Drops SSH still completes on Pi. Claude times out but you can re-run or check /command.log
Security Add express-rate-limit to /token and /mcp/messages for hardening
Local Testing Run npx @modelcontextprotocol/inspector node index.js on Pi for testing UI

Conclusion

We have everything needed to upgrade Trinity Core from REST API to native Claude.ai web connector. This is a ~2 hour implementation task.

Implementation Checklist:

  • Backup current index.js
  • Update package.json (add "type": "module")
  • Install new dependencies
  • Replace index.js with new implementation (Blocks A-F)
  • Restart mcp-server service
  • Test with MCP Inspector
  • Register in Claude.ai Settings → Connectors
  • Test list_servers and run_command from Claude.ai

Fire + Frost + Foundation = Where Love Builds Legacy 💙🔥❄️