Skip to main content
Glama

Context Pods

by conorluddy
list.tsโ€ข7.32 kB
/** * List command implementation */ import { promises as fs } from 'fs'; import path from 'path'; import type { CommandContext, CommandResult, MCPInfo } from '../types/cli-types.js'; import { output } from '../utils/output-formatter.js'; /** * List generated MCP servers */ export async function listCommand( options: { all?: boolean; format?: 'table' | 'json' }, context: CommandContext, ): Promise<CommandResult> { try { output.startSpinner('Scanning for MCP servers...'); const mcps = await findMCPServers(context); output.stopSpinner(); if (mcps.length === 0) { output.info('No MCP servers found'); return { success: true, message: 'No MCP servers found', data: [], }; } // Filter inactive servers if not showing all const filteredMCPs = options.all ? mcps : mcps.filter((mcp) => mcp.status !== 'inactive'); if (options.format === 'json') { console.log(JSON.stringify(filteredMCPs, null, 2)); } else { displayMCPTable(filteredMCPs); } return { success: true, message: `Found ${filteredMCPs.length} MCP server(s)`, data: filteredMCPs, }; } catch (error) { output.stopSpinner(); output.error('Failed to list MCP servers', error as Error); return { success: false, error: error as Error, message: error instanceof Error ? error.message : 'Failed to list MCPs', }; } } /** * Find MCP servers in the output directory */ async function findMCPServers(context: CommandContext): Promise<MCPInfo[]> { const mcps: MCPInfo[] = []; const outputDir = context.outputPath; try { // Check if output directory exists await fs.access(outputDir); const entries = await fs.readdir(outputDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const mcpPath = path.join(outputDir, entry.name); const mcpInfo = await analyzeMCPDirectory(entry.name, mcpPath); if (mcpInfo) { mcps.push(mcpInfo); } } } } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { throw error; } // Output directory doesn't exist, return empty array } return mcps.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime()); } /** * Analyze a directory to determine if it's an MCP server */ async function analyzeMCPDirectory(name: string, mcpPath: string): Promise<MCPInfo | null> { try { const packageJsonPath = path.join(mcpPath, 'package.json'); const stat = await fs.stat(mcpPath); // Check if it has a package.json let template: string | undefined; let status: 'active' | 'inactive' | 'error' = 'inactive'; try { const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(packageJsonContent); // Check if it looks like an MCP server if ( packageJson.keywords?.includes('mcp') || packageJson.keywords?.includes('model-context-protocol') || packageJson.name?.includes('mcp') ) { status = 'active'; // Try to determine template from package.json or other indicators template = packageJson['context-pods']?.template || packageJson.template || (await detectTemplateFromStructure(mcpPath)); } else { // Not an MCP server, return null return null; } } catch { // No package.json or invalid JSON, check if it looks like an MCP project anyway const hasSrcDir = await pathExists(path.join(mcpPath, 'src')); const hasIndexFile = (await pathExists(path.join(mcpPath, 'src', 'index.ts'))) || (await pathExists(path.join(mcpPath, 'src', 'index.js'))) || (await pathExists(path.join(mcpPath, 'index.ts'))) || (await pathExists(path.join(mcpPath, 'index.js'))); if (hasSrcDir || hasIndexFile) { status = 'error'; // Looks like a project but has issues } else { return null; // Not an MCP server directory } } return { name, path: mcpPath, status, template, createdAt: stat.birthtime, lastModified: stat.mtime, }; } catch { return null; } } /** * Detect template from directory structure */ async function detectTemplateFromStructure(mcpPath: string): Promise<string | undefined> { // Check for TypeScript if (await pathExists(path.join(mcpPath, 'tsconfig.json'))) { // Check for advanced TypeScript features const srcPath = path.join(mcpPath, 'src'); const hasTools = await pathExists(path.join(srcPath, 'tools')); const hasResources = await pathExists(path.join(srcPath, 'resources')); if (hasTools && hasResources) { return 'typescript-advanced'; } else { return 'basic'; } } // Check for Python if ( (await pathExists(path.join(mcpPath, 'requirements.txt'))) || (await pathExists(path.join(mcpPath, 'main.py'))) ) { return 'python-basic'; } return undefined; } /** * Check if path exists */ async function pathExists(path: string): Promise<boolean> { try { await fs.access(path); return true; } catch { return false; } } /** * Display MCP servers in table format */ function displayMCPTable(mcps: MCPInfo[]): void { output.info(`Found ${mcps.length} MCP server(s):`); output.divider(); mcps.forEach((mcp, index) => { const statusColor = mcp.status === 'active' ? 'green' : mcp.status === 'error' ? 'red' : 'gray'; const templateDisplay = mcp.template || 'Unknown'; const relativeTime = formatRelativeTime(mcp.lastModified); output.info(`${index + 1}. ${output.package(mcp.name)}`); output.table([ { label: ' Status', value: mcp.status, color: statusColor }, { label: ' Template', value: templateDisplay, color: 'blue' }, { label: ' Path', value: path.relative(process.cwd(), mcp.path), color: 'yellow' }, { label: ' Modified', value: relativeTime, color: 'gray' }, ]); if (index < mcps.length - 1) { console.log(); } }); output.divider(); // Show summary statistics const statusCounts = mcps.reduce( (acc, mcp) => { acc[mcp.status] = (acc[mcp.status] || 0) + 1; return acc; }, {} as Record<string, number>, ); const summaryParts = Object.entries(statusCounts).map(([status, count]) => { // Could use color for future formatting enhancements // const color = status === 'active' ? 'green' : status === 'error' ? 'red' : 'gray'; return `${count} ${status}`; }); output.info(`Summary: ${summaryParts.join(', ')}`); } /** * Format relative time */ function formatRelativeTime(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffMins < 1) { return 'just now'; } else if (diffMins < 60) { return `${diffMins}m ago`; } else if (diffHours < 24) { return `${diffHours}h ago`; } else if (diffDays < 7) { return `${diffDays}d ago`; } else { return date.toLocaleDateString(); } }

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/conorluddy/ContextPods'

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