Skip to main content
Glama
system.ts14.4 kB
import { z } from 'zod'; import { exec } from 'child_process'; import { promisify } from 'util'; import os from 'os'; import { logAudit } from '../audit.js'; const execAsync = promisify(exec); const platform = os.platform(); // Schema definitions export const GetSystemInfoSchema = {}; export const ListProcessesSchema = { filter: z.string().optional().describe('Filter processes by name (partial match)'), sortBy: z.enum(['cpu', 'memory', 'name', 'pid']).optional().describe('Sort by field'), limit: z.number().optional().describe('Limit number of results'), }; export const KillProcessSchema = { pid: z.number().optional().describe('Process ID to kill'), name: z.string().optional().describe('Process name to kill (all matching)'), force: z.boolean().optional().describe('Force kill'), }; export const GetEnvironmentSchema = { variable: z.string().optional().describe('Specific variable to get (returns all if not specified)'), }; export const SetEnvironmentSchema = { variable: z.string().describe('Variable name'), value: z.string().describe('Variable value'), persistent: z.boolean().optional().describe('Make persistent (requires admin on Windows)'), }; export const GetNetworkInfoSchema = {}; export const WaitSchema = { ms: z.number().describe('Milliseconds to wait'), }; export const NotifySchema = { title: z.string().describe('Notification title'), message: z.string().describe('Notification message'), icon: z.string().optional().describe('Path to icon'), }; // Tool handlers export async function handleGetSystemInfo() { try { const cpus = os.cpus(); const totalMem = os.totalmem(); const freeMem = os.freemem(); const info = { platform: os.platform(), arch: os.arch(), release: os.release(), hostname: os.hostname(), uptime: os.uptime(), uptimeHuman: formatUptime(os.uptime()), cpu: { model: cpus[0]?.model, cores: cpus.length, speed: cpus[0]?.speed, }, memory: { total: totalMem, totalHuman: formatBytes(totalMem), free: freeMem, freeHuman: formatBytes(freeMem), used: totalMem - freeMem, usedHuman: formatBytes(totalMem - freeMem), usagePercent: ((totalMem - freeMem) / totalMem * 100).toFixed(1) + '%', }, user: { username: os.userInfo().username, homedir: os.homedir(), shell: os.userInfo().shell, }, env: { PATH: process.env.PATH?.split(path.delimiter).slice(0, 5).join(path.delimiter) + '...', NODE_ENV: process.env.NODE_ENV, } }; await logAudit('get_system_info', {}, 'success'); return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }], }; } catch (error: any) { await logAudit('get_system_info', {}, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleListProcesses(args: { filter?: string; sortBy?: string; limit?: number }) { try { let processes: any[] = []; if (platform === 'win32') { const { stdout } = await execAsync('powershell -Command "Get-Process | Select-Object Id, ProcessName, CPU, WorkingSet64 | ConvertTo-Json"', { timeout: 10000 }); const parsed = JSON.parse(stdout); processes = (Array.isArray(parsed) ? parsed : [parsed]).map((p: any) => ({ pid: p.Id, name: p.ProcessName, cpu: p.CPU || 0, memory: p.WorkingSet64 || 0, memoryHuman: formatBytes(p.WorkingSet64 || 0), })); } else if (platform === 'darwin') { const { stdout } = await execAsync('ps aux', { timeout: 10000 }); const lines = stdout.trim().split('\n').slice(1); processes = lines.map(line => { const parts = line.split(/\s+/); return { pid: parseInt(parts[1]), name: parts[10] || parts.slice(10).join(' '), cpu: parseFloat(parts[2]), memory: parseFloat(parts[3]), }; }); } else { const { stdout } = await execAsync('ps aux --sort=-pcpu', { timeout: 10000 }); const lines = stdout.trim().split('\n').slice(1); processes = lines.map(line => { const parts = line.split(/\s+/); return { pid: parseInt(parts[1]), name: parts[10] || parts.slice(10).join(' '), cpu: parseFloat(parts[2]), memory: parseFloat(parts[3]), }; }); } // Filter if (args.filter) { const filter = args.filter.toLowerCase(); processes = processes.filter(p => p.name.toLowerCase().includes(filter)); } // Sort if (args.sortBy) { processes.sort((a, b) => { if (args.sortBy === 'cpu') return b.cpu - a.cpu; if (args.sortBy === 'memory') return b.memory - a.memory; if (args.sortBy === 'name') return a.name.localeCompare(b.name); if (args.sortBy === 'pid') return a.pid - b.pid; return 0; }); } // Limit const limit = args.limit || 50; processes = processes.slice(0, limit); await logAudit('list_processes', args, { count: processes.length }); return { content: [{ type: 'text', text: JSON.stringify({ count: processes.length, processes }, null, 2) }], }; } catch (error: any) { await logAudit('list_processes', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleKillProcess(args: { pid?: number; name?: string; force?: boolean }) { try { let killed = 0; if (args.pid) { if (platform === 'win32') { await execAsync(`taskkill ${args.force ? '/F ' : ''}/PID ${args.pid}`, { timeout: 5000 }); } else { await execAsync(`kill ${args.force ? '-9 ' : ''}${args.pid}`, { timeout: 5000 }); } killed = 1; } else if (args.name) { if (platform === 'win32') { const { stdout } = await execAsync(`taskkill ${args.force ? '/F ' : ''}/IM "${args.name}*"`, { timeout: 5000 }); const match = stdout.match(/(\d+) process/); killed = match ? parseInt(match[1]) : 1; } else { await execAsync(`pkill ${args.force ? '-9 ' : ''}"${args.name}"`, { timeout: 5000 }); killed = 1; } } await logAudit('kill_process', args, { killed }); return { content: [{ type: 'text', text: JSON.stringify({ killed, pid: args.pid, name: args.name }, null, 2) }], }; } catch (error: any) { await logAudit('kill_process', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleGetEnvironment(args: { variable?: string }) { try { let env: Record<string, string | undefined>; if (args.variable) { env = { [args.variable]: process.env[args.variable] }; } else { // Return a subset of common variables const commonVars = ['PATH', 'HOME', 'USER', 'SHELL', 'TERM', 'LANG', 'NODE_ENV', 'TEMP', 'TMP']; env = {}; for (const v of commonVars) { if (process.env[v]) { env[v] = process.env[v]; } } } await logAudit('get_environment', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify(env, null, 2) }], }; } catch (error: any) { await logAudit('get_environment', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleSetEnvironment(args: { variable: string; value: string; persistent?: boolean }) { try { // Set for current process process.env[args.variable] = args.value; if (args.persistent) { if (platform === 'win32') { await execAsync(`setx ${args.variable} "${args.value}"`, { timeout: 5000 }); } else { // On Unix, we can't really persist without modifying shell config files // Just note that this would need to be added to .bashrc/.zshrc etc. } } await logAudit('set_environment', { variable: args.variable, persistent: args.persistent }, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ set: true, variable: args.variable, persistent: args.persistent || false }, null, 2) }], }; } catch (error: any) { await logAudit('set_environment', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleGetNetworkInfo() { try { const interfaces = os.networkInterfaces(); const networks: any[] = []; for (const [name, addrs] of Object.entries(interfaces)) { if (!addrs) continue; for (const addr of addrs) { if (!addr.internal) { networks.push({ interface: name, family: addr.family, address: addr.address, netmask: addr.netmask, mac: addr.mac, }); } } } await logAudit('get_network_info', {}, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ hostname: os.hostname(), interfaces: networks }, null, 2) }], }; } catch (error: any) { await logAudit('get_network_info', {}, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleWait(args: { ms: number }) { try { await new Promise(resolve => setTimeout(resolve, args.ms)); await logAudit('wait', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ waited: true, ms: args.ms }, null, 2) }], }; } catch (error: any) { await logAudit('wait', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } export async function handleNotify(args: { title: string; message: string; icon?: string }) { try { if (platform === 'win32') { const script = ` [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null $template = @" <toast> <visual> <binding template="ToastText02"> <text id="1">${args.title}</text> <text id="2">${args.message}</text> </binding> </visual> </toast> "@ $xml = New-Object Windows.Data.Xml.Dom.XmlDocument $xml.LoadXml($template) $toast = [Windows.UI.Notifications.ToastNotification]::new($xml) [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("MCP Server").Show($toast) `; await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { timeout: 5000 }); } else if (platform === 'darwin') { await execAsync(`osascript -e 'display notification "${args.message}" with title "${args.title}"'`, { timeout: 5000 }); } else { await execAsync(`notify-send "${args.title}" "${args.message}"`, { timeout: 5000 }); } await logAudit('notify', args, 'success'); return { content: [{ type: 'text', text: JSON.stringify({ notified: true, title: args.title }, null, 2) }], }; } catch (error: any) { await logAudit('notify', args, null, error.message); return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, }; } } // Utility functions import path from 'path'; function formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function formatUptime(seconds: number): string { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); const parts = []; if (days > 0) parts.push(`${days}d`); if (hours > 0) parts.push(`${hours}h`); if (minutes > 0) parts.push(`${minutes}m`); return parts.join(' ') || '< 1m'; }

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/Mnehmos/mnehmos.ooda.mcp'

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