Skip to main content
Glama

Agent MCP

agent.ts48.9 kB
// Agent management tools for Agent-MCP Node.js // Ported from Python admin_tools.py import { z } from 'zod'; import { randomUUID } from 'crypto'; import { registerTool } from './registry.js'; import { getDbConnection } from '../db/connection.js'; import { getDatabaseStats } from '../db/schema.js'; import { VERSION, AGENT_COLORS, getProjectDir, MCP_DEBUG } from '../core/config.js'; import { verifyToken, getAgentId, generateToken as authGenerateToken, registerActiveAgent } from '../core/auth.js'; import { globalState as coreGlobalState } from '../core/globals.js'; import { isTmuxAvailable, createTmuxSession, sendCommandToSession, generateAgentSessionName, sendPromptAsync, sendPromptToSession, killTmuxSession, sessionExists } from '../utils/tmux.js'; // import { buildAgentPrompt, TemplateType } from '../utils/promptTemplates.js'; import path from 'path'; import { promises as fs } from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); // Types for agent management export interface Agent { token: string; agent_id: string; capabilities: string[]; status: 'created' | 'active' | 'terminated' | 'failed' | 'completed'; current_task?: string; working_directory: string; color: string; created_at: string; updated_at: string; terminated_at?: string; } // Global state tracking (similar to Python globals) const globalState = { activeAgents: new Map<string, Agent>(), agentWorkingDirs: new Map<string, string>(), agentTmuxSessions: new Map<string, string>(), // agent_id -> tmux_session_name agentColorIndex: 0, serverStartTime: new Date().toISOString() }; // Helper functions function generateAgentToken(): string { return randomUUID().replace(/-/g, ''); } function getNextAgentColor(): string { const color = AGENT_COLORS[globalState.agentColorIndex % AGENT_COLORS.length] || 'blue'; globalState.agentColorIndex++; return color; } function logAgentAction(agentId: string, action: string, details: any = {}) { const db = getDbConnection(); const timestamp = new Date().toISOString(); try { const stmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); stmt.run(agentId, action, timestamp, JSON.stringify(details)); if (MCP_DEBUG) { console.log(`📝 Logged action: ${action} for agent ${agentId}`); } } catch (error) { console.error(`Failed to log agent action: ${error}`); } } // Create Agent Tool registerTool( 'create_agent', 'Admin-only tool to create a new agent with specified capabilities and task assignments. Agents MUST have at least one task assigned. Workflow: 1) Create unassigned tasks first, 2) Create agent with those task IDs.', z.object({ agent_id: z.string().describe('Unique identifier for the agent'), capabilities: z.array(z.string()).optional().describe('List of agent capabilities'), task_ids: z.array(z.string()).optional().describe('List of task IDs to assign to the agent (required - must have at least one task)'), token: z.string().describe('Admin authentication token (required)') }), async (args, context) => { const { agent_id, capabilities = [], task_ids = [], token } = args; // Verify admin authentication - REQUIRED if (!token || !verifyToken(token, 'admin')) { return { content: [{ type: 'text' as const, text: '❌ Unauthorized: Admin privileges required to create agents' }], isError: true }; } // Basic validation if (!agent_id) { return { content: [{ type: 'text' as const, text: '❌ Error: agent_id is required. Please provide a unique identifier for the agent.' }], isError: true }; } // Agents must have at least one task assigned if (!task_ids || task_ids.length === 0) { return { content: [{ type: 'text' as const, text: '❌ Error: Agents must be created with at least one task assigned. Please provide task_ids.\n\n💡 **Workflow:**\n1. First create unassigned tasks using assign_task (without agent_token)\n2. Then create agent with those task IDs using create_agent' }], isError: true }; } // Check if agent already exists if (globalState.activeAgents.has(agent_id)) { return { content: [{ type: 'text' as const, text: `❌ Agent '${agent_id}' already exists in active memory` }], isError: true }; } const db = getDbConnection(); try { // Check database for existing agent const existingAgent = db.prepare('SELECT agent_id FROM agents WHERE agent_id = ?').get(agent_id); if (existingAgent) { return { content: [{ type: 'text' as const, text: `❌ Agent '${agent_id}' already exists in database` }], isError: true }; } // Validate tasks if provided for (const taskId of task_ids) { const task = db.prepare('SELECT task_id, assigned_to, status FROM tasks WHERE task_id = ?').get(taskId); if (!task) { return { content: [{ type: 'text' as const, text: `❌ Task '${taskId}' not found in database` }], isError: true }; } if ((task as any).assigned_to) { return { content: [{ type: 'text' as const, text: `❌ Task '${taskId}' is already assigned to agent '${(task as any).assigned_to}'` }], isError: true }; } } // Generate agent data const newToken = generateAgentToken(); const createdAt = new Date().toISOString(); const agentColor = getNextAgentColor(); const workingDir = getProjectDir(); const status = 'created'; // Begin transaction const transaction = db.transaction(() => { // Insert agent const insertAgent = db.prepare(` INSERT INTO agents ( token, agent_id, capabilities, created_at, status, working_directory, color, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `); insertAgent.run( newToken, agent_id, JSON.stringify(capabilities), createdAt, status, workingDir, agentColor, createdAt ); // Assign tasks const assignedTasks: string[] = []; for (const taskId of task_ids) { const updateTask = db.prepare(` UPDATE tasks SET assigned_to = ?, status = 'pending', updated_at = ? WHERE task_id = ? `); const result = updateTask.run(agent_id, createdAt, taskId); if (result.changes > 0) { assignedTasks.push(taskId); } } // Set current task to first assigned task if (assignedTasks.length > 0) { const updateCurrentTask = db.prepare(` UPDATE agents SET current_task = ? WHERE agent_id = ? `); updateCurrentTask.run(assignedTasks[0], agent_id); } // Log agent creation logAgentAction('admin', 'created_agent', { agent_id, color: agentColor, working_directory: workingDir, assigned_tasks: assignedTasks }); return assignedTasks; }); const assignedTasks = transaction(); // Update global state const agentData: Agent = { token: newToken, agent_id, capabilities, status: 'created', current_task: assignedTasks[0] || undefined, working_directory: workingDir, color: agentColor, created_at: createdAt, updated_at: createdAt }; globalState.activeAgents.set(agent_id, agentData); globalState.agentWorkingDirs.set(agent_id, workingDir); // Launch tmux session for the agent let launchStatus = ''; if (await isTmuxAvailable()) { try { // Create sanitized session name const tmuxSessionName = generateAgentSessionName(agent_id, token); // Create the tmux session (without immediate command, no environment variables) if (await createTmuxSession(tmuxSessionName, workingDir, undefined, undefined)) { // Track the tmux session in globals globalState.agentTmuxSessions.set(agent_id, tmuxSessionName); // Initial setup commands for visibility in tmux session const welcomeMessage = `echo '=== Agent ${agent_id} initialization starting ==='`; if (await sendCommandToSession(tmuxSessionName, welcomeMessage)) { if (MCP_DEBUG) { console.log(`✅ Sent welcome message to agent '${agent_id}'`); } } // Add setup delay to ensure commands execute properly await new Promise(resolve => setTimeout(resolve, 1000)); // Verify we're in the correct working directory const verifyCommand = `echo 'Working directory:' && pwd`; await sendCommandToSession(tmuxSessionName, verifyCommand); await new Promise(resolve => setTimeout(resolve, 1000)); // Get server port for MCP registration const serverPort = process.env.PORT || '3001'; const mcpServerUrl = `http://localhost:${serverPort}/mcp`; // Log MCP server info const mcpInfoCommand = `echo 'MCP Server URL: ${mcpServerUrl}'`; await sendCommandToSession(tmuxSessionName, mcpInfoCommand); await new Promise(resolve => setTimeout(resolve, 1000)); // Register MCP server connection const mcpAddCommand = `claude mcp add -t sse AgentMCP-Node ${mcpServerUrl}`; if (MCP_DEBUG) { console.log(`Registering MCP server for agent '${agent_id}': ${mcpAddCommand}`); } if (!await sendCommandToSession(tmuxSessionName, mcpAddCommand)) { console.error(`Failed to register MCP server for agent '${agent_id}'`); launchStatus = `❌ Failed to register MCP server for agent '${agent_id}'.`; } else { // Add delay to ensure MCP registration completes await new Promise(resolve => setTimeout(resolve, 1000)); // Verify MCP registration const verifyMcpCommand = 'claude mcp list'; if (MCP_DEBUG) { console.log(`Verifying MCP registration for agent '${agent_id}'`); } await sendCommandToSession(tmuxSessionName, verifyMcpCommand); await new Promise(resolve => setTimeout(resolve, 1000)); // Start Claude const startClaudeMessage = "echo '--- Starting Claude with MCP ---'"; await sendCommandToSession(tmuxSessionName, startClaudeMessage); await new Promise(resolve => setTimeout(resolve, 1000)); const claudeCommand = 'claude --dangerously-skip-permissions'; if (MCP_DEBUG) { console.log(`Starting Claude for agent '${agent_id}': ${claudeCommand}`); } if (!await sendCommandToSession(tmuxSessionName, claudeCommand)) { console.error(`Failed to start Claude for agent '${agent_id}'`); launchStatus = `❌ Failed to start Claude for agent '${agent_id}' after MCP registration.`; } else { launchStatus = `✅ tmux session '${tmuxSessionName}' created for agent '${agent_id}' with MCP registration and Claude.`; // Log completion message to tmux session const completionMessage = `echo '=== Agent ${agent_id} setup complete - Claude starting ==='`; await sendCommandToSession(tmuxSessionName, completionMessage); // Send the exact prompt from Python Agent-MCP (worker_with_rag template) console.log(`🔥 SCHEDULING TIMEOUT for agent '${agent_id}' with session '${tmuxSessionName}'`); const timeoutId = setTimeout(async () => { console.log(`🎯 TIMEOUT CALLBACK EXECUTING for agent '${agent_id}'`); const prompt = `You are ${agent_id} - Agent Token: ${newToken}. Start working on your assigned tasks.`; try { console.log(`🔧 About to send keys to session: ${tmuxSessionName}`); // Step 1: Type the message await execAsync(`tmux send-keys -t "${tmuxSessionName}" "${prompt}"`); console.log(`📝 Typed prompt to agent '${agent_id}'`); // Step 2: Wait 0.5 seconds await new Promise(resolve => setTimeout(resolve, 500)); // Step 3: Hit Enter await execAsync(`tmux send-keys -t "${tmuxSessionName}" Enter`); console.log(`✅ Sent prompt to agent '${agent_id}' with token: ${newToken}`); } catch (error) { console.error(`❌ Failed to send prompt to agent '${agent_id}':`, error); } console.log(`🏁 TIMEOUT CALLBACK COMPLETED for agent '${agent_id}'`); }, 4000); // Wait 4 seconds for Claude to fully start console.log(`⏰ Timeout scheduled with ID: ${timeoutId} for agent '${agent_id}'`); } } if (MCP_DEBUG) { console.log(`tmux session '${tmuxSessionName}' launched for agent '${agent_id}'`); } } else { launchStatus = `❌ Failed to create tmux session for agent '${agent_id}'.`; console.error(launchStatus); } } catch (error) { launchStatus = `❌ Failed to launch tmux session: ${error instanceof Error ? error.message : String(error)}`; console.error(launchStatus); } } else { console.warn('tmux is not available - agent session cannot be launched automatically'); launchStatus = '⚠️ tmux not available - manual agent setup required.'; } const response = [ `✅ **Agent '${agent_id}' Created Successfully**`, '', `**Details:**`, `- Token: ${newToken}`, `- Color: ${agentColor}`, `- Working Directory: ${workingDir}`, `- Status: ${status}`, `- Capabilities: ${capabilities.join(', ') || 'None'}`, '' ]; if (assignedTasks.length > 0) { response.push(`**Assigned Tasks:**`); assignedTasks.forEach(taskId => { response.push(`- ${taskId}`); }); response.push(`**Current Task:** ${assignedTasks[0]}`); response.push(''); } else { response.push(`**Tasks:** No tasks assigned`); response.push(''); } // Add tmux launch status if (launchStatus) { response.push(`**Launch Status:**`); response.push(launchStatus); response.push(''); } // Add tmux session info if available const sessionName = globalState.agentTmuxSessions.get(agent_id); if (sessionName) { response.push(`**Tmux Session:** ${sessionName}`); response.push(`**Connect Command:** \`tmux attach-session -t ${sessionName}\``); response.push(''); } response.push('🤖 Agent is ready for activation'); return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { console.error(`Error creating agent ${agent_id}:`, error); return { content: [{ type: 'text' as const, text: `❌ Error creating agent: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // View Status Tool registerTool( 'view_status', 'View the status of all agents, connections, and the MCP server', z.object({ token: z.string().optional().describe('Admin authentication token (optional - uses session context)') }), async (args, context) => { // For MCP usage, default to admin access for system status // In production, this would check session context for proper authentication try { const db = getDbConnection(); const stats = getDatabaseStats(); // Get all agents from database const agents = db.prepare('SELECT * FROM agents ORDER BY created_at DESC').all(); // Calculate uptime const startTime = new Date(globalState.serverStartTime); const uptime = Math.floor((Date.now() - startTime.getTime()) / 1000); const uptimeHours = Math.floor(uptime / 3600); const uptimeMinutes = Math.floor((uptime % 3600) / 60); const uptimeSeconds = uptime % 60; const statusInfo = { server: { version: VERSION, uptime: `${uptimeHours}h ${uptimeMinutes}m ${uptimeSeconds}s`, startTime: globalState.serverStartTime }, agents: { total: agents.length, active: agents.filter((a: any) => a.status === 'active').length, created: agents.filter((a: any) => a.status === 'created').length, terminated: agents.filter((a: any) => a.status === 'terminated').length }, database: stats, memory: { activeAgents: globalState.activeAgents.size, workingDirs: globalState.agentWorkingDirs.size } }; const response = [ `🏥 **Agent-MCP System Status**`, '', `**Server Info:**`, `- Version: ${statusInfo.server.version}`, `- Uptime: ${statusInfo.server.uptime}`, `- Started: ${statusInfo.server.startTime}`, '', `**Agent Summary:**`, `- Total Agents: ${statusInfo.agents.total}`, `- Active: ${statusInfo.agents.active}`, `- Created: ${statusInfo.agents.created}`, `- Terminated: ${statusInfo.agents.terminated}`, '', `**Database Tables:**` ]; Object.entries(statusInfo.database).forEach(([table, count]) => { response.push(`- ${table}: ${count >= 0 ? count : 'N/A'} records`); }); if (agents.length > 0) { response.push('', '**Agent Details:**'); agents.slice(0, 10).forEach((agent: any) => { response.push(`- **${agent.agent_id}** (${agent.status}) - ${agent.color}`); if (agent.current_task) { response.push(` Current Task: ${agent.current_task}`); } }); if (agents.length > 10) { response.push(`... and ${agents.length - 10} more agents`); } } response.push('', '✅ System operational'); return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { return { content: [{ type: 'text' as const, text: `❌ Error getting system status: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Terminate Agent Tool registerTool( 'terminate_agent', 'Admin-only tool to terminate an active agent with the given ID. Requires valid admin token.', z.object({ agent_id: z.string().describe('Unique identifier for the agent to terminate'), token: z.string().describe('Admin authentication token (required)') }), async (args, context) => { const { agent_id, token } = args; // Verify admin authentication - REQUIRED if (!token || !verifyToken(token, 'admin')) { return { content: [{ type: 'text' as const, text: '❌ Unauthorized: Admin privileges required to terminate agents' }], isError: true }; } if (!agent_id) { return { content: [{ type: 'text' as const, text: '❌ Error: agent_id is required' }], isError: true }; } const db = getDbConnection(); try { // Check if agent exists const agent = db.prepare('SELECT * FROM agents WHERE agent_id = ? AND status != ?').get(agent_id, 'terminated'); if (!agent) { return { content: [{ type: 'text' as const, text: `❌ Agent '${agent_id}' not found or already terminated` }], isError: true }; } const terminatedAt = new Date().toISOString(); // Begin transaction const transaction = db.transaction(() => { // Update agent status const updateAgent = db.prepare(` UPDATE agents SET status = ?, terminated_at = ?, updated_at = ?, current_task = NULL WHERE agent_id = ? AND status != ? `); updateAgent.run('terminated', terminatedAt, terminatedAt, agent_id, 'terminated'); // Unassign any assigned tasks const unassignTasks = db.prepare(` UPDATE tasks SET assigned_to = NULL, status = 'pending', updated_at = ? WHERE assigned_to = ? `); const unassignResult = unassignTasks.run(terminatedAt, agent_id); // Log termination logAgentAction('admin', 'terminated_agent', { agent_id, tasks_unassigned: unassignResult.changes }); return unassignResult.changes; }); const tasksUnassigned = transaction(); // Kill tmux session if it exists const tmuxSessionName = globalState.agentTmuxSessions.get(agent_id); let tmuxStatus = ''; if (tmuxSessionName) { try { if (await sessionExists(tmuxSessionName)) { const killed = await killTmuxSession(tmuxSessionName); if (killed) { tmuxStatus = `- Tmux session '${tmuxSessionName}' killed`; if (MCP_DEBUG) { console.log(`✅ Killed tmux session for agent '${agent_id}': ${tmuxSessionName}`); } } else { tmuxStatus = `- ⚠️ Failed to kill tmux session '${tmuxSessionName}'`; console.warn(`Failed to kill tmux session for agent '${agent_id}': ${tmuxSessionName}`); } } else { tmuxStatus = `- Tmux session '${tmuxSessionName}' already stopped`; } } catch (error) { tmuxStatus = `- ⚠️ Error killing tmux session: ${error instanceof Error ? error.message : String(error)}`; console.error(`Error killing tmux session for agent '${agent_id}':`, error); } // Remove from session tracking globalState.agentTmuxSessions.delete(agent_id); } else { tmuxStatus = '- No tmux session found'; } // Update global state globalState.activeAgents.delete(agent_id); globalState.agentWorkingDirs.delete(agent_id); const response = [ `✅ **Agent '${agent_id}' Terminated Successfully**`, '', `- Terminated at: ${terminatedAt}`, `- Tasks unassigned: ${tasksUnassigned}`, tmuxStatus, '- Removed from active memory', '', '🔴 Agent is fully stopped' ]; return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { console.error(`Error terminating agent ${agent_id}:`, error); return { content: [{ type: 'text' as const, text: `❌ Error terminating agent: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // List Agents Tool registerTool( 'list_agents', 'List all agents with filtering options', z.object({ status: z.enum(['created', 'active', 'terminated', 'failed', 'completed']).optional().describe('Filter by agent status'), limit: z.number().min(1).max(100).default(50).optional().describe('Maximum number of agents to return'), include_details: z.boolean().default(false).optional().describe('Include detailed agent information') }), async (args, context) => { const { status, limit = 50, include_details = false } = args; try { const db = getDbConnection(); let query = 'SELECT * FROM agents'; const params: any[] = []; if (status) { query += ' WHERE status = ?'; params.push(status); } query += ' ORDER BY created_at DESC LIMIT ?'; params.push(limit); const agents = db.prepare(query).all(...params); if (agents.length === 0) { return { content: [{ type: 'text' as const, text: `No agents found${status ? ` with status '${status}'` : ''}` }] }; } const response = [ `🤖 **Agent List** (${agents.length} found)`, '' ]; agents.forEach((agent: any) => { const caps = JSON.parse(agent.capabilities || '[]'); response.push(`**${agent.agent_id}** - ${agent.status} ${agent.color}`); if (include_details) { response.push(` Token: ${agent.token.substring(0, 8)}...`); response.push(` Created: ${agent.created_at}`); response.push(` Working Dir: ${agent.working_directory}`); if (agent.current_task) { response.push(` Current Task: ${agent.current_task}`); } if (caps.length > 0) { response.push(` Capabilities: ${caps.join(', ')}`); } if (agent.terminated_at) { response.push(` Terminated: ${agent.terminated_at}`); } } else { if (agent.current_task) { response.push(` Task: ${agent.current_task}`); } response.push(` Created: ${new Date(agent.created_at).toLocaleDateString()}`); } response.push(''); }); return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { return { content: [{ type: 'text' as const, text: `❌ Error listing agents: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); /** * Relaunch a terminated/completed/failed/cancelled agent in its existing tmux session */ registerTool( 'relaunch_agent', 'Relaunch an existing terminated/completed/failed/cancelled agent by reusing its tmux session. Sends /clear to reset and sends a new prompt.', z.object({ token: z.string().describe('Admin authentication token'), agent_id: z.string().describe('ID of the agent to relaunch'), generate_new_token: z.boolean().optional().default(false).describe('Generate a new token for the relaunched agent'), custom_prompt: z.string().optional().describe('Custom prompt to send instead of template prompt'), prompt_template: z.string().optional().default('worker_with_rag').describe('Prompt template to use') }), async (args, context) => { try { const { token, agent_id, generate_new_token = false, custom_prompt, prompt_template = 'worker_with_rag' } = args; // Admin authentication if (!verifyToken(token, 'admin')) { return { content: [{ type: 'text' as const, text: 'Unauthorized: Admin token required' }], isError: true }; } if (!agent_id) { return { content: [{ type: 'text' as const, text: 'Error: agent_id is required' }], isError: true }; } const db = getDbConnection(); // Check if agent exists and get current status const agent = db.prepare('SELECT * FROM agents WHERE agent_id = ?').get(agent_id) as any; if (!agent) { return { content: [{ type: 'text' as const, text: `Agent '${agent_id}' not found` }], isError: true }; } const currentStatus = agent.status; // Only allow relaunch for certain statuses const allowedStatuses = ['terminated', 'completed', 'failed', 'cancelled']; if (!allowedStatuses.includes(currentStatus)) { return { content: [{ type: 'text' as const, text: `Cannot relaunch agent with status '${currentStatus}'. Allowed statuses: ${allowedStatuses.join(', ')}` }], isError: true }; } // Check if tmux session still exists const sessionName = globalState.agentTmuxSessions.get(agent_id); if (!sessionName) { return { content: [{ type: 'text' as const, text: `Agent '${agent_id}' has no active tmux session to relaunch. Use create_agent instead.` }], isError: true }; } if (!(await sessionExists(sessionName))) { // Clean up the dead session reference globalState.agentTmuxSessions.delete(agent_id); return { content: [{ type: 'text' as const, text: `Tmux session '${sessionName}' for agent '${agent_id}' no longer exists. Use create_agent instead.` }], isError: true }; } // Send /clear command to reset the session const clearSuccess = await sendCommandToSession(sessionName, '/clear'); if (!clearSuccess) { return { content: [{ type: 'text' as const, text: `Failed to send /clear command to session '${sessionName}'` }], isError: true }; } // Generate new token if requested let agentToken = agent.token; if (generate_new_token) { agentToken = authGenerateToken(); db.prepare('UPDATE agents SET token = ? WHERE agent_id = ?').run(agentToken, agent_id); } // Update agent status to active const updatedAt = new Date().toISOString(); db.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE agent_id = ?') .run('active', updatedAt, agent_id); // Build and send new prompt let promptToSend: string; if (custom_prompt) { promptToSend = custom_prompt; } else { // Build agent prompt using template system promptToSend = `You are ${agent_id} - Agent Token: ${agentToken}. Start working on your assigned tasks.`; } // Send the new prompt to restart the agent with a delay setTimeout(async () => { try { await sendPromptToSession(sessionName, promptToSend); } catch (error) { console.error(`Failed to send restart prompt to ${agent_id}:`, error); } }, 2000); // Update in-memory state globalState.activeAgents.set(agentToken, { token: agentToken, agent_id, status: 'active', capabilities: JSON.parse(agent.capabilities || '[]'), working_directory: agent.working_directory, color: agent.color, created_at: agent.created_at, updated_at: updatedAt, current_task: agent.current_task }); // Log the action const actionDetails = { agent_id, session_name: sessionName, previous_status: currentStatus, new_token_generated: generate_new_token, prompt_template }; db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `).run('admin', 'relaunch_agent', new Date().toISOString(), JSON.stringify(actionDetails)); const responseParts = [ `✅ **Agent '${agent_id}' successfully relaunched**`, ``, `**Session:** ${sessionName}`, `**Status:** ${currentStatus} → active`, `**Action:** Session cleared and new prompt sent`, `` ]; if (generate_new_token) { responseParts.push(`**New Token:** ${agentToken}`); } else { responseParts.push(`**Token:** ${agentToken} (existing)`); } responseParts.push(``, `🚀 Agent is now active and should start working shortly`); return { content: [{ type: 'text' as const, text: responseParts.join('\n') }], isError: false }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('Error relaunching agent:', error); return { content: [{ type: 'text' as const, text: `❌ Error relaunching agent: ${errorMessage}` }], isError: true }; } } ); /** * Audit agent sessions and clean up inconsistencies */ registerTool( 'audit_agent_sessions', 'Intelligently audit agent tmux sessions. Analyzes activity patterns, task status, and recommends actions rather than auto-fixing.', z.object({ token: z.string().describe('Admin authentication token'), auto_cleanup_dead: z.boolean().optional().default(true).describe('Auto cleanup clearly dead sessions'), stale_threshold_minutes: z.number().optional().default(10).describe('Minutes of inactivity before considering stale'), kill_stale_sessions: z.boolean().optional().default(false).describe('Automatically kill stale sessions') }), async (args, context) => { try { const { token, auto_cleanup_dead = true, stale_threshold_minutes = 10, kill_stale_sessions = false } = args; // Admin authentication if (!verifyToken(token, 'admin')) { return { content: [{ type: 'text' as const, text: 'Unauthorized: Admin token required' }], isError: true }; } const db = getDbConnection(); const adminTokenSuffix = token.slice(-4).toLowerCase(); // Get all agents from database const agents = db.prepare('SELECT agent_id, status, token FROM agents').all() as any[]; // Get all tmux sessions with the admin token suffix const { stdout: tmuxOutput } = await execAsync('tmux list-sessions -F "#{session_name}"'); const allTmuxSessions = tmuxOutput.trim().split('\n').filter(line => line.length > 0); const agentSessions = allTmuxSessions.filter(session => session.includes(`-${adminTokenSuffix}`)); const auditResults = []; const cleanupActions = []; // Check each agent in database for (const agent of agents) { const expectedSessionName = `${agent.agent_id}-${adminTokenSuffix}`; const hasActiveTmuxSession = agentSessions.includes(expectedSessionName); const isInMemory = globalState.agentTmuxSessions.has(agent.agent_id); const memorySessionName = globalState.agentTmuxSessions.get(agent.agent_id); const result = { agent_id: agent.agent_id, status: agent.status, expected_session: expectedSessionName, has_tmux_session: hasActiveTmuxSession, in_memory: isInMemory, memory_session: memorySessionName, consistency: 'OK' }; // Check for inconsistencies if (agent.status === 'active' && !hasActiveTmuxSession) { result.consistency = 'INCONSISTENT: Active agent without tmux session'; if (auto_cleanup_dead) { // Update agent status to terminated db.prepare('UPDATE agents SET status = ?, updated_at = ? WHERE agent_id = ?') .run('terminated', new Date().toISOString(), agent.agent_id); cleanupActions.push(`Set ${agent.agent_id} to terminated (no tmux session)`); } } else if ((agent.status === 'terminated' || agent.status === 'failed') && hasActiveTmuxSession) { result.consistency = 'INCONSISTENT: Terminated agent with live tmux session'; if (auto_cleanup_dead) { // Add to memory so it can be relaunched globalState.agentTmuxSessions.set(agent.agent_id, expectedSessionName); cleanupActions.push(`Added ${agent.agent_id} to memory (found live tmux session)`); } } else if (isInMemory && !hasActiveTmuxSession) { result.consistency = 'INCONSISTENT: In memory but no tmux session'; if (auto_cleanup_dead) { globalState.agentTmuxSessions.delete(agent.agent_id); cleanupActions.push(`Removed ${agent.agent_id} from memory (no tmux session)`); } } else if (!isInMemory && hasActiveTmuxSession && agent.status !== 'terminated') { result.consistency = 'INCONSISTENT: Has tmux session but not in memory'; if (auto_cleanup_dead) { globalState.agentTmuxSessions.set(agent.agent_id, expectedSessionName); cleanupActions.push(`Added ${agent.agent_id} to memory (found tmux session)`); } } auditResults.push(result); } // Check for orphaned tmux sessions (sessions without corresponding agents) for (const sessionName of agentSessions) { const agentIdMatch = sessionName.match(/^(.+)-[a-f0-9]{4}$/); if (agentIdMatch) { const agentId = agentIdMatch[1]; const agentExists = agents.some(a => a.agent_id === agentId); if (!agentExists) { auditResults.push({ agent_id: agentId, status: 'ORPHANED', expected_session: sessionName, has_tmux_session: true, in_memory: false, memory_session: null, consistency: 'ORPHANED: Tmux session without database entry' }); if (auto_cleanup_dead) { cleanupActions.push(`Found orphaned session: ${sessionName} (no database entry)`); } } } } // Build report const reportParts = [ '🔍 **Agent Session Audit Report**', '', `**Summary:**`, `- Total agents in DB: ${agents.length}`, `- Active tmux sessions: ${agentSessions.length}`, `- Inconsistencies found: ${auditResults.filter(r => r.consistency !== 'OK').length}`, '' ]; if (auto_cleanup_dead && cleanupActions.length > 0) { reportParts.push( '🧹 **Cleanup Actions Performed:**', ...cleanupActions.map(action => `- ${action}`), '' ); } const inconsistentAgents = auditResults.filter(r => r.consistency !== 'OK'); if (inconsistentAgents.length > 0) { reportParts.push( '⚠️ **Inconsistencies:**', ...inconsistentAgents.map(agent => `- **${agent.agent_id}** (${agent.status}): ${agent.consistency}` ), '' ); } reportParts.push( '✅ **Consistent Agents:**', ...auditResults.filter(r => r.consistency === 'OK').map(agent => `- **${agent.agent_id}** (${agent.status}): Tmux=${agent.has_tmux_session}, Memory=${agent.in_memory}` ) ); return { content: [{ type: 'text' as const, text: reportParts.join('\n') }], isError: false }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('Error auditing agent sessions:', error); return { content: [{ type: 'text' as const, text: `❌ Error auditing sessions: ${errorMessage}` }], isError: true }; } } ); /** * Smart audit with activity analysis and intelligent recommendations */ registerTool( 'smart_audit_agents', 'Intelligently audit agents with activity analysis, task status checking, and smart recommendations for cleanup.', z.object({ token: z.string().describe('Admin authentication token'), stale_threshold_minutes: z.number().optional().default(10).describe('Minutes of inactivity before considering stale'), auto_kill_stale: z.boolean().optional().default(false).describe('Automatically kill sessions with no activity') }), async (args, context) => { try { const { token, stale_threshold_minutes = 10, auto_kill_stale = false } = args; // Admin authentication if (!verifyToken(token, 'admin')) { return { content: [{ type: 'text' as const, text: 'Unauthorized: Admin token required' }], isError: true }; } const db = getDbConnection(); const adminTokenSuffix = token.slice(-4).toLowerCase(); const staleThresholdMs = stale_threshold_minutes * 60 * 1000; const now = new Date(); // Get all agents with extended info const agents = db.prepare(` SELECT a.agent_id, a.status, a.token, a.created_at, a.terminated_at, a.current_task, t.title as task_title, t.status as task_status FROM agents a LEFT JOIN tasks t ON a.current_task = t.task_id `).all() as any[]; // Get tmux sessions const { stdout: tmuxOutput } = await execAsync('tmux list-sessions -F "#{session_name}"'); const allTmuxSessions = tmuxOutput.trim().split('\n').filter(line => line.length > 0); const agentSessions = allTmuxSessions.filter(session => session.includes(`-${adminTokenSuffix}`)); // Get recent activity for all agents (last hour) const recentActivity = db.prepare(` SELECT agent_id, action_type, timestamp FROM agent_actions WHERE timestamp > datetime('now', '-1 hour') ORDER BY timestamp DESC `).all() as any[]; const auditResults = []; const recommendations = []; const autoActions = []; // Analyze each agent for (const agent of agents) { const expectedSessionName = `${agent.agent_id}-${adminTokenSuffix}`; const hasActiveTmuxSession = agentSessions.includes(expectedSessionName); const isInMemory = globalState.agentTmuxSessions.has(agent.agent_id); // Get last activity for this agent const agentActivity = recentActivity.filter(a => a.agent_id === agent.agent_id); const lastActivity = agentActivity.length > 0 ? new Date(agentActivity[0].timestamp) : null; const minutesSinceActivity = lastActivity ? (now.getTime() - lastActivity.getTime()) / (1000 * 60) : Infinity; // Check if task is still relevant const taskRelevant = agent.task_status === 'in_progress' || agent.task_status === 'pending'; const result = { agent_id: agent.agent_id, status: agent.status, task_status: agent.task_status || 'none', task_title: agent.task_title || 'none', has_tmux_session: hasActiveTmuxSession, in_memory: isInMemory, last_activity: lastActivity ? lastActivity.toISOString() : 'never', minutes_inactive: lastActivity ? Math.round(minutesSinceActivity) : 'never', is_stale: minutesSinceActivity > stale_threshold_minutes, recommendation: 'OK' }; // Generate intelligent recommendations if (hasActiveTmuxSession && agent.status === 'terminated') { if (minutesSinceActivity > stale_threshold_minutes) { result.recommendation = 'KILL SESSION: Terminated agent with stale session'; recommendations.push(`🗑️ Kill ${agent.agent_id} session (terminated ${Math.round(minutesSinceActivity)}min ago)`); if (auto_kill_stale) { try { await killTmuxSession(expectedSessionName); globalState.agentTmuxSessions.delete(agent.agent_id); autoActions.push(`Killed stale session: ${expectedSessionName}`); } catch (error) { autoActions.push(`Failed to kill session ${expectedSessionName}: ${error}`); } } } else if (taskRelevant) { result.recommendation = 'CONSIDER RELAUNCH: Recent termination with active task'; recommendations.push(`🔄 Consider relaunching ${agent.agent_id} (task ${agent.task_status}, terminated recently)`); } else { result.recommendation = 'KILL SESSION: Terminated with completed/irrelevant task'; recommendations.push(`🗑️ Kill ${agent.agent_id} session (task ${agent.task_status})`); } } else if (hasActiveTmuxSession && agent.status === 'created') { if (minutesSinceActivity > stale_threshold_minutes) { result.recommendation = 'KILL SESSION: Created but never activated'; recommendations.push(`🗑️ Kill ${agent.agent_id} session (created but inactive ${Math.round(minutesSinceActivity)}min)`); } else { result.recommendation = 'MONITOR: Recently created, may be starting up'; recommendations.push(`👀 Monitor ${agent.agent_id} (recently created)`); } } else if (hasActiveTmuxSession && agent.status === 'active') { if (minutesSinceActivity > stale_threshold_minutes) { result.recommendation = 'INVESTIGATE: Active agent but no recent activity'; recommendations.push(`🔍 Check ${agent.agent_id} (active but silent ${Math.round(minutesSinceActivity)}min)`); } else { result.recommendation = 'HEALTHY: Active with recent activity'; } } else if (!hasActiveTmuxSession && agent.status === 'active') { result.recommendation = 'UPDATE STATUS: Active agent without session'; recommendations.push(`📝 Set ${agent.agent_id} status to terminated (no session)`); } // Auto-add to memory if session exists but not tracked if (hasActiveTmuxSession && !isInMemory && agent.status !== 'terminated') { globalState.agentTmuxSessions.set(agent.agent_id, expectedSessionName); autoActions.push(`Added ${agent.agent_id} to memory tracking`); } auditResults.push(result); } // Build smart report const totalSessions = agentSessions.length; const staleSessions = auditResults.filter(r => r.is_stale && r.has_tmux_session).length; const activeSessions = auditResults.filter(r => r.has_tmux_session && r.status === 'active').length; const reportParts = [ '🧠 **Smart Agent Audit Report**', '', `**Activity Summary:**`, `- Total agents: ${agents.length}`, `- Live tmux sessions: ${totalSessions}`, `- Active agents: ${activeSessions}`, `- Stale sessions (>${stale_threshold_minutes}min): ${staleSessions}`, '' ]; if (autoActions.length > 0) { reportParts.push( '⚡ **Auto Actions Performed:**', ...autoActions.map(action => `- ${action}`), '' ); } if (recommendations.length > 0) { reportParts.push( '💡 **Recommendations:**', ...recommendations, '' ); } // Show detailed status for agents with sessions const agentsWithSessions = auditResults.filter(r => r.has_tmux_session); if (agentsWithSessions.length > 0) { reportParts.push( '📊 **Agents with Sessions:**', ...agentsWithSessions.map(agent => `- **${agent.agent_id}** (${agent.status}): Task=${agent.task_status}, Last=${agent.minutes_inactive === 'never' ? 'never' : agent.minutes_inactive + 'min ago'}, ${agent.recommendation}` ), '' ); } return { content: [{ type: 'text' as const, text: reportParts.join('\n') }], isError: false }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('Error in smart agent audit:', error); return { content: [{ type: 'text' as const, text: `❌ Error in smart audit: ${errorMessage}` }], isError: true }; } } ); console.log('✅ Agent management tools registered successfully');

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/rinadelph/Agent-MCP'

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