This commit is contained in:
2026-03-17 16:22:33 +03:00
parent df0b60c97a
commit 0fda025de3
10 changed files with 2342 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { executeCommand } from '../ssh-client.js';
import { SAFE_COMMAND_PREFIXES } from '../constants.js';
export function registerSystemTools(server: McpServer): void {
// ── rpi_system_status ────────────────────────────────────────────────
server.registerTool(
'rpi_system_status',
{
title: 'Raspberry Pi System Status',
description: `Get system resource usage on the Raspberry Pi: CPU load, memory, disk, uptime, and temperature.
Returns a formatted overview of the system health.`,
inputSchema: {},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async () => {
try {
const commands = [
{ label: 'Hostname', cmd: 'hostname' },
{ label: 'Uptime', cmd: 'uptime' },
{ label: 'CPU Temperature', cmd: 'cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo "N/A"' },
{ label: 'Memory', cmd: 'free -h | head -3' },
{ label: 'Disk', cmd: 'df -h / | tail -1' },
{ label: 'CPU Load', cmd: 'top -bn1 | head -5' },
];
const sections: string[] = ['# 🖥️ Raspberry Pi System Status\n'];
for (const { label, cmd } of commands) {
const result = await executeCommand(cmd);
let value = result.stdout || 'N/A';
// Convert CPU temp from millidegrees
if (label === 'CPU Temperature' && value !== 'N/A') {
const temp = parseInt(value);
if (!isNaN(temp)) {
value = `${(temp / 1000).toFixed(1)}°C`;
}
}
sections.push(`## ${label}\n\`\`\`\n${value}\n\`\`\`\n`);
}
return { content: [{ type: 'text', text: sections.join('\n') }] };
} catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
);
// ── rpi_docker_networks ──────────────────────────────────────────────
server.registerTool(
'rpi_docker_networks',
{
title: 'List Docker Networks',
description: `List all Docker networks on the Raspberry Pi with driver and scope info.`,
inputSchema: {},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
async () => {
try {
const result = await executeCommand('docker network ls --format "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.Scope}}"');
if (result.code !== 0) {
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }] };
}
return { content: [{ type: 'text', text: `## Docker Networks\n\n\`\`\`\n${result.stdout}\n\`\`\`` }] };
} catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
);
// ── rpi_run_command ──────────────────────────────────────────────────
server.registerTool(
'rpi_run_command',
{
title: 'Run Safe Command on RPi',
description: `Execute a read-only command on the Raspberry Pi via SSH.
Only whitelisted safe commands are allowed (docker ps, docker logs, df, free, uptime, cat, ls, tail, head, grep, etc.).
Args:
- command (string): The command to execute (must start with a safe prefix)`,
inputSchema: {
command: z.string().min(1).max(500).describe('Safe read-only command to execute'),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
async ({ command }) => {
try {
const trimmedCmd = command.trim();
const isSafe = SAFE_COMMAND_PREFIXES.some(prefix => trimmedCmd.startsWith(prefix));
if (!isSafe) {
return {
content: [{
type: 'text',
text: `❌ Command rejected: '${trimmedCmd}' is not in the safe command list.\n\nAllowed prefixes:\n${SAFE_COMMAND_PREFIXES.map(p => ` - ${p}`).join('\n')}`
}]
};
}
const result = await executeCommand(trimmedCmd);
const output = result.stdout || result.stderr || '(no output)';
const status = result.code === 0 ? '✅' : `⚠️ (exit code: ${result.code})`;
return { content: [{ type: 'text', text: `${status} \`${trimmedCmd}\`\n\n\`\`\`\n${output}\n\`\`\`` }] };
} catch (error) {
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
}
}
);
}