Skip to main content
Glama
server.js17.3 kB
#!/usr/bin/env node // DXT-specific server implementation for Claude Desktop // Fixes process lifecycle issues while keeping NPM package unchanged import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; // Import from parent dist directory (NPM build) import { HistorySearchEngine } from '../dist/search.js'; import { BeautifulFormatter } from '../dist/formatter.js'; import { UniversalHistorySearchEngine } from '../dist/universal-engine.js'; class ClaudeHistorianDXTServer { constructor() { this.server = new Server( { name: 'claude-historian', version: '1.0.1', }, { capabilities: { tools: {}, }, } ); this.searchEngine = new HistorySearchEngine(); this.universalEngine = new UniversalHistorySearchEngine(); this.formatter = new BeautifulFormatter(); this.setupToolHandlers(); } setupToolHandlers() { // Copy exact tool handlers from main server implementation this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'search_conversations', description: 'Search through Claude Code conversation history with smart insights', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query to find relevant conversations' }, project: { type: 'string', description: 'Optional project name to filter results' }, timeframe: { type: 'string', description: 'Time range filter (today, week, month)' }, limit: { type: 'number', description: 'Maximum number of results (default: 10)', default: 10 }, detail_level: { type: 'string', description: 'Response detail: summary (default), detailed, raw', enum: ['summary', 'detailed', 'raw'], default: 'summary' } }, required: ['query'] } }, { name: 'find_file_context', description: 'Find all conversations and changes related to a specific file', inputSchema: { type: 'object', properties: { filepath: { type: 'string', description: 'File path to search for in conversation history' }, operation_type: { type: 'string', description: 'Filter by operation: read, edit, create, or all', enum: ['read', 'edit', 'create', 'all'], default: 'all' }, limit: { type: 'number', description: 'Maximum number of results (default: 15)', default: 15 }, detail_level: { type: 'string', description: 'Response detail: summary (default), detailed, raw', enum: ['summary', 'detailed', 'raw'], default: 'summary' } }, required: ['filepath'] } }, { name: 'find_similar_queries', description: 'Find previous similar questions or queries with enhanced matching', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Query to find similar previous questions' }, limit: { type: 'number', description: 'Maximum number of results (default: 8)', default: 8 }, detail_level: { type: 'string', description: 'Response detail: summary (default), detailed, raw', enum: ['summary', 'detailed', 'raw'], default: 'summary' } }, required: ['query'] } }, { name: 'get_error_solutions', description: 'Find solutions for specific errors with enhanced matching', inputSchema: { type: 'object', properties: { error_pattern: { type: 'string', description: 'Error message or pattern to search for solutions' }, limit: { type: 'number', description: 'Maximum number of results (default: 8)', default: 8 }, detail_level: { type: 'string', description: 'Response detail: summary (default), detailed, raw', enum: ['summary', 'detailed', 'raw'], default: 'summary' } }, required: ['error_pattern'] } }, { name: 'list_recent_sessions', description: 'Browse recent sessions with smart activity detection and summaries', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of sessions (default: 10)', default: 10 }, project: { type: 'string', description: 'Optional project name to filter sessions' }, include_summary: { type: 'boolean', description: 'Include intelligent session summaries (default: true)', default: true } } } }, { name: 'extract_compact_summary', description: 'Get intelligent summary of a conversation session with key insights', inputSchema: { type: 'object', properties: { session_id: { type: 'string', description: 'Session ID to summarize' }, max_messages: { type: 'number', description: 'Maximum messages to analyze (default: 10)', default: 10 }, focus: { type: 'string', description: 'Focus area: solutions, tools, files, or all', enum: ['solutions', 'tools', 'files', 'all'], default: 'all' } }, required: ['session_id'] } }, { name: 'find_tool_patterns', description: 'Analyze tool usage patterns, workflows, and successful practices', inputSchema: { type: 'object', properties: { tool_name: { type: 'string', description: 'Optional specific tool name to analyze' }, pattern_type: { type: 'string', description: 'Type of patterns: tools, workflows, or solutions', enum: ['tools', 'workflows', 'solutions'], default: 'tools' }, limit: { type: 'number', description: 'Maximum number of patterns (default: 12)', default: 12 } } } } ] }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'search_conversations': { const universalResult = await this.universalEngine.searchConversations( args?.query, args?.project, args?.timeframe, args?.limit || 10 ); const detailLevel = args?.detail_level || 'summary'; const formattedResult = this.formatter.formatSearchConversations(universalResult.results, detailLevel); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Query: "${args?.query}" | Action: Conversation search`; const scope = args?.project ? ` | Project: ${args?.project}` : ''; const timeInfo = args?.timeframe ? ` | Time: ${args?.timeframe}` : ''; lines[0] = sourceInfo; lines[1] = actionInfo + scope + timeInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } case 'find_file_context': { const universalResult = await this.universalEngine.findFileContext( args?.filepath, args?.limit || 15 ); const detailLevel = args?.detail_level || 'summary'; const operationType = args?.operation_type || 'all'; const formattedResult = this.formatter.formatFileContext(universalResult.results, args?.filepath, detailLevel, operationType); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Target: "${args?.filepath}" | Action: File change history`; const filterInfo = operationType !== 'all' ? ` | Filter: ${operationType}` : ''; lines[0] = sourceInfo; lines[1] = actionInfo + filterInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } case 'find_similar_queries': { const universalResult = await this.universalEngine.findSimilarQueries( args?.query, args?.limit || 8 ); const detailLevel = args?.detail_level || 'summary'; const formattedResult = this.formatter.formatSimilarQueries(universalResult.results, args?.query, detailLevel); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Query: "${args?.query}" | Action: Similar queries & patterns`; lines[0] = sourceInfo; lines[1] = actionInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } case 'get_error_solutions': { const universalResult = await this.universalEngine.getErrorSolutions( args?.error_pattern, args?.limit || 8 ); const detailLevel = args?.detail_level || 'summary'; const formattedResult = this.formatter.formatErrorSolutions( universalResult.results, args?.error_pattern, detailLevel ); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Error: "${args?.error_pattern}" | Action: Solution lookup`; lines[0] = sourceInfo; lines[1] = actionInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } case 'list_recent_sessions': { const limit = args?.limit || 10; const project = args?.project; const includeSummary = args?.include_summary !== false; const universalResult = await this.universalEngine.getRecentSessions(limit, project); const formattedResult = this.formatter.formatRecentSessions(universalResult.results, project); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Action: Recent session analysis` + (project ? ` | Project: ${project}` : '') + (includeSummary ? ' | With summaries' : ''); lines[0] = sourceInfo; lines[1] = actionInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } case 'extract_compact_summary': { const sessionId = args?.session_id; const maxMessages = args?.max_messages || 10; const focus = args?.focus || 'all'; const universalResult = await this.universalEngine.generateCompactSummary(sessionId, maxMessages, focus); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Session: "${sessionId}" | Action: Compact summary | Focus: ${focus}`; const summaryContent = universalResult.results.summary; const formattedResult = `${sourceInfo}\n${actionInfo}\n\n${summaryContent}`; return { content: [ { type: 'text', text: formattedResult, }, ], }; } case 'find_tool_patterns': { const universalResult = await this.universalEngine.getToolPatterns( args?.tool_name, args?.limit || 12 ); const patternType = args?.pattern_type || 'tools'; const formattedResult = this.formatter.formatToolPatterns(universalResult.results, args?.tool_name, patternType); const lines = formattedResult.split('\n'); const sourceInfo = universalResult.enhanced ? 'Searching: Claude Code + Desktop' : 'Searching: Claude Code'; const actionInfo = `Tool: "${args?.tool_name || 'all'}" | Action: Pattern analysis | Type: ${patternType}`; lines[0] = sourceInfo; lines[1] = actionInfo; return { content: [ { type: 'text', text: lines.join('\n'), }, ], }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { console.error('DXT Tool execution error:', error); throw new McpError( ErrorCode.InternalError, `Error executing ${request.params.name}: ${error}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Claude Historian DXT server running on stdio'); // DXT-specific: Let Node.js event loop keep process alive naturally // DO NOT use infinite promise - it blocks Claude Desktop transport // Handle graceful shutdown process.on('SIGINT', () => { console.error('DXT server received SIGINT, shutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('DXT server received SIGTERM, shutting down gracefully...'); process.exit(0); }); // Add error handlers for debugging process.on('uncaughtException', (error) => { console.error('DXT server uncaught exception:', error); process.exit(1); }); process.on('unhandledRejection', (reason) => { console.error('DXT server unhandled rejection:', reason); process.exit(1); }); } } // Start the DXT server const server = new ClaudeHistorianDXTServer(); 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/Vvkmnn/claude-historian'

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