Skip to main content
Glama

Chat Context MCP

by aolshaun
claude-code-db.ts7.72 kB
/** * Claude Code Database Reader * * Reads chat sessions from Claude Code's JSONL files * Location: ~/.claude/projects/[project-path]/[session-id].jsonl */ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; /** * Single message from Claude Code JSONL file */ export interface ClaudeCodeMessage { parentUuid: string | null; isSidechain: boolean; userType: string; cwd: string; sessionId: string; version: string; gitBranch: string; type: 'user' | 'assistant'; message: { role: 'user' | 'assistant'; content: string | Array<{ type: 'text' | 'tool_use' | 'tool_result'; text?: string; id?: string; name?: string; input?: any; tool_use_id?: string; content?: string; }>; model?: string; id?: string; usage?: any; }; uuid: string; timestamp: string; requestId?: string; } /** * Claude Code session metadata */ export interface ClaudeCodeSession { sessionId: string; projectPath: string; projectName: string; messageCount: number; createdAt: number; lastAccessedAt: number; firstMessagePreview?: string; } /** * Database reader for Claude Code sessions */ export class ClaudeCodeDB { private claudeProjectsPath: string; constructor(claudeProjectsPath?: string) { this.claudeProjectsPath = claudeProjectsPath || path.join(os.homedir(), '.claude', 'projects'); } /** * Get all Claude Code sessions across all projects */ getAllSessions(): ClaudeCodeSession[] { const sessions: ClaudeCodeSession[] = []; if (!fs.existsSync(this.claudeProjectsPath)) { return sessions; } const projectDirs = fs.readdirSync(this.claudeProjectsPath, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); for (const projectDir of projectDirs) { const projectPath = path.join(this.claudeProjectsPath, projectDir); const sessionFiles = this.getSessionFilesInProject(projectPath); for (const sessionFile of sessionFiles) { try { const session = this.parseSessionFile(sessionFile, projectDir); if (session) { sessions.push(session); } } catch (error) { // Skip invalid session files console.error(`Error parsing session file ${sessionFile}:`, error); } } } return sessions; } /** * Get all session files in a project directory */ private getSessionFilesInProject(projectPath: string): string[] { if (!fs.existsSync(projectPath)) { return []; } return fs.readdirSync(projectPath) .filter(file => file.endsWith('.jsonl')) .map(file => path.join(projectPath, file)); } /** * Parse a session JSONL file */ private parseSessionFile(filePath: string, projectDir: string): ClaudeCodeSession | null { const content = fs.readFileSync(filePath, 'utf-8'); const lines = content.trim().split('\n').filter(line => line.trim()); if (lines.length === 0) { return null; } const messages: ClaudeCodeMessage[] = []; let sessionId = ''; let cwd = ''; let firstMessageText = ''; let createdAt = 0; let lastAccessedAt = 0; for (const line of lines) { try { const msg = JSON.parse(line) as ClaudeCodeMessage; messages.push(msg); // Extract metadata from first message if (!sessionId) { sessionId = msg.sessionId; cwd = msg.cwd; createdAt = new Date(msg.timestamp).getTime(); } // Get first user message for preview if (!firstMessageText && msg.type === 'user') { if (typeof msg.message.content === 'string') { firstMessageText = msg.message.content; } else if (Array.isArray(msg.message.content)) { const textContent = msg.message.content.find(c => c.type === 'text'); if (textContent && textContent.text) { firstMessageText = textContent.text; } } } // Track last accessed time const msgTime = new Date(msg.timestamp).getTime(); if (msgTime > lastAccessedAt) { lastAccessedAt = msgTime; } } catch (error) { // Skip invalid lines continue; } } if (!sessionId) { return null; } // Extract project name from cwd or directory name const projectName = cwd ? path.basename(cwd) : this.extractProjectNameFromDir(projectDir); return { sessionId, projectPath: cwd || this.extractProjectPathFromDir(projectDir), projectName, messageCount: messages.length, createdAt, lastAccessedAt, firstMessagePreview: firstMessageText.substring(0, 200) }; } /** * Extract project name from directory name * Example: "-Users-macbook-play-chat-context-mcp" -> "chat-context-mcp" */ private extractProjectNameFromDir(dirName: string): string { const parts = dirName.split('-').filter(p => p); return parts[parts.length - 1] || 'unknown'; } /** * Extract project path from directory name * Example: "-Users-macbook-play-chat-context-mcp" -> "/Users/macbook/play/chat-context-mcp" */ private extractProjectPathFromDir(dirName: string): string { // Directory names are encoded with dashes replacing slashes // Example: -Users-macbook-play-project -> /Users/macbook/play/project return '/' + dirName.replace(/^-/, '').replace(/-/g, '/'); } /** * Get messages for a specific session */ getSessionMessages(sessionId: string): ClaudeCodeMessage[] { // Find the session file across all projects if (!fs.existsSync(this.claudeProjectsPath)) { return []; } const projectDirs = fs.readdirSync(this.claudeProjectsPath, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); for (const projectDir of projectDirs) { const projectPath = path.join(this.claudeProjectsPath, projectDir); const sessionFile = path.join(projectPath, `${sessionId}.jsonl`); if (fs.existsSync(sessionFile)) { return this.readMessagesFromFile(sessionFile); } } return []; } /** * Read all messages from a JSONL file */ private readMessagesFromFile(filePath: string): ClaudeCodeMessage[] { const content = fs.readFileSync(filePath, 'utf-8'); const lines = content.trim().split('\n').filter(line => line.trim()); const messages: ClaudeCodeMessage[] = []; for (const line of lines) { try { const msg = JSON.parse(line) as ClaudeCodeMessage; messages.push(msg); } catch (error) { // Skip invalid lines continue; } } return messages; } /** * Get session timestamps for sync optimization * Returns Map<sessionId, lastAccessedTimestamp> */ getSessionTimestamps(limit?: number): Map<string, number> { const sessions = this.getAllSessions(); const timestamps = new Map<string, number>(); // Sort by last accessed (newest first) const sorted = sessions.sort((a, b) => b.lastAccessedAt - a.lastAccessedAt); // Apply limit if specified const limited = limit ? sorted.slice(0, limit) : sorted; for (const session of limited) { timestamps.set(session.sessionId, session.lastAccessedAt); } return timestamps; } /** * Get all session IDs */ getAllSessionIds(): string[] { return this.getAllSessions().map(s => s.sessionId); } /** * Close the database connection (no-op for file-based storage) */ close(): void { // No cleanup needed for file-based storage } }

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/aolshaun/chat-context-mcp'

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