Skip to main content
Glama

IDE Chat Summarizer

by RalphLi213
main.py24.5 kB
""" IDE Chat Summarizer MCP Server This MCP server is designed for IDE users (VS Code, Cursor, Visual Studio) to summarize chat conversations with AI and store the summaries as organized markdown files in your notes directory. Usage: uv run python main.py Or as MCP server: uv run mcp run main.py """ import os import json from datetime import datetime from pathlib import Path from typing import List, Dict, Any from mcp.server.fastmcp import FastMCP # Create MCP server mcp = FastMCP("IDE Chat Summarizer") # Notes directory configuration # Uses environment variable CHAT_NOTES_DIR if set, otherwise defaults to ~/Documents/ChatSummaries import os NOTES_DIR = Path(os.getenv("CHAT_NOTES_DIR", Path.home() / "Documents" / "ChatSummaries")) # Alternative configurations (uncomment and modify as needed): # NOTES_DIR = Path("D:/RL/Notes/MarkdownNotes") # Custom absolute path # NOTES_DIR = Path.cwd() / "summaries" # Relative to current directory # NOTES_DIR = Path.home() / "Notes" / "ChatSummaries" # User's Notes folder def ensure_notes_directory(): """Ensure the notes directory exists""" NOTES_DIR.mkdir(parents=True, exist_ok=True) return NOTES_DIR.exists() def generate_filename(title: str = None) -> str: """Generate a filename for the summary""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") if title: # Clean title for filename clean_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip() clean_title = clean_title.replace(' ', '_')[:50] # Limit length return f"chat_summary_{timestamp}_{clean_title}.md" return f"chat_summary_{timestamp}.md" def extract_code_blocks(text: str) -> List[Dict[str, str]]: """Extract code blocks from chat text""" import re # Pattern to match code blocks (both ``` and single backticks) code_block_pattern = r'```(\w+)?\n(.*?)\n```|`([^`]+)`' code_blocks = [] # Find all code blocks matches = re.findall(code_block_pattern, text, re.DOTALL) for match in matches: if match[0] or match[1]: # Triple backtick code block language = match[0] if match[0] else "text" code = match[1].strip() if code: # Only include non-empty code blocks code_blocks.append({ "language": language, "code": code, "type": "block" }) elif match[2]: # Inline code code = match[2].strip() if code: code_blocks.append({ "language": "text", "code": code, "type": "inline" }) return code_blocks def identify_final_solutions(chat_history: str, code_blocks: List[Dict[str, str]]) -> List[Dict[str, str]]: """Identify code blocks that appear to be final solutions""" if not code_blocks: return [] final_solutions = [] # Keywords that suggest a final solution solution_keywords = [ "final", "solution", "working", "complete", "fixed", "resolved", "here's the", "this works", "final version", "complete code", "final implementation", "working example", "solved" ] # Look for code blocks that appear near solution keywords lines = chat_history.split('\n') for i, code_block in enumerate(code_blocks): # Check if this code block appears near solution keywords code_context = "" # Find the code block in the original text to get context code_start_idx = chat_history.find(code_block["code"]) if code_start_idx != -1: # Get 200 characters before and after the code block for context context_start = max(0, code_start_idx - 200) context_end = min(len(chat_history), code_start_idx + len(code_block["code"]) + 200) code_context = chat_history[context_start:context_end].lower() # Check if any solution keywords appear in the context is_solution = any(keyword in code_context for keyword in solution_keywords) # Also consider code blocks that appear later in the conversation as more likely to be solutions position_weight = (code_start_idx / len(chat_history)) * 100 # Percentage through conversation if is_solution or position_weight > 70: # If it's marked as solution or appears in last 30% of conversation final_solutions.append({ **code_block, "context": code_context[:100] + "..." if len(code_context) > 100 else code_context, "position_weight": position_weight }) return final_solutions @mcp.tool() def summarize_chat( chat_history: str, title: str = None, summary_style: str = "detailed", include_full_history: bool = True, create_separate_full_history: bool = False ) -> str: """ Summarize chat history and save it as a markdown file. Args: chat_history: The chat conversation text to summarize title: Optional title for the summary (will be used in filename) summary_style: Style of summary - 'brief', 'detailed', or 'bullet_points' include_full_history: Whether to include the full chat history in the summary file (default: True) create_separate_full_history: Whether to create a separate file with just the full history (default: False) Returns: Path to the created summary file and preview of the summary """ try: # Ensure notes directory exists if not ensure_notes_directory(): return "Error: Could not create or access notes directory" # Detect code sections and final solutions code_blocks = extract_code_blocks(chat_history) final_solutions = identify_final_solutions(chat_history, code_blocks) has_code = len(code_blocks) > 0 has_solutions = len(final_solutions) > 0 # Generate summary based on style with code awareness if summary_style == "brief": if has_code: summary_prompt = "Provide a brief 2-3 sentence summary of the key points from this conversation. Include any final code solutions or important code snippets that solve the main problem." else: summary_prompt = "Provide a brief 2-3 sentence summary of the key points from this conversation." elif summary_style == "bullet_points": if has_code: summary_prompt = "Create a bullet-point summary of the main topics and decisions from this conversation. Include a section for final code solutions and important code snippets." else: summary_prompt = "Create a bullet-point summary of the main topics and decisions from this conversation." else: # detailed if has_code: summary_prompt = "Provide a detailed summary including main topics discussed, key decisions made, and important insights from this conversation. Pay special attention to final code solutions, working code examples, and important code snippets that solve the main problems discussed." else: summary_prompt = "Provide a detailed summary including main topics discussed, key decisions made, and important insights from this conversation." # Create summary content (this would typically use an AI model) # For now, we'll create a structured summary lines = chat_history.split('\n') message_count = len([line for line in lines if line.strip()]) # Generate filename filename = generate_filename(title) filepath = NOTES_DIR / filename # Determine content organization based on size history_size_mb = len(chat_history) / (1024 * 1024) is_large_history = history_size_mb > 1 # Consider >1MB as large # Create code solutions section if found code_solutions_section = "" if has_solutions: code_solutions_section = "\n### 💻 Final Code Solutions\n\n" for i, solution in enumerate(final_solutions, 1): lang = solution.get("language", "text") code = solution["code"] solution_type = "Code Block" if solution["type"] == "block" else "Inline Code" code_solutions_section += f"**Solution {i} ({solution_type} - {lang}):**\n" code_solutions_section += f"```{lang}\n{code}\n```\n\n" elif has_code and not has_solutions: # Include all code blocks if no specific solutions were identified code_solutions_section = "\n### 📝 Code Snippets\n\n" for i, code_block in enumerate(code_blocks[:5], 1): # Limit to first 5 blocks lang = code_block.get("language", "text") code = code_block["code"] code_type = "Code Block" if code_block["type"] == "block" else "Inline Code" code_solutions_section += f"**Code {i} ({code_type} - {lang}):**\n" code_solutions_section += f"```{lang}\n{code}\n```\n\n" # Create summary section summary_section = f"""## Summary {summary_prompt} ### Key Points - Total messages in conversation: {message_count} - Conversation length: {len(chat_history):,} characters ({history_size_mb:.2f} MB) - Large history: {'Yes' if is_large_history else 'No'} - Code blocks found: {len(code_blocks)} - Final solutions identified: {len(final_solutions)} {code_solutions_section}""" # Create markdown content based on options if include_full_history: if is_large_history: # For large histories, put summary first, then full history in collapsible section markdown_content = f"""# Chat Summary: {title or 'Untitled'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Messages:** {message_count} **Summary Style:** {summary_style.replace('_', ' ').title()} {summary_section} ## Full Chat History <details> <summary>Click to expand full conversation ({history_size_mb:.2f} MB)</summary> ``` {chat_history} ``` </details> --- *Generated by Chat History Summarizer MCP Server* """ else: # For smaller histories, include everything normally markdown_content = f"""# Chat Summary: {title or 'Untitled'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Messages:** {message_count} **Summary Style:** {summary_style.replace('_', ' ').title()} {summary_section} ## Original Chat History ``` {chat_history} ``` --- *Generated by Chat History Summarizer MCP Server* """ else: # Summary only markdown_content = f"""# Chat Summary: {title or 'Untitled'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Messages:** {message_count} **Summary Style:** {summary_style.replace('_', ' ').title()} {summary_section} > **Note:** Full chat history not included in this summary file. > Original conversation length: {len(chat_history):,} characters ({history_size_mb:.2f} MB) --- *Generated by Chat History Summarizer MCP Server* """ # Save main summary file with open(filepath, 'w', encoding='utf-8') as f: f.write(markdown_content) result_message = f"Summary saved to: {filepath}" # Create separate full history file if requested if create_separate_full_history: history_filename = filepath.stem.replace("chat_summary_", "chat_full_") + ".md" history_filepath = NOTES_DIR / history_filename full_history_content = f"""# Full Chat History: {title or 'Untitled'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Messages:** {message_count} **Size:** {len(chat_history):,} characters ({history_size_mb:.2f} MB) ## Complete Conversation ``` {chat_history} ``` --- *Full chat history preserved by Chat History Summarizer MCP Server* """ with open(history_filepath, 'w', encoding='utf-8') as f: f.write(full_history_content) result_message += f"\nFull history saved separately to: {history_filepath}" preview = markdown_content[:500] + "..." if len(markdown_content) > 500 else markdown_content return f"{result_message}\n\nPreview:\n{preview}" except Exception as e: return f"Error creating summary: {str(e)}" @mcp.tool() def summarize_large_chat( chat_history: str, title: str = None, chunk_size: int = 50000, overlap: int = 5000 ) -> str: """ Handle extremely large chat histories by chunking them into manageable pieces. Each chunk gets its own summary, then creates a master summary. Args: chat_history: The large chat conversation text to summarize title: Optional title for the summary chunk_size: Size of each chunk in characters (default: 50,000) overlap: Overlap between chunks in characters (default: 5,000) Returns: Information about the chunked summaries created """ try: if not ensure_notes_directory(): return "Error: Could not create or access notes directory" history_size = len(chat_history) if history_size <= chunk_size: return f"Chat history ({history_size:,} chars) is small enough for regular summarization. Use summarize_chat instead." # Calculate chunks chunks = [] start = 0 chunk_num = 1 while start < len(chat_history): end = min(start + chunk_size, len(chat_history)) chunk_text = chat_history[start:end] chunks.append({ 'number': chunk_num, 'text': chunk_text, 'start': start, 'end': end, 'size': len(chunk_text) }) start = end - overlap # Overlap to maintain context chunk_num += 1 # Create individual chunk summaries timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") clean_title = title.replace(' ', '_')[:30] if title else "Large_Chat" chunk_summaries = [] chunk_files = [] for chunk in chunks: chunk_filename = f"chat_chunk_{timestamp}_{clean_title}_part{chunk['number']:02d}.md" chunk_filepath = NOTES_DIR / chunk_filename chunk_content = f"""# Chat Chunk {chunk['number']}: {title or 'Large Chat'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Chunk:** {chunk['number']} of {len(chunks)} **Characters:** {chunk['start']:,} - {chunk['end']:,} ({chunk['size']:,} chars) **Total Size:** {history_size:,} characters ## Chunk Summary This is part {chunk['number']} of a large conversation that was split into {len(chunks)} chunks for processing. ### Key Points from This Chunk - Chunk size: {chunk['size']:,} characters - Position in conversation: {(chunk['start']/history_size)*100:.1f}% - {(chunk['end']/history_size)*100:.1f}% ## Chunk Content ``` {chunk['text']} ``` --- *Generated by Chat History Summarizer MCP Server - Large Chat Handler* """ with open(chunk_filepath, 'w', encoding='utf-8') as f: f.write(chunk_content) chunk_files.append(chunk_filename) chunk_summaries.append(f"Chunk {chunk['number']}: {chunk['size']:,} chars ({chunk['start']:,}-{chunk['end']:,})") # Create master summary file master_filename = f"chat_master_{timestamp}_{clean_title}.md" master_filepath = NOTES_DIR / master_filename master_content = f"""# Master Summary: {title or 'Large Chat'} **Date:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} **Total Size:** {history_size:,} characters ({history_size/(1024*1024):.2f} MB) **Chunks Created:** {len(chunks)} **Chunk Size:** {chunk_size:,} characters **Overlap:** {overlap:,} characters ## Overview This large conversation was automatically split into {len(chunks)} chunks for better handling and processing. ## Chunk Breakdown {chr(10).join(f"- **{summary}**" for summary in chunk_summaries)} ## Chunk Files Created {chr(10).join(f"- `{filename}`" for filename in chunk_files)} ## Usage Instructions 1. **Read individual chunks** for detailed content 2. **Search across chunks** to find specific topics 3. **Use chunk summaries** for quick reference 4. **Combine insights** from multiple chunks as needed ## Master Summary > **Note:** For extremely large conversations, consider reading individual chunks for complete context. > This master file provides an overview of the conversation structure. --- *Generated by Chat History Summarizer MCP Server - Large Chat Handler* """ with open(master_filepath, 'w', encoding='utf-8') as f: f.write(master_content) return f"""Large chat processed successfully! **Master Summary:** {master_filepath} **Total Size:** {history_size:,} characters ({history_size/(1024*1024):.2f} MB) **Chunks Created:** {len(chunks)} **Files Created:** - Master: {master_filename} {chr(10).join(f"- Chunk {i+1}: {filename}" for i, filename in enumerate(chunk_files))} All chunks preserve the complete original conversation with overlapping context for continuity.""" except Exception as e: return f"Error processing large chat: {str(e)}" @mcp.tool() def list_summaries(limit: int = 10) -> str: """ List recent chat summaries from the notes directory. Args: limit: Maximum number of summaries to list (default: 10) Returns: List of recent summary files with their creation dates """ try: if not ensure_notes_directory(): return "Error: Could not access notes directory" # Find all chat summary files summary_files = list(NOTES_DIR.glob("chat_summary_*.md")) if not summary_files: return "No chat summaries found in the notes directory." # Sort by modification time (newest first) summary_files.sort(key=lambda x: x.stat().st_mtime, reverse=True) # Limit results summary_files = summary_files[:limit] result = f"Recent Chat Summaries (showing {len(summary_files)} of {len(list(NOTES_DIR.glob('chat_summary_*.md')))} total):\n\n" for file in summary_files: # Get file stats stat = file.stat() created = datetime.fromtimestamp(stat.st_ctime).strftime("%Y-%m-%d %H:%M") size_kb = round(stat.st_size / 1024, 1) # Try to extract title from filename name_parts = file.stem.replace("chat_summary_", "").split("_", 2) display_name = name_parts[2] if len(name_parts) > 2 else "Untitled" display_name = display_name.replace("_", " ") result += f"**{display_name}**\n" result += f" {created} | {size_kb} KB | {file.name}\n\n" return result except Exception as e: return f"Error listing summaries: {str(e)}" @mcp.tool() def delete_summary(filename: str) -> str: """ Delete a chat summary file. Args: filename: Name of the summary file to delete Returns: Confirmation message """ try: if not ensure_notes_directory(): return "Error: Could not access notes directory" filepath = NOTES_DIR / filename if not filepath.exists(): return f"File not found: {filename}" if not filepath.name.startswith("chat_summary_"): return f"Can only delete chat summary files. File must start with 'chat_summary_'" filepath.unlink() return f"Successfully deleted: {filename}" except Exception as e: return f"Error deleting summary: {str(e)}" @mcp.resource("summary://{filename}") def get_summary_content(filename: str) -> str: """ Get the content of a specific chat summary file. Args: filename: Name of the summary file Returns: Content of the summary file """ try: if not ensure_notes_directory(): return "Error: Could not access notes directory" filepath = NOTES_DIR / filename if not filepath.exists(): return f"File not found: {filename}" with open(filepath, 'r', encoding='utf-8') as f: return f.read() except Exception as e: return f"Error reading summary: {str(e)}" @mcp.resource("notes://directory") def get_notes_directory_info() -> str: """Get information about the notes directory""" try: if not ensure_notes_directory(): return "Error: Could not access notes directory" total_files = len(list(NOTES_DIR.glob("*.md"))) summary_files = len(list(NOTES_DIR.glob("chat_summary_*.md"))) other_files = total_files - summary_files info = f"""# Notes Directory Information **Path:** {NOTES_DIR} **Total Markdown Files:** {total_files} **Chat Summaries:** {summary_files} **Other Files:** {other_files} **Directory Size:** {sum(f.stat().st_size for f in NOTES_DIR.rglob('*') if f.is_file()) / 1024:.1f} KB ## Recent Activity Last modified: {datetime.fromtimestamp(max((f.stat().st_mtime for f in NOTES_DIR.rglob('*') if f.is_file()), default=0)).strftime("%Y-%m-%d %H:%M:%S") if any(NOTES_DIR.rglob('*')) else 'No files'} """ return info except Exception as e: return f"Error getting directory info: {str(e)}" @mcp.prompt() def create_summary_prompt( conversation_type: str = "general", focus_area: str = "all" ) -> str: """ Generate a prompt for creating chat summaries. Args: conversation_type: Type of conversation (general, technical, meeting, brainstorm) focus_area: What to focus on (all, decisions, action_items, insights) Returns: A customized prompt for summarizing the conversation """ base_prompts = { "general": "Please summarize this general conversation, highlighting the main topics discussed and key points made.", "technical": "Please summarize this technical discussion, focusing on solutions proposed, technologies mentioned, and implementation details.", "meeting": "Please summarize this meeting, emphasizing decisions made, action items assigned, and next steps.", "brainstorm": "Please summarize this brainstorming session, capturing the ideas generated, creative solutions proposed, and potential directions explored." } focus_additions = { "decisions": " Pay special attention to any decisions that were made and their rationale.", "action_items": " Highlight any action items, tasks, or follow-up work that was identified.", "insights": " Focus on key insights, learnings, and important realizations that emerged.", "all": " Include all important aspects: decisions, action items, insights, and key discussion points." } base_prompt = base_prompts.get(conversation_type, base_prompts["general"]) focus_addition = focus_additions.get(focus_area, focus_additions["all"]) return base_prompt + focus_addition + " Structure the summary clearly with appropriate headings and bullet points where helpful." def main(): """Run the FastMCP server""" import sys # Set UTF-8 encoding for stdout to handle emojis on Windows if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8') try: print("🚀 Starting IDE Chat Summarizer MCP Server...") print(f"📁 Notes directory: {NOTES_DIR}") except UnicodeEncodeError: # Fallback without emojis for systems that can't handle them print("Starting IDE Chat Summarizer MCP Server...") print(f"Notes directory: {NOTES_DIR}") # Run the FastMCP server mcp.run() if __name__ == "__main__": main()

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/RalphLi213/ide-chat-summarizer-mcp'

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