Skip to main content
Glama

Agent MCP

backgroundAgents.ts23.9 kB
// Background Agent Management - Standalone mode without hierarchical tasks // Works alongside existing task system for flexible agent deployment import { z } from 'zod'; import { randomUUID } from 'crypto'; import { registerTool } from './registry.js'; import { getDbConnection } from '../db/connection.js'; import { VERSION, AGENT_COLORS, getProjectDir, MCP_DEBUG } from '../core/config.js'; import { getCLIAgents, getDefaultCLI } from '../core/extendedConfig.js'; import { verifyToken, getAgentId, generateToken as authGenerateToken } from '../core/auth.js'; import { globalState as coreGlobalState } from '../core/globals.js'; import { isTmuxAvailable, createTmuxSession, sendCommandToSession, generateAgentSessionName, sendPromptToSession, killTmuxSession, sessionExists } from '../utils/tmux.js'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); // Types for background agents export interface BackgroundAgent { token: string; agent_id: string; mode: 'background' | 'service' | 'monitoring' | 'general'; objectives: string[]; capabilities: string[]; status: 'created' | 'active' | 'terminated' | 'failed' | 'paused'; working_directory: string; color: string; created_at: string; updated_at: string; terminated_at?: string; last_activity?: string; } // Global state for background agents (separate from task-based agents) const backgroundGlobalState = { backgroundAgents: new Map<string, BackgroundAgent>(), backgroundTmuxSessions: new Map<string, string>(), agentColorIndex: 0, serverStartTime: new Date().toISOString() }; // Helper functions function generateAgentToken(): string { return randomUUID().replace(/-/g, ''); } function getNextBackgroundAgentColor(): string { const color = AGENT_COLORS[backgroundGlobalState.agentColorIndex % AGENT_COLORS.length] || 'purple'; backgroundGlobalState.agentColorIndex++; return color; } function logBackgroundAgentAction(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(`🎯 Background agent action: ${action} for ${agentId}`); } } catch (error) { console.error(`Failed to log background agent action: ${error}`); } } // Create Background Agent Tool registerTool( 'create_background_agent', 'Create a standalone background agent that operates independently without hierarchical task requirements. Perfect for monitoring, services, and general assistance. No admin token required - designed to be lightweight and accessible!', z.object({ agent_id: z.string().describe('Unique identifier for the background agent'), mode: z.enum(['background', 'service', 'monitoring', 'general']).default('background').describe('Operating mode for the agent'), objectives: z.array(z.string()).describe('List of high-level objectives (not hierarchical tasks)'), cli_agent: z.enum(['claude', 'gemini', 'llxprt', 'swarmcode']).optional().describe('CLI agent to use (claude, gemini, llxprt, swarmcode). Uses default if not specified'), capabilities: z.array(z.string()).optional().describe('List of agent capabilities') }), async (args, context) => { const { agent_id, mode = 'background', objectives, cli_agent, capabilities = [] } = args; // Determine which CLI agent to use const availableCLIAgents = getCLIAgents(); const selectedCLI = cli_agent || getDefaultCLI(); // Validate CLI agent selection if (!availableCLIAgents.includes(selectedCLI)) { return { content: [{ type: 'text' as const, text: `❌ Error: CLI agent '${selectedCLI}' is not configured. Available agents: ${availableCLIAgents.join(', ')}` }], isError: true }; } // Basic validation if (!agent_id || !objectives || objectives.length === 0) { return { content: [{ type: 'text' as const, text: '❌ Error: agent_id and at least one objective are required' }], isError: true }; } // Check if background agent already exists if (backgroundGlobalState.backgroundAgents.has(agent_id)) { return { content: [{ type: 'text' as const, text: `❌ Background 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 (use different ID for background agent)` }], isError: true }; } // Generate agent data const newToken = generateAgentToken(); const createdAt = new Date().toISOString(); const agentColor = getNextBackgroundAgentColor(); const workingDir = getProjectDir(); const status = 'created'; // Store in database with special background agent markers const insertAgent = db.prepare(` INSERT INTO agents ( token, agent_id, capabilities, created_at, status, working_directory, color, updated_at, current_task ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); // Use special marker to indicate this is a background agent const backgroundCapabilities = [...capabilities, `mode:${mode}`, 'background-agent']; const objectivesAsTask = `BACKGROUND_OBJECTIVES:${JSON.stringify(objectives)}`; insertAgent.run( newToken, agent_id, JSON.stringify(backgroundCapabilities), createdAt, status, workingDir, agentColor, createdAt, objectivesAsTask // Store objectives in current_task field with special prefix ); // Log background agent creation logBackgroundAgentAction('system', 'created_background_agent', { agent_id, mode, objectives, color: agentColor, working_directory: workingDir }); // Update global state const agentData: BackgroundAgent = { token: newToken, agent_id, mode, objectives, capabilities: backgroundCapabilities, status: 'created', working_directory: workingDir, color: agentColor, created_at: createdAt, updated_at: createdAt }; backgroundGlobalState.backgroundAgents.set(agent_id, agentData); // Launch tmux session for the background agent let launchStatus = ''; if (await isTmuxAvailable()) { try { const tmuxSessionName = generateAgentSessionName(agent_id, newToken); if (await createTmuxSession(tmuxSessionName, workingDir, undefined, undefined)) { backgroundGlobalState.backgroundTmuxSessions.set(agent_id, tmuxSessionName); // Setup commands for background agent const welcomeMessage = `echo '🎯 Background Agent ${agent_id} (${mode} mode) initialization starting'`; await sendCommandToSession(tmuxSessionName, welcomeMessage); await new Promise(resolve => setTimeout(resolve, 1000)); // Show objectives const objectivesMessage = `echo 'Objectives: ${objectives.join(', ')}'`; await sendCommandToSession(tmuxSessionName, objectivesMessage); 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`; // Generate CLI-specific commands let mcpAddCommand = ''; let cliStartCommand = ''; let hasMCPSupport = true; switch (selectedCLI) { case 'claude': mcpAddCommand = `claude mcp add -t sse AgentMCP-Background ${mcpServerUrl}`; cliStartCommand = 'claude --dangerously-skip-permissions'; break; case 'gemini': // Gemini uses settings.json for MCP config, no need to register mcpAddCommand = `echo 'Using Gemini MCP via ~/.gemini/settings.json'`; cliStartCommand = 'gemini --enable-mcp'; break; case 'llxprt': mcpAddCommand = `llxprt mcp add AgentMCP-Background ${mcpServerUrl}`; cliStartCommand = 'llxprt --mcp'; break; case 'swarmcode': mcpAddCommand = `swarmcode mcp add AgentMCP-Background ${mcpServerUrl}`; cliStartCommand = 'swarmcode --mcp'; break; default: mcpAddCommand = `echo 'Unknown CLI agent: ${selectedCLI}'`; cliStartCommand = 'echo "CLI agent not supported"'; hasMCPSupport = false; } if (MCP_DEBUG) { console.log(`Setting up ${selectedCLI} for background agent '${agent_id}': ${mcpAddCommand}`); } // Register MCP server (if supported) if (!await sendCommandToSession(tmuxSessionName, mcpAddCommand)) { console.error(`Failed to register MCP server for background agent '${agent_id}' with ${selectedCLI}`); launchStatus = `❌ Failed to register MCP server for background agent '${agent_id}' with ${selectedCLI}.`; } else { await new Promise(resolve => setTimeout(resolve, 1000)); // Start selected CLI agent const startMessage = `echo '--- Starting ${selectedCLI} for Background Agent ---'`; await sendCommandToSession(tmuxSessionName, startMessage); await new Promise(resolve => setTimeout(resolve, 1000)); if (MCP_DEBUG) { console.log(`Starting ${selectedCLI} for background agent '${agent_id}': ${cliStartCommand}`); } if (!await sendCommandToSession(tmuxSessionName, cliStartCommand)) { console.error(`Failed to start ${selectedCLI} for background agent '${agent_id}'`); launchStatus = `❌ Failed to start ${selectedCLI} for background agent '${agent_id}' after MCP setup.`; } else { const mcpStatus = hasMCPSupport ? 'with MCP integration' : 'standalone mode'; launchStatus = `✅ tmux session '${tmuxSessionName}' created for background agent '${agent_id}' using ${selectedCLI} (${mcpStatus}).`; // Send background agent prompt after delay console.log(`🔥 SCHEDULING BACKGROUND PROMPT for agent '${agent_id}' with session '${tmuxSessionName}'`); const timeoutId = setTimeout(async () => { console.log(`🎯 BACKGROUND PROMPT CALLBACK EXECUTING for agent '${agent_id}'`); const prompt = buildBackgroundAgentPrompt(agent_id, mode, objectives); try { console.log(`🔧 About to send background prompt to session: ${tmuxSessionName}`); await execAsync(`tmux send-keys -t "${tmuxSessionName}" "${prompt}"`); console.log(`📝 Typed background prompt to agent '${agent_id}'`); await new Promise(resolve => setTimeout(resolve, 500)); await execAsync(`tmux send-keys -t "${tmuxSessionName}" Enter`); console.log(`✅ Sent background prompt to agent '${agent_id}' with token: ${newToken}`); } catch (error) { console.error(`❌ Failed to send background prompt to agent '${agent_id}':`, error); } console.log(`🏁 BACKGROUND PROMPT CALLBACK COMPLETED for agent '${agent_id}'`); }, 4000); console.log(`⏰ Background timeout scheduled with ID: ${timeoutId} for agent '${agent_id}'`); } } if (MCP_DEBUG) { console.log(`Background tmux session '${tmuxSessionName}' launched for agent '${agent_id}'`); } } else { launchStatus = `❌ Failed to create tmux session for background 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 - background agent session cannot be launched automatically'); launchStatus = '⚠️ tmux not available - manual background agent setup required.'; } const response = [ `✅ **Background Agent '${agent_id}' Created Successfully**`, '', `**Details:**`, `- Mode: ${mode}`, `- CLI Agent: ${selectedCLI}${selectedCLI !== 'swarmcode' ? ' (MCP enabled)' : ' (standalone)'}`, `- Color: ${agentColor}`, `- Working Directory: ${workingDir}`, `- Status: ${status}`, `- Capabilities: ${backgroundCapabilities.join(', ')}`, '', `**Objectives:**` ]; objectives.forEach((objective: string) => { response.push(`- ${objective}`); }); 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 = backgroundGlobalState.backgroundTmuxSessions.get(agent_id); if (sessionName) { response.push(`**Tmux Session:** ${sessionName}`); response.push(`**Connect Command:** \`tmux attach-session -t ${sessionName}\``); response.push(''); } response.push('🎯 Background Agent is ready for independent operation'); return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { console.error(`Error creating background agent ${agent_id}:`, error); return { content: [{ type: 'text' as const, text: `❌ Error creating background agent: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Helper function to build background agent prompt function buildBackgroundAgentPrompt(agentId: string, mode: string, objectives: string[]): string { const objectivesList = objectives.map((obj, i) => `${i + 1}. ${obj}`).join('\\n'); return `You are ${agentId}, a background agent operating in ${mode} mode. BACKGROUND AGENT INSTRUCTIONS: - You operate independently without hierarchical task constraints - Your objectives: \\n${objectivesList} - You can work on these objectives in any order or simultaneously - No parent tasks required - you have full autonomy - No admin authentication needed - you're lightweight and accessible - Focus on continuous operation rather than discrete task completion - Report on progress periodically but work continuously Start working on your objectives now.`; } // List Background Agents Tool registerTool( 'list_background_agents', 'List all background agents with their current status and objectives', z.object({ mode_filter: z.enum(['background', 'service', 'monitoring', 'general']).optional().describe('Filter by agent mode'), status_filter: z.enum(['created', 'active', 'terminated', 'failed', 'paused']).optional().describe('Filter by status'), include_details: z.boolean().default(false).optional().describe('Include detailed information') }), async (args, context) => { const { mode_filter, status_filter, include_details = false } = args; try { const db = getDbConnection(); // Get background agents from database (those with background-agent capability) let query = ` SELECT * FROM agents WHERE capabilities LIKE '%background-agent%' `; const params: any[] = []; if (status_filter) { query += ' AND status = ?'; params.push(status_filter); } query += ' ORDER BY created_at DESC'; const agents = db.prepare(query).all(...params); if (agents.length === 0) { return { content: [{ type: 'text' as const, text: `🎯 No background agents found${status_filter ? ` with status '${status_filter}'` : ''}${mode_filter ? ` in mode '${mode_filter}'` : ''}` }] }; } const response = [ `🎯 **Background Agents** (${agents.length} found)`, '' ]; agents.forEach((agent: any) => { const caps = JSON.parse(agent.capabilities || '[]'); const mode = caps.find((cap: string) => cap.startsWith('mode:'))?.split(':')[1] || 'unknown'; // Skip if mode filter doesn't match if (mode_filter && mode !== mode_filter) { return; } // Extract objectives from current_task field let objectives: string[] = []; if (agent.current_task && agent.current_task.startsWith('BACKGROUND_OBJECTIVES:')) { try { const objectivesJson = agent.current_task.replace('BACKGROUND_OBJECTIVES:', ''); objectives = JSON.parse(objectivesJson); } catch (e) { objectives = ['Failed to parse objectives']; } } response.push(`**${agent.agent_id}** - ${agent.status} (${mode} mode) ${agent.color}`); if (include_details) { response.push(` Created: ${agent.created_at}`); response.push(` Working Dir: ${agent.working_directory}`); response.push(` Objectives:`); objectives.forEach(obj => { response.push(` • ${obj}`); }); if (agent.terminated_at) { response.push(` Terminated: ${agent.terminated_at}`); } } else { response.push(` Objectives: ${objectives.slice(0, 2).join(', ')}${objectives.length > 2 ? '...' : ''}`); 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 background agents: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Terminate Background Agent Tool registerTool( 'terminate_background_agent', 'Terminate a background agent by ID. No admin token required - background agents are lightweight and accessible!', z.object({ agent_id: z.string().describe('ID of the background agent to terminate') }), async (args, context) => { const { agent_id } = args; if (!agent_id) { return { content: [{ type: 'text' as const, text: '❌ Error: agent_id is required' }], isError: true }; } const db = getDbConnection(); try { // Check if background agent exists const agent = db.prepare(` SELECT * FROM agents WHERE agent_id = ? AND capabilities LIKE '%background-agent%' AND status != ? `).get(agent_id, 'terminated'); if (!agent) { return { content: [{ type: 'text' as const, text: `❌ Background agent '${agent_id}' not found or already terminated` }], isError: true }; } const terminatedAt = new Date().toISOString(); // Update agent status const updateAgent = db.prepare(` UPDATE agents SET status = ?, terminated_at = ?, updated_at = ? WHERE agent_id = ? AND capabilities LIKE '%background-agent%' `); updateAgent.run('terminated', terminatedAt, terminatedAt, agent_id); // Kill tmux session if it exists const tmuxSessionName = backgroundGlobalState.backgroundTmuxSessions.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 background agent '${agent_id}': ${tmuxSessionName}`); } } else { tmuxStatus = `- ⚠️ Failed to kill tmux session '${tmuxSessionName}'`; } } else { tmuxStatus = `- Tmux session '${tmuxSessionName}' already stopped`; } } catch (error) { tmuxStatus = `- ⚠️ Error killing tmux session: ${error instanceof Error ? error.message : String(error)}`; } backgroundGlobalState.backgroundTmuxSessions.delete(agent_id); } else { tmuxStatus = '- No tmux session found'; } // Update global state backgroundGlobalState.backgroundAgents.delete(agent_id); // Log termination logBackgroundAgentAction('system', 'terminated_background_agent', { agent_id, terminated_at: terminatedAt }); const response = [ `✅ **Background Agent '${agent_id}' Terminated Successfully**`, '', `- Terminated at: ${terminatedAt}`, tmuxStatus, '- Removed from active memory', '', '🔴 Background agent is fully stopped' ]; return { content: [{ type: 'text' as const, text: response.join('\n') }] }; } catch (error) { console.error(`Error terminating background agent ${agent_id}:`, error); return { content: [{ type: 'text' as const, text: `❌ Error terminating background agent: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); console.log('✅ Background 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