Skip to main content
Glama
index.ts22.2 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { LogDatabase } from "./database.js"; import { getGitInfo } from "./git-utils.js"; import * as path from "path"; import * as os from "os"; class ConsoleLogMCPServer { private server: Server; private db: LogDatabase; constructor() { this.server = new Server( { name: "console-log-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); // Default log directory (can be overridden via environment variable) const defaultLogDir = process.env.CONSOLE_LOG_DIR || path.join(os.homedir(), ".console-logs"); this.db = new LogDatabase(defaultLogDir); this.setupHandlers(); } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_logs", description: "Search through console log files using full-text search", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (supports FTS5 syntax)", }, process: { type: "string", description: "Optional: Filter by specific process name", }, level: { type: "string", description: "Optional: Filter by log level (error, warn, info, debug)", }, since: { type: "string", description: "Optional: Search logs since this timestamp (ISO format)", }, limit: { type: "number", description: "Optional: Limit number of results (default: 50)", }, }, required: ["query"], }, }, { name: "get_recent_errors", description: "Get recent error messages from all console logs", inputSchema: { type: "object", properties: { hours: { type: "number", description: "Number of hours to look back (default: 1)", }, limit: { type: "number", description: "Optional: Limit number of results (default: 20)", }, process: { type: "string", description: "Optional: Filter by specific process name", }, }, }, }, { name: "list_processes", description: "List all processes that have console logs", inputSchema: { type: "object", properties: { active_only: { type: "boolean", description: "Only show processes with recent activity (default: false)", }, }, }, }, { name: "tail_process_logs", description: "Get latest log entries from a specific process", inputSchema: { type: "object", properties: { process: { type: "string", description: "Process name to tail logs for", }, lines: { type: "number", description: "Number of lines to return (default: 20)", }, level: { type: "string", description: "Optional: Filter by log level", }, }, required: ["process"], }, }, { name: "get_log_summary", description: "Get summary of log activity across all processes", inputSchema: { type: "object", properties: { hours: { type: "number", description: "Number of hours to summarize (default: 24)", }, }, }, }, { name: "create_session_summary", description: "Create a session summary that can be searched by future Copilot sessions", inputSchema: { type: "object", properties: { title: { type: "string", description: "Title of the session summary", }, description: { type: "string", description: "Detailed description of the session (can be markdown)", }, tags: { type: "array", items: { type: "string" }, description: "Array of tags for categorizing the summary", }, project: { type: "string", description: "Optional: Project name (auto-detected from git/package.json if not provided)", }, llm_model: { type: "string", description: "Optional: LLM model used during the session", }, files_changed: { type: "array", items: { type: "string" }, description: "Optional: Array of file paths (auto-detected from git if not provided)", }, workspace_root: { type: "string", description: "Optional: Root directory of the workspace for auto-detection", }, }, required: ["title", "description"], }, }, { name: "search_session_summaries", description: "Search through session summaries for context and insights", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query to find relevant session summaries", }, project: { type: "string", description: "Optional: Filter by specific project", }, since: { type: "string", description: "Optional: Search summaries since this timestamp (ISO format)", }, limit: { type: "number", description: "Optional: Limit number of results (default: 50)", }, }, required: ["query"], }, }, { name: "get_session_summaries_by_project", description: "Get session summaries for a specific project", inputSchema: { type: "object", properties: { project: { type: "string", description: "Project name to get summaries for", }, limit: { type: "number", description: "Optional: Limit number of results (default: 50)", }, }, required: ["project"], }, }, { name: "get_session_summaries_by_tags", description: "Get session summaries by tags", inputSchema: { type: "object", properties: { tags: { type: "array", items: { type: "string" }, description: "Array of tags to search for", }, limit: { type: "number", description: "Optional: Limit number of results (default: 50)", }, }, required: ["tags"], }, }, { name: "get_recent_session_summaries", description: "Get recent session summaries", inputSchema: { type: "object", properties: { hours: { type: "number", description: "Number of hours to look back (default: 24)", }, limit: { type: "number", description: "Optional: Limit number of results (default: 50)", }, }, }, }, { name: "list_projects", description: "List all projects that have session summaries", inputSchema: { type: "object", properties: {}, }, }, { name: "prune_old_logs", description: "Remove old console logs from the database to free up space. Only affects console logs, not session summaries.", inputSchema: { type: "object", properties: { max_age_hours: { type: "number", description: "Maximum age of logs to keep in hours (e.g., 168 for 1 week, 720 for 1 month)", }, dry_run: { type: "boolean", description: "Optional: If true, shows what would be deleted without actually deleting (default: false)", }, }, required: ["max_age_hours"], }, }, { name: "get_log_statistics", description: "Get statistics about the console logs database including size and age", inputSchema: { type: "object", properties: {}, }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "search_logs": { const { query, process: processName, level, since, limit = 50, } = args as { query: string; process?: string; level?: string; since?: string; limit?: number; }; const results = this.db.searchLogs( query, limit, processName, level, since, ); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "get_recent_errors": { const { hours = 1, limit = 20, process: processName, } = args as { hours?: number; limit?: number; process?: string; }; const errors = this.db.getRecentErrors(hours, limit, processName); return { content: [ { type: "text", text: JSON.stringify(errors, null, 2), }, ], }; } case "list_processes": { const { active_only = false } = args as { active_only?: boolean }; const processes = this.db.getAllProcesses(active_only); return { content: [ { type: "text", text: JSON.stringify(processes, null, 2), }, ], }; } case "tail_process_logs": { const { process: processName, lines = 20, level, } = args as { process: string; lines?: number; level?: string; }; const logs = this.db.getProcessLogs(processName, lines, level); return { content: [ { type: "text", text: JSON.stringify(logs, null, 2), }, ], }; } case "get_log_summary": { const { hours = 24 } = args as { hours?: number }; const summary = this.db.getLogSummary(hours); return { content: [ { type: "text", text: JSON.stringify(summary, null, 2), }, ], }; } case "create_session_summary": { const { title, description, tags = [], project, llm_model, files_changed, workspace_root, } = args as { title: string; description: string; tags?: string[]; project?: string; llm_model?: string; files_changed?: string[]; workspace_root?: string; }; // Auto-detect project info if not provided const gitInfo = getGitInfo(workspace_root); const finalProject = project || gitInfo.projectName; const finalFilesChanged = files_changed || gitInfo.changedFiles; const sessionSummary = { title, description, tags: JSON.stringify(tags), timestamp: new Date().toISOString(), project: finalProject, llm_model, files_changed: JSON.stringify(finalFilesChanged), }; const id = this.db.createSessionSummary(sessionSummary); return { content: [ { type: "text", text: `Session summary created with ID: ${id}\nProject: ${finalProject}\nFiles changed: ${finalFilesChanged.length} files`, }, ], }; } case "search_session_summaries": { const { query, project, since, limit = 50, } = args as { query: string; project?: string; since?: string; limit?: number; }; const results = this.db.searchSessionSummaries( query, limit, project, since, ); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "get_session_summaries_by_project": { const { project, limit = 50 } = args as { project: string; limit?: number; }; const results = this.db.getSessionSummariesByProject( project, limit, ); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "get_session_summaries_by_tags": { const { tags, limit = 50 } = args as { tags: string[]; limit?: number; }; const results = this.db.getSessionSummariesByTags(tags, limit); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "get_recent_session_summaries": { const { hours = 24, limit = 50 } = args as { hours?: number; limit?: number; }; const results = this.db.getRecentSessionSummaries(hours, limit); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "list_projects": { const projects = this.db.getAllProjects(); return { content: [ { type: "text", text: JSON.stringify(projects, null, 2), }, ], }; } case "prune_old_logs": { if (!args) { throw new Error("Missing required arguments"); } const maxAgeHours = args.max_age_hours as number; const dryRun = (args.dry_run as boolean) || false; if (dryRun) { // For dry run, count what would be deleted const cutoffTime = new Date( Date.now() - maxAgeHours * 60 * 60 * 1000, ).toISOString(); // Count logs that would be deleted const countStmt = this.db["db"].prepare(` SELECT COUNT(*) as count FROM log_entries WHERE timestamp < ? `); const logsToDeleteCount = ( countStmt.get(cutoffTime) as { count: number } ).count; // Count processes that would become orphaned const orphanCountStmt = this.db["db"].prepare(` SELECT COUNT(DISTINCT p.id) as count FROM processes p WHERE NOT EXISTS ( SELECT 1 FROM log_entries le WHERE le.process_id = p.id AND le.timestamp >= ? ) `); const processesToDeleteCount = ( orphanCountStmt.get(cutoffTime) as { count: number } ).count; return { content: [ { type: "text", text: JSON.stringify( { dry_run: true, max_age_hours: maxAgeHours, cutoff_time: cutoffTime, logs_that_would_be_deleted: logsToDeleteCount, processes_that_would_be_deleted: processesToDeleteCount, message: `DRY RUN: Would delete ${logsToDeleteCount} log entries and ${processesToDeleteCount} orphaned processes older than ${maxAgeHours} hours`, }, null, 2, ), }, ], }; } else { const result = this.db.pruneOldLogs(maxAgeHours); return { content: [ { type: "text", text: JSON.stringify( { max_age_hours: maxAgeHours, deleted_logs: result.deletedLogs, deleted_processes: result.deletedProcesses, message: `Successfully deleted ${result.deletedLogs} old log entries and ${result.deletedProcesses} orphaned processes`, }, null, 2, ), }, ], }; } } case "get_log_statistics": { const stats = this.db.getLogStatistics(); return { content: [ { type: "text", text: JSON.stringify( { ...stats, disk_usage_mb: Math.round((stats.diskUsageKB / 1024) * 100) / 100, age_info: stats.oldestLog && stats.newestLog ? { oldest_log_age_hours: Math.round( (new Date().getTime() - new Date(stats.oldestLog).getTime()) / (1000 * 60 * 60), ), newest_log_age_hours: Math.round( (new Date().getTime() - new Date(stats.newestLog).getTime()) / (1000 * 60 * 60), ), } : null, }, null, 2, ), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Console MCP server running on stdio"); } } const server = new ConsoleLogMCPServer(); server.run().catch(console.error);

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/bvelasquez/console_mcp'

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