Skip to main content
Glama
agent-inventory-scanner.js12.1 kB
// AGENT INVENTORY SCANNER - Runs BEFORE building any new agent // Creates a "shopping list" of available tools and agents to reuse // Prevents duplicate work and identifies reusable components import { promises as fs } from 'fs'; import path from 'path'; export class AgentInventoryScanner { constructor() { this.inventoryPath = 'C:/MCP/agent-inventory.json'; this.scanPaths = [ 'C:/MCP/src/agents', 'C:/AI Projects/agents', 'C:/AI Projects/scripts', 'C:/tools', 'C:/AgentSystem' ]; this.inventory = { lastScanned: null, totalAgents: 0, totalTools: 0, agents: {}, tools: {}, workflows: {}, reusableComponents: [] }; } // MAIN METHOD: Run full scan and create shopping list async scan() { console.log('🔍 Starting Agent Inventory Scan...'); try { // Scan all directories for (const scanPath of this.scanPaths) { await this.scanDirectory(scanPath); } // Generate reusable components list this.identifyReusableComponents(); // Save inventory await this.saveInventory(); console.log('✅ Agent Inventory Scan Complete!'); console.log(`📊 Found: ${this.inventory.totalAgents} agents, ${this.inventory.totalTools} tools`); return this.inventory; } catch (error) { console.error('❌ Scan failed:', error); throw error; } } // Scan a directory for agents and tools async scanDirectory(dirPath) { try { const exists = await this.directoryExists(dirPath); if (!exists) { console.log(`⚠️ Directory not found: ${dirPath}`); return; } const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { // Skip node_modules and hidden folders if (entry.name === 'node_modules' || entry.name.startsWith('.')) { continue; } await this.scanDirectory(fullPath); } else if (entry.isFile()) { await this.analyzeFile(fullPath, entry.name); } } } catch (error) { console.error(`⚠️ Failed to scan ${dirPath}:`, error.message); } } // Analyze a file to determine if it's an agent or tool async analyzeFile(filePath, fileName) { try { // Only analyze JS/TS files if (!fileName.endsWith('.js') && !fileName.endsWith('.ts')) { return; } const content = await fs.readFile(filePath, 'utf-8'); // Detect if it's an agent if (this.isAgent(content, fileName)) { const agentInfo = this.extractAgentInfo(content, fileName, filePath); this.inventory.agents[agentInfo.id] = agentInfo; this.inventory.totalAgents++; console.log(` 📌 Found Agent: ${agentInfo.name}`); } // Detect if it's a tool else if (this.isTool(content, fileName)) { const toolInfo = this.extractToolInfo(content, fileName, filePath); this.inventory.tools[toolInfo.id] = toolInfo; this.inventory.totalTools++; console.log(` 🔧 Found Tool: ${toolInfo.name}`); } } catch (error) { // Skip files that can't be read } } // Check if file is an agent isAgent(content, fileName) { const agentIndicators = [ 'export class', 'class Agent', 'Agent extends', 'agent-', 'orchestrator', 'controller' ]; return agentIndicators.some(indicator => content.includes(indicator) || fileName.toLowerCase().includes(indicator) ); } // Check if file is a tool isTool(content, fileName) { const toolIndicators = [ 'export function', 'export const', '-tools', '-client', 'integration' ]; return toolIndicators.some(indicator => content.includes(indicator) || fileName.toLowerCase().includes(indicator) ); } // Extract agent information extractAgentInfo(content, fileName, filePath) { const name = this.extractClassName(content) || fileName.replace(/\.(js|ts)$/, ''); const id = this.generateId(name); return { id: id, name: name, type: 'agent', filePath: filePath, fileName: fileName, capabilities: this.extractCapabilities(content), dependencies: this.extractDependencies(content), hasMCPIntegration: content.includes('mcp') || content.includes('MCP'), hasNotionSync: content.includes('notion'), hasDatabase: content.includes('pool.query') || content.includes('database'), scannedAt: new Date().toISOString() }; } // Extract tool information extractToolInfo(content, fileName, filePath) { const name = this.extractExportName(content) || fileName.replace(/\.(js|ts)$/, ''); const id = this.generateId(name); return { id: id, name: name, type: 'tool', filePath: filePath, fileName: fileName, functions: this.extractFunctions(content), dependencies: this.extractDependencies(content), scannedAt: new Date().toISOString() }; } // Extract class name from code extractClassName(content) { const classMatch = content.match(/export class (\w+)/); return classMatch ? classMatch[1] : null; } // Extract export name from code extractExportName(content) { const exportMatch = content.match(/export (?:const|function) (\w+)/); return exportMatch ? exportMatch[1] : null; } // Extract capabilities from agent code extractCapabilities(content) { const capabilities = []; const capabilityPatterns = { 'Document Control': /doc.*control|documentation|version/i, 'File Organization': /organiz|filing|directory/i, 'Quality Review': /quality|review|assess/i, 'Database Management': /database|query|pool/i, 'Notion Sync': /notion.*sync|syncToNotion/i, 'Workflow Orchestration': /orchestrat|workflow|pipeline/i, 'Approval System': /approval|review.*board/i }; for (const [capability, pattern] of Object.entries(capabilityPatterns)) { if (pattern.test(content)) { capabilities.push(capability); } } return capabilities; } // Extract function names from code extractFunctions(content) { const functions = []; const functionMatches = content.matchAll(/(?:async\s+)?(?:function\s+)?(\w+)\s*\([^)]*\)\s*{/g); for (const match of functionMatches) { functions.push(match[1]); } return functions.slice(0, 10); // Limit to first 10 } // Extract dependencies from imports extractDependencies(content) { const deps = []; const importMatches = content.matchAll(/import .* from ['"](.+)['"]/g); for (const match of importMatches) { deps.push(match[1]); } return [...new Set(deps)]; // Remove duplicates } // Identify reusable components across agents identifyReusableComponents() { const components = []; // Find core capabilities that appear in multiple agents const capabilityMap = {}; for (const agent of Object.values(this.inventory.agents)) { for (const capability of agent.capabilities) { if (!capabilityMap[capability]) { capabilityMap[capability] = []; } capabilityMap[capability].push(agent.name); } } // Mark capabilities used by multiple agents as reusable for (const [capability, agents] of Object.entries(capabilityMap)) { if (agents.length >= 2) { components.push({ capability: capability, usedBy: agents, recommendation: 'Core component - use in new agents' }); } } this.inventory.reusableComponents = components; } // Save inventory to JSON file async saveInventory() { this.inventory.lastScanned = new Date().toISOString(); await fs.writeFile( this.inventoryPath, JSON.stringify(this.inventory, null, 2), 'utf-8' ); console.log(`💾 Inventory saved to: ${this.inventoryPath}`); } // Helper: Check if directory exists async directoryExists(dirPath) { try { await fs.access(dirPath); return true; } catch { return false; } } // Helper: Generate unique ID generateId(name) { return name.toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); } // QUERY METHOD: Search for existing agents/tools before building async findSimilar(description) { // Load inventory if not loaded if (this.inventory.totalAgents === 0) { await this.loadInventory(); } const keywords = description.toLowerCase().split(/\s+/); const matches = []; // Search agents for (const agent of Object.values(this.inventory.agents)) { let score = 0; const searchText = `${agent.name} ${agent.capabilities.join(' ')}`.toLowerCase(); for (const keyword of keywords) { if (searchText.includes(keyword)) { score++; } } if (score > 0) { matches.push({ ...agent, matchScore: score, category: 'agent' }); } } // Search tools for (const tool of Object.values(this.inventory.tools)) { let score = 0; const searchText = `${tool.name} ${tool.functions.join(' ')}`.toLowerCase(); for (const keyword of keywords) { if (searchText.includes(keyword)) { score++; } } if (score > 0) { matches.push({ ...tool, matchScore: score, category: 'tool' }); } } // Sort by match score matches.sort((a, b) => b.matchScore - a.matchScore); return matches.slice(0, 5); // Return top 5 matches } // Load existing inventory async loadInventory() { try { const data = await fs.readFile(this.inventoryPath, 'utf-8'); this.inventory = JSON.parse(data); console.log('📂 Loaded existing inventory'); } catch (error) { console.log('📂 No existing inventory found - will create new'); } } } // Export singleton instance export const agentInventory = new AgentInventoryScanner();

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/bermingham85/mcp-puppet-pipeline'

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