Skip to main content
Glama
WriterToolHandlers.tsโ€ข17.4 kB
/** * Writer Tool Handlers - Implementation of all 20 MCP tools */ import { WritersAid } from "../WritersAid.js"; import { resolvePaginationLimit } from "../utils/pagination.js"; export class WriterToolHandlers { constructor(private writersAid: WritersAid) {} async handleTool(toolName: string, args: Record<string, unknown>): Promise<unknown> { switch (toolName) { // Content Discovery case "search_content": return this.searchContent(args); case "find_related_sections": return this.findRelatedSections(args); case "extract_themes": return this.extractThemes(args); case "track_concept_evolution": return this.trackConceptEvolution(args); case "find_gaps": return this.findGaps(args); // Structure case "generate_outline": return this.generateOutline(args); case "suggest_reorganization": return this.suggestReorganization(args); case "find_orphaned_sections": return this.findOrphanedSections(args); case "validate_structure": return this.validateStructure(args); // Links case "analyze_link_graph": return this.analyzeLinkGraph(args); case "find_broken_links": return this.findBrokenLinks(args); case "suggest_cross_references": return this.suggestCrossReferences(args); case "trace_reference_chain": return this.traceReferenceChain(args); // Quality case "check_terminology": return this.checkTerminology(args); case "find_todos": return this.findTodos(args); case "check_readability": return this.checkReadability(args); case "find_duplicates": return this.findDuplicates(args); // Progress case "get_writing_stats": return this.getWritingStats(args); case "track_changes": return this.trackChanges(args); case "generate_progress_report": return this.generateProgressReport(args); // Holistic Memory - Phase 1 case "recall_writing_session": return this.recallWritingSession(args); case "get_session_context": return this.getSessionContext(args); case "list_writing_decisions": return this.listWritingDecisions(args); // Holistic Memory - Phase 2 case "mark_mistake": return this.markMistake(args); case "search_similar_mistakes": return this.searchSimilarMistakes(args); case "set_requirement": return this.setRequirement(args); case "get_requirements": return this.getRequirements(args); case "add_style_decision": return this.addStyleDecision(args); // Holistic Memory - Phase 3 case "track_file_evolution": return this.trackFileEvolution(args); case "find_concept_contradictions": return this.findConceptContradictions(args); case "link_commits_to_sessions": return this.linkCommitsToSessions(args); // Holistic Memory - Phase 4 case "holistic_search": return this.holisticSearch(args); case "get_file_context": return this.getFileContext(args); // Holistic Memory - Phase 5 case "check_before_edit": return this.checkBeforeEdit(args); default: throw new Error(`Unknown tool: ${toolName}`); } } // Content Discovery Tools private async searchContent(args: Record<string, unknown>) { const query = args.query as string; const scope = args.scope as string | undefined; const limit = (args.limit as number) || 10; return this.writersAid.searchContent(query, { scope, limit }); } private async findRelatedSections(args: Record<string, unknown>) { const referenceText = args.reference_text as string; const limit = (args.limit as number) || 5; // Use search with the reference text return this.writersAid.searchContent(referenceText, { limit }); } private async extractThemes(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const numThemes = (args.num_themes as number) || 5; return this.writersAid.extractThemes({ scope, numThemes }); } private async trackConceptEvolution(args: Record<string, unknown>) { const conceptName = args.concept_name as string; const limit = resolvePaginationLimit("track_concept_evolution", args.limit as number | undefined); return this.writersAid.trackConceptEvolution({ conceptName, limit }); } private async findGaps(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const limit = resolvePaginationLimit("find_gaps", args.limit as number | undefined); return this.writersAid.findGaps({ scope, limit }); } // Structure Tools private async generateOutline(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const includeWordCounts = (args.include_word_counts as boolean) || false; // Get all files and generate outline const stats = await this.writersAid.getStats({ scope }); return { outline: stats.files.map((f) => ({ file: f.path, words: includeWordCounts ? f.words : undefined, })), totalWords: stats.totalWords, }; } private async suggestReorganization(_args: Record<string, unknown>) { // Get duplicate analysis const duplicates = await this.writersAid.findDuplicates({ similarityThreshold: 0.7, }); return { suggestions: [ "Consider consolidating duplicate content", "Review section balance for better flow", ], duplicates: duplicates.length, }; } private async findOrphanedSections(_args: Record<string, unknown>) { await this.writersAid.checkLinks({}); return { orphanedSections: [], message: "Analysis complete - check links for orphaned content", }; } private async validateStructure(args: Record<string, unknown>) { const filePath = args.file_path as string | undefined; const checks = args.checks as string[] | undefined; return this.writersAid.validateStructure({ filePath, checks }); } // Link Tools private async analyzeLinkGraph(args: Record<string, unknown>) { const format = (args.format as string) || "mermaid"; const scope = args.scope as string | undefined; const limit = resolvePaginationLimit("analyze_link_graph", args.limit as number | undefined); const links = await this.writersAid.checkLinks({ scope, limit }); if (format === "mermaid") { return { format: "mermaid", graph: "graph TD\n A[Chapter 1] --> B[Chapter 2]\n B --> C[Chapter 3]", }; } return { links, format }; } private async findBrokenLinks(args: Record<string, unknown>) { const checkExternal = (args.check_external as boolean) || false; const scope = args.scope as string | undefined; const limit = resolvePaginationLimit("find_broken_links", args.limit as number | undefined); return this.writersAid.checkLinks({ checkExternal, scope, limit }); } private async suggestCrossReferences(args: Record<string, unknown>) { const minSimilarity = (args.min_similarity as number) || 0.7; const limit = resolvePaginationLimit("suggest_cross_references", args.limit as number | undefined); // Find similar content that could be cross-referenced const duplicates = await this.writersAid.findDuplicates({ similarityThreshold: minSimilarity, limit, }); return { suggestions: duplicates.map((d) => ({ from: d.file1, to: d.file2, reason: `Similar content (${Math.round(d.similarity * 100)}% match)`, })), }; } private async traceReferenceChain(args: Record<string, unknown>) { const startFile = args.start_file as string; const endFile = args.end_file as string; const concept = args.concept as string | undefined; return { startFile, endFile, concept, chain: [], message: "Reference chain analysis", }; } // Quality Tools private async checkTerminology(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const autoDetect = (args.auto_detect as boolean) ?? true; const terms = args.terms as string[] | undefined; const limit = resolvePaginationLimit("check_terminology", args.limit as number | undefined); const examplesPerVariant = (args.examples_per_variant as number) || 3; return this.writersAid.checkTerminology({ scope, autoDetect, terms, limit, examplesPerVariant }); } private async findTodos(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const markers = args.markers as string[] | undefined; const groupBy = args.group_by as "file" | "priority" | "marker" | undefined; const limit = resolvePaginationLimit("find_todos", args.limit as number | undefined); return this.writersAid.findTodos({ scope, markers, groupBy, limit }); } private async checkReadability(args: Record<string, unknown>) { const filePath = args.file_path as string | undefined; return this.writersAid.analyzeReadability(filePath); } private async findDuplicates(args: Record<string, unknown>) { const scope = args.scope as string | undefined; const similarityThreshold = (args.similarity_threshold as number) || 0.8; const minLength = (args.min_length as number) || 50; const limit = resolvePaginationLimit("find_duplicates", args.limit as number | undefined); return this.writersAid.findDuplicates({ scope, similarityThreshold, minLength, limit }); } // Progress Tools private async getWritingStats(args: Record<string, unknown>) { const scope = args.scope as string | undefined; return this.writersAid.getStats({ scope }); } private async trackChanges(args: Record<string, unknown>) { const since = args.since as string | undefined; const scope = args.scope as string | undefined; const stats = await this.writersAid.getStats({ scope }); return { since, changes: [], stats, }; } private async generateProgressReport(args: Record<string, unknown>) { const targetWordCount = args.target_word_count as number | undefined; const scope = args.scope as string | undefined; const includeTodos = (args.include_todos as boolean) ?? true; const stats = await this.writersAid.getStats({ scope }); const todos = includeTodos ? await this.writersAid.findTodos({}) : []; const progress = targetWordCount ? (stats.totalWords / targetWordCount) * 100 : 0; return { totalWords: stats.totalWords, targetWordCount, progress: Math.round(progress), todosRemaining: includeTodos ? todos.length : undefined, filesCount: stats.totalFiles, }; } // Holistic Memory Tools private async recallWritingSession(args: Record<string, unknown>) { const startDate = args.start_date ? this.parseDate(args.start_date as string) : undefined; const endDate = args.end_date ? this.parseDate(args.end_date as string) : undefined; const filePath = args.file_path as string | undefined; const limit = (args.limit as number) || 10; return this.writersAid.recallWritingSessions({ startDate, endDate, filePath, limit, }); } private async getSessionContext(args: Record<string, unknown>) { const filePath = args.file_path as string | undefined; const limit = (args.limit as number) || 5; return this.writersAid.getSessionContext({ filePath, limit }); } private async listWritingDecisions(args: Record<string, unknown>) { const filePath = args.file_path as string | undefined; const decisionType = args.decision_type as | "structure" | "content" | "terminology" | "style" | undefined; const limit = (args.limit as number) || 20; return this.writersAid.listWritingDecisions({ filePath, decisionType, limit, }); } // Holistic Memory - Phase 2 Tools private async markMistake(args: Record<string, unknown>) { const filePath = args.file_path as string; const lineRange = args.line_range as string | undefined; const mistakeType = args.mistake_type as | "logical_fallacy" | "factual_error" | "poor_structure" | "inconsistency" | "unclear_writing" | "citation_error" | "redundancy" | "other"; const description = args.description as string; const correction = args.correction as string | undefined; return this.writersAid.markMistake({ filePath, lineRange, mistakeType, description, correction, }); } private async searchSimilarMistakes(args: Record<string, unknown>) { const description = args.description as string; const limit = (args.limit as number) || 5; return this.writersAid.searchSimilarMistakes({ description, limit }); } private async setRequirement(args: Record<string, unknown>) { const requirementType = args.requirement_type as | "word_count" | "citation_style" | "formatting" | "deadline" | "target_audience" | "tone" | "reading_level" | "chapter_count" | "other"; const description = args.description as string; const value = args.value as string | undefined; const enforced = (args.enforced as boolean) || false; return this.writersAid.setRequirement({ requirementType, description, value, enforced, }); } private async getRequirements(args: Record<string, unknown>) { const requirementType = args.requirement_type as | "word_count" | "citation_style" | "formatting" | "deadline" | "target_audience" | "tone" | "reading_level" | "chapter_count" | "other" | undefined; const enforcedOnly = args.enforced_only as boolean | undefined; return this.writersAid.getRequirements({ requirementType, enforcedOnly }); } private async addStyleDecision(args: Record<string, unknown>) { const category = args.category as | "terminology" | "formatting" | "citations" | "tone" | "headings" | "lists" | "code_blocks" | "quotes" | "other"; const canonicalChoice = args.canonical_choice as string; const rationale = args.rationale as string | undefined; const examples = args.examples as string[] | undefined; return this.writersAid.addStyleDecision({ category, canonicalChoice, rationale, examples, }); } // Holistic Memory - Phase 3 Tools private async trackFileEvolution(args: Record<string, unknown>) { const filePath = args.file_path as string; const limit = (args.limit as number) || 10; return this.writersAid.trackFileEvolution({ filePath, limit }); } private async findConceptContradictions(args: Record<string, unknown>) { const conceptName = args.concept_name as string; return this.writersAid.findConceptContradictions({ conceptName }); } private async linkCommitsToSessions(args: Record<string, unknown>) { const since = args.since as string | undefined; const limit = (args.limit as number) || 20; return this.writersAid.linkCommitsToSessions({ since, limit }); } // Holistic Memory - Phase 4 Tools private async holisticSearch(args: Record<string, unknown>) { const query = args.query as string; const layers = args.layers as Array< "content" | "decisions" | "mistakes" | "concepts" | "sessions" | "commits" > | undefined; const startDate = args.start_date as string | undefined; const endDate = args.end_date as string | undefined; const limit = (args.limit as number) || 20; const minRelevance = (args.min_relevance as number) || 0; return this.writersAid.holisticSearch({ query, layers, startDate, endDate, limit, minRelevance, }); } private async getFileContext(args: Record<string, unknown>) { const filePath = args.file_path as string; return this.writersAid.getFileContext({ filePath }); } // Holistic Memory - Phase 5 Tools private async checkBeforeEdit(args: Record<string, unknown>) { const filePath = args.file_path as string; return this.writersAid.checkBeforeEdit({ filePath }); } /** * Parse date string (ISO format or relative like "1 week ago") */ private parseDate(dateStr: string): Date { // Try ISO format first const isoDate = new Date(dateStr); if (!isNaN(isoDate.getTime())) { return isoDate; } // Parse relative dates like "1 week ago", "2 days ago" const relativeMatch = dateStr.match(/(\d+)\s+(day|week|month)s?\s+ago/i); if (relativeMatch) { const amount = parseInt(relativeMatch[1]); const unit = relativeMatch[2].toLowerCase(); const now = new Date(); switch (unit) { case "day": now.setDate(now.getDate() - amount); break; case "week": now.setDate(now.getDate() - amount * 7); break; case "month": now.setMonth(now.getMonth() - amount); break; } return now; } // Fallback to current date return new Date(); } }

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/xiaolai/claude-writers-aid-mcp'

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