Skip to main content
Glama
jakedx6
by jakedx6
documents.js46.4 kB
import { supabaseService } from '../lib/api-client.js'; import { requireAuth } from '../lib/auth.js'; import { logger } from '../lib/logger.js'; import { z } from 'zod'; // Input schemas for document tools const ListDocumentsSchema = z.object({ project_id: z.string().uuid().optional(), document_type: z.enum(['requirement', 'design', 'technical', 'meeting_notes', 'other']).optional(), search: z.string().optional(), limit: z.number().int().positive().max(100).default(20) }); const GetDocumentSchema = z.object({ document_id: z.string().uuid() }); const CreateDocumentSchema = z.object({ project_id: z.string().uuid().optional(), title: z.string().min(1).max(500), content: z.string(), document_type: z.enum(['requirement', 'design', 'technical', 'meeting_notes', 'other']), metadata: z.record(z.any()).optional() }); const UpdateDocumentSchema = z.object({ document_id: z.string().uuid(), title: z.string().min(1).max(500).optional(), content: z.string().optional(), document_type: z.enum(['requirement', 'design', 'technical', 'meeting_notes', 'other']).optional(), metadata: z.record(z.any()).optional() }); const SearchDocumentsSchema = z.object({ query: z.string().min(1), project_id: z.string().uuid().optional(), document_types: z.array(z.string()).optional(), limit: z.number().int().positive().max(50).default(20), include_content: z.boolean().default(false) }); /** * List documents with filtering */ export const listDocumentsTool = { name: 'list_documents', description: 'List documents with optional filtering by project or document type', inputSchema: { type: 'object', properties: { project_id: { type: 'string', format: 'uuid', description: 'Filter documents by project ID' }, document_type: { type: 'string', enum: ['requirement', 'design', 'technical', 'meeting_notes', 'other'], description: 'Filter documents by type' }, search: { type: 'string', description: 'Search documents by title or content' }, limit: { type: 'number', minimum: 1, maximum: 100, default: 20, description: 'Maximum number of documents to return' } } } }; export const listDocuments = requireAuth(async (args) => { const { project_id, document_type, search, limit } = ListDocumentsSchema.parse(args); logger.info('Listing documents', { project_id, document_type, search, limit }); const documents = await supabaseService.getDocuments({ project_id, type: document_type, search }, { limit }, { field: 'updated_at', order: 'desc' }); // Add document analytics const documentAnalytics = { total_documents: documents.length, document_types: documents.reduce((acc, doc) => { acc[doc.document_type] = (acc[doc.document_type] || 0) + 1; return acc; }, {}), ai_ready_count: 0, // Metadata not available in current schema average_content_length: documents.reduce((sum, doc) => sum + doc.content.length, 0) / (documents.length || 1) }; return { documents, analytics: documentAnalytics, filters_applied: { project_id, document_type, search } }; }); /** * Create new document with markdown support */ export const createDocumentTool = { name: 'create_document', description: 'Create a new document with markdown content and optional frontmatter metadata', inputSchema: { type: 'object', properties: { project_id: { type: 'string', format: 'uuid', description: 'Project ID to associate the document with (required)' }, title: { type: 'string', minLength: 1, maxLength: 500, description: 'The title of the document' }, content: { type: 'string', description: 'The markdown content of the document (can include YAML frontmatter)' }, document_type: { type: 'string', enum: ['requirement', 'design', 'technical', 'meeting_notes', 'other'], description: 'The type of document being created' }, metadata: { type: 'object', description: 'Additional metadata for the document' } }, required: ['title', 'content', 'document_type', 'project_id'] } }; export const createDocument = requireAuth(async (args) => { const documentData = CreateDocumentSchema.parse(args); logger.info('Creating new document', { project_id: documentData.project_id, title: documentData.title, document_type: documentData.document_type }); // Parse frontmatter and analyze content const contentAnalysis = analyzeDocumentContentHelper(documentData.content, documentData.document_type); // Validate that project_id is provided if (!documentData.project_id) { throw new Error('project_id is required for document creation'); } const document = await supabaseService.createDocument({ project_id: documentData.project_id, title: documentData.title, content: documentData.content, document_type: documentData.document_type // Removed format and metadata as they don't exist in the database schema }); logger.info('Document created successfully', { document_id: document.id, title: document.title }); return { document, content_analysis: contentAnalysis, message: `Document "${document.title}" created successfully` }; }); /** * Update existing document */ export const updateDocumentTool = { name: 'update_document', description: 'Update an existing document with new content or metadata', inputSchema: { type: 'object', properties: { document_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the document to update' }, title: { type: 'string', minLength: 1, maxLength: 500, description: 'New title for the document' }, content: { type: 'string', description: 'New markdown content for the document' }, document_type: { type: 'string', enum: ['requirement', 'design', 'technical', 'meeting_notes', 'other'], description: 'New document type' }, metadata: { type: 'object', description: 'Updated metadata for the document' } }, required: ['document_id'] } }; export const updateDocument = requireAuth(async (args) => { const { document_id, ...updates } = UpdateDocumentSchema.parse(args); logger.info('Updating document', { document_id, updates: Object.keys(updates) }); // Analyze content if it's being updated let contentAnalysis = undefined; if (updates.content) { const currentDoc = await supabaseService.getDocument(document_id); contentAnalysis = analyzeDocumentContentHelper(updates.content, updates.document_type || currentDoc.document_type); // Removed metadata updates as metadata doesn't exist in the database schema } const document = await supabaseService.updateDocument(document_id, updates); logger.info('Document updated successfully', { document_id: document.id }); return { document, content_analysis: contentAnalysis, message: `Document "${document.title}" updated successfully` }; }); /** * Search documents with advanced filtering */ export const searchDocumentsTool = { name: 'search_documents', description: 'Search documents by content with advanced filtering and ranking', inputSchema: { type: 'object', properties: { query: { type: 'string', minLength: 1, description: 'Search query to find in document titles and content' }, project_id: { type: 'string', format: 'uuid', description: 'Limit search to specific project' }, document_types: { type: 'array', items: { type: 'string', enum: ['requirement', 'design', 'technical', 'meeting_notes', 'other'] }, description: 'Filter by document types' }, limit: { type: 'number', minimum: 1, maximum: 50, default: 20, description: 'Maximum number of results to return' }, include_content: { type: 'boolean', default: false, description: 'Whether to include full document content in results' } }, required: ['query'] } }; export const searchDocuments = requireAuth(async (args) => { const { query, project_id, document_types, limit, include_content } = SearchDocumentsSchema.parse(args); logger.info('Searching documents', { query, project_id, document_types, limit }); // Get all matching documents const allDocuments = await supabaseService.getDocuments({ project_id, search: query }, { limit: limit * 2 }, // Get more for better ranking { field: 'updated_at', order: 'desc' }); // Filter by document types if specified let filteredDocuments = allDocuments; if (document_types && document_types.length > 0) { filteredDocuments = allDocuments.filter(doc => document_types.includes(doc.document_type)); } // Rank results by relevance const rankedResults = rankSearchResults(filteredDocuments, query, include_content); // Limit results const finalResults = rankedResults.slice(0, limit); return { results: finalResults, total_found: rankedResults.length, search_metadata: { query, filters: { project_id, document_types }, ranking_factors: ['title_match', 'content_relevance', 'document_freshness', 'ai_readiness'] } }; }); /** * Get document with AI context */ export const getDocumentContextTool = { name: 'get_document_context', description: 'Get document with full context including links, references, and AI metadata', inputSchema: { type: 'object', properties: { document_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the document' } }, required: ['document_id'] } }; export const getDocumentContext = requireAuth(async (args) => { const { document_id } = GetDocumentSchema.parse(args); logger.info('Getting document context', { document_id }); const document = await supabaseService.getDocument(document_id); // Analyze document content and extract metadata const contentAnalysis = analyzeDocumentContentHelper(document.content, document.document_type); const linkAnalysis = extractDocumentLinks(document.content); const aiContext = extractAIContext({}); // Find related documents const relatedDocs = await findRelatedDocuments(document); return { document, content_analysis: contentAnalysis, link_analysis: linkAnalysis, ai_context: aiContext, related_documents: relatedDocs, recommendations: generateDocumentRecommendations(document, contentAnalysis) }; }); // Helper functions for document analysis function analyzeDocumentContentHelper(content, documentType) { // Parse frontmatter if present const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); const hasFrontmatter = !!frontmatterMatch; // Extract plain content (without frontmatter) const plainContent = hasFrontmatter ? content.replace(/^---\n[\s\S]*?\n---\n/, '') : content; // Basic content analysis const words = plainContent.split(/\s+/).filter(w => w.length > 0); const lines = plainContent.split('\n'); const headings = (plainContent.match(/^#+\s+.+$/gm) || []).length; const codeBlocks = (plainContent.match(/```[\s\S]*?```/g) || []).length; const links = (plainContent.match(/\[([^\]]+)\]\([^)]+\)/g) || []).length; const internalLinks = (plainContent.match(/\[\[([^\]]+)\]\]/g) || []).length; return { word_count: words.length, line_count: lines.length, character_count: plainContent.length, heading_count: headings, code_block_count: codeBlocks, link_count: links, internal_link_count: internalLinks, has_frontmatter: hasFrontmatter, estimated_read_time: Math.ceil(words.length / 200), // 200 words per minute content_complexity: calculateContentComplexity(plainContent, documentType), ai_readiness_score: calculateAIReadinessScore(content, hasFrontmatter) }; } function extractDocumentLinks(content) { // Extract all types of links const externalLinks = (content.match(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g) || []) .map(match => { const [, text, url] = match.match(/\[([^\]]+)\]\(([^)]+)\)/) || []; return { text, url, type: 'external' }; }); const internalLinks = (content.match(/\[\[([^\]]+)\]\]/g) || []) .map(match => { const text = match.replace(/\[\[|\]\]/g, ''); return { text, type: 'internal' }; }); const anchorLinks = (content.match(/\[([^\]]+)\]\(#([^)]+)\)/g) || []) .map(match => { const [, text, anchor] = match.match(/\[([^\]]+)\]\(#([^)]+)\)/) || []; return { text, anchor, type: 'anchor' }; }); return { external_links: externalLinks, internal_links: internalLinks, anchor_links: anchorLinks, total_links: externalLinks.length + internalLinks.length + anchorLinks.length, link_health: 'unknown' // Would be calculated by checking link validity }; } function extractAIContext(metadata) { // Since metadata doesn't exist in the database schema, return default values return { has_ai_instructions: false, has_ai_context: false, ai_capabilities: [], ai_restrictions: [], validation_status: { isValid: false, issues: [], suggestions: [] } }; } async function findRelatedDocuments(document) { // Simple related document finding based on project and type if (!document.project_id) return []; try { const relatedDocs = await supabaseService.getDocuments({ project_id: document.project_id }, { limit: 5 }); return relatedDocs .filter(doc => doc.id !== document.id) .slice(0, 3); // Return top 3 related docs } catch (error) { logger.error('Error finding related documents:', error); return []; } } function calculateContentComplexity(content, documentType) { let complexity = 0; // Factor in length if (content.length > 5000) complexity += 2; else if (content.length > 2000) complexity += 1; // Factor in structure const headings = (content.match(/^#+\s+.+$/gm) || []).length; if (headings > 10) complexity += 2; else if (headings > 5) complexity += 1; // Factor in code blocks const codeBlocks = (content.match(/```[\s\S]*?```/g) || []).length; if (codeBlocks > 5) complexity += 2; else if (codeBlocks > 2) complexity += 1; // Factor in document type if (['technical_spec', 'api_docs'].includes(documentType)) complexity += 1; if (complexity >= 4) return 'high'; if (complexity >= 2) return 'medium'; return 'low'; } function calculateAIReadinessScore(content, hasFrontmatter) { let score = 0; // Frontmatter presence if (hasFrontmatter) score += 30; // Structure quality const headings = (content.match(/^#+\s+.+$/gm) || []).length; if (headings > 0) score += 20; // Content length (not too short, not too long) const words = content.split(/\s+/).length; if (words > 100 && words < 5000) score += 20; // Code examples if (content.includes('```')) score += 15; // Links and references if (content.includes('[') && content.includes('](')) score += 15; return Math.min(100, score); } function rankSearchResults(documents, query, includeContent) { const queryLower = query.toLowerCase(); return documents .map(doc => { let score = 0; // Title match (highest weight) if (doc.title.toLowerCase().includes(queryLower)) { score += 50; if (doc.title.toLowerCase().startsWith(queryLower)) score += 25; } // Content relevance const contentLower = doc.content.toLowerCase(); const queryMatches = (contentLower.match(new RegExp(queryLower, 'g')) || []).length; score += Math.min(30, queryMatches * 5); // Document freshness const daysSinceUpdate = (Date.now() - new Date(doc.updated_at).getTime()) / (1000 * 60 * 60 * 24); if (daysSinceUpdate < 7) score += 10; else if (daysSinceUpdate < 30) score += 5; // AI readiness // Removed metadata check as it doesn't exist in the database schema // Document type relevance if (['readme', 'api_docs'].includes(doc.document_type)) score += 10; return { ...doc, content: includeContent ? doc.content : undefined, search_score: score, query_matches: queryMatches }; }) .sort((a, b) => b.search_score - a.search_score); } function generateDocumentRecommendations(document, analysis) { const recommendations = []; if (!analysis.has_frontmatter) { recommendations.push('Add YAML frontmatter with metadata to improve AI integration'); } if (analysis.word_count < 100) { recommendations.push('Document seems too brief - consider adding more detailed content'); } if (analysis.heading_count === 0) { recommendations.push('Add headings to improve document structure and readability'); } if (analysis.internal_link_count === 0 && document.project_id) { recommendations.push('Consider adding links to related project documents'); } if (analysis.ai_readiness_score < 60) { recommendations.push('Improve AI readiness by adding structured metadata and clear sections'); } return recommendations; } /** * Get document by ID */ export const getDocumentTool = { name: 'get_document', description: 'Get a document by ID with basic information', inputSchema: { type: 'object', properties: { document_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the document' } }, required: ['document_id'] } }; export const getDocument = requireAuth(async (args) => { const { document_id } = GetDocumentSchema.parse(args); logger.info('Getting document', { document_id }); const document = await supabaseService.getDocument(document_id); return { document, message: `Document "${document.title}" retrieved successfully` }; }); /** * Add document collaborator */ export const addDocumentCollaboratorTool = { name: 'add_document_collaborator', description: 'Add a collaborator to a document with specific permissions', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'ID of the document' }, user_id: { type: 'string', description: 'ID of the user to add as collaborator' }, permission_level: { type: 'string', enum: ['read', 'comment', 'edit', 'admin'], default: 'edit', description: 'Permission level for the collaborator' }, notify: { type: 'boolean', default: true, description: 'Whether to notify the user about collaboration invite' } }, required: ['document_id', 'user_id'] } }; const AddDocumentCollaboratorSchema = z.object({ document_id: z.string().min(1), user_id: z.string().min(1), permission_level: z.enum(['read', 'comment', 'edit', 'admin']).default('edit'), notify: z.boolean().default(true) }); export const addDocumentCollaborator = requireAuth(async (args) => { const { document_id, user_id, permission_level, notify } = AddDocumentCollaboratorSchema.parse(args); logger.info('Adding document collaborator', { document_id, user_id, permission_level }); // Get document and verify permissions const document = await supabaseService.getDocument(document_id); if (!document) { throw new Error('Document not found'); } // Create collaboration record const collaboration = await supabaseService.createDocumentCollaboration({ document_id, user_id, permission_level, status: 'active', invited_at: new Date().toISOString() }); // Note: metadata field doesn't exist in the database schema // Collaboration would be tracked in a separate table return { collaboration, document: await supabaseService.getDocument(document_id), message: `User added as ${permission_level} collaborator` }; }); /** * Analyze document content */ export const analyzeDocumentContentTool = { name: 'analyze_document_content', description: 'Perform advanced analysis on document content', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'ID of the document to analyze' }, analysis_types: { type: 'array', items: { type: 'string', enum: ['readability', 'completeness', 'ai_optimization', 'link_analysis', 'structure_analysis'] }, default: ['readability', 'completeness'], description: 'Types of analysis to perform' }, include_suggestions: { type: 'boolean', default: true, description: 'Whether to include improvement suggestions' } }, required: ['document_id'] } }; const AnalyzeDocumentContentSchema = z.object({ document_id: z.string().min(1), analysis_types: z.array(z.enum(['readability', 'completeness', 'ai_optimization', 'link_analysis', 'structure_analysis'])).default(['readability', 'completeness']), include_suggestions: z.boolean().default(true) }); export const analyzeDocumentContent = requireAuth(async (args) => { const { document_id, analysis_types, include_suggestions } = AnalyzeDocumentContentSchema.parse(args); logger.info('Analyzing document content', { document_id, analysis_types }); const document = await supabaseService.getDocument(document_id); if (!document) { throw new Error('Document not found'); } const analysis = { document_id, title: document.title, analyzed_at: new Date().toISOString(), results: {} }; // Perform requested analyses for (const analysisType of analysis_types) { switch (analysisType) { case 'readability': analysis.results.readability = analyzeReadability(document.content); break; case 'completeness': analysis.results.completeness = analyzeCompleteness(document); break; case 'ai_optimization': analysis.results.ai_optimization = analyzeAIOptimization(document); break; case 'link_analysis': analysis.results.link_analysis = analyzeLinkStructure(document.content); break; case 'structure_analysis': analysis.results.structure_analysis = analyzeDocumentStructure(document.content); break; } } // Generate improvement suggestions if (include_suggestions) { analysis.suggestions = generateImprovementSuggestions(analysis.results, document); } // Calculate overall score analysis.overall_score = calculateOverallDocumentScore(analysis.results); return analysis; }); /** * Get document collaboration history */ export const getDocumentCollaborationTool = { name: 'get_document_collaboration', description: 'Get collaboration history and current collaborators for a document', inputSchema: { type: 'object', properties: { document_id: { type: 'string', description: 'ID of the document' }, include_activity: { type: 'boolean', default: true, description: 'Whether to include recent activity' }, time_range: { type: 'string', enum: ['day', 'week', 'month', 'all'], default: 'week', description: 'Time range for activity' } }, required: ['document_id'] } }; const GetDocumentCollaborationSchema = z.object({ document_id: z.string().min(1), include_activity: z.boolean().default(true), time_range: z.enum(['day', 'week', 'month', 'all']).default('week') }); export const getDocumentCollaboration = requireAuth(async (args) => { const { document_id, include_activity, time_range } = GetDocumentCollaborationSchema.parse(args); logger.info('Getting document collaboration', { document_id, time_range }); const document = await supabaseService.getDocument(document_id); if (!document) { throw new Error('Document not found'); } // Get collaborators const collaborations = await supabaseService.getDocumentCollaborations(document_id); // Get activity if requested let activity = []; if (include_activity) { activity = await getDocumentActivity(document_id, time_range); } // Analyze collaboration patterns const collaborationStats = analyzeCollaborationPatterns(collaborations, activity); return { document: { id: document.id, title: document.title, created_by: document.created_by }, collaborators: collaborations.map(c => ({ user_id: c.user_id, permission_level: c.permission_level, status: c.status, joined_at: c.invited_at, last_activity: activity.filter(a => a.user_id === c.user_id)[0]?.timestamp })), activity: activity.slice(0, 50), // Limit to 50 recent activities statistics: collaborationStats }; }); /** * Generate document templates */ export const generateDocumentTemplateTool = { name: 'generate_document_template', description: 'Generate a document template based on type and requirements', inputSchema: { type: 'object', properties: { template_type: { type: 'string', enum: ['readme', 'api_doc', 'meeting_notes', 'technical_spec', 'user_guide', 'project_proposal'], description: 'Type of template to generate' }, project_context: { type: 'object', properties: { name: { type: 'string' }, description: { type: 'string' }, tech_stack: { type: 'array', items: { type: 'string' } }, team_size: { type: 'number' } }, description: 'Project context for template customization' }, ai_optimized: { type: 'boolean', default: true, description: 'Whether to include AI-optimization features' }, include_examples: { type: 'boolean', default: true, description: 'Whether to include example content' } }, required: ['template_type'] } }; const GenerateDocumentTemplateSchema = z.object({ template_type: z.enum(['readme', 'api_doc', 'meeting_notes', 'technical_spec', 'user_guide', 'project_proposal']), project_context: z.object({ name: z.string().optional(), description: z.string().optional(), tech_stack: z.array(z.string()).optional(), team_size: z.number().optional() }).optional(), ai_optimized: z.boolean().default(true), include_examples: z.boolean().default(true) }); export const generateDocumentTemplate = requireAuth(async (args) => { const { template_type, project_context, ai_optimized, include_examples } = GenerateDocumentTemplateSchema.parse(args); logger.info('Generating document template', { template_type, ai_optimized }); const template = generateTemplateContent(template_type, project_context, ai_optimized, include_examples); return { template_type, content: template.content, frontmatter: template.frontmatter, sections: template.sections, ai_instructions: ai_optimized ? template.ai_instructions : undefined, usage_tips: template.usage_tips }; }); /** * Bulk document operations */ export const bulkDocumentOperationsTool = { name: 'bulk_document_operations', description: 'Perform bulk operations on multiple documents', inputSchema: { type: 'object', properties: { document_ids: { type: 'array', items: { type: 'string' }, description: 'Array of document IDs' }, operation: { type: 'string', enum: ['update_metadata', 'add_tags', 'change_visibility', 'archive', 'analyze'], description: 'Operation to perform' }, operation_data: { type: 'object', description: 'Data for the operation' } }, required: ['document_ids', 'operation'] } }; const BulkDocumentOperationsSchema = z.object({ document_ids: z.array(z.string().min(1)).min(1), operation: z.enum(['update_metadata', 'add_tags', 'change_visibility', 'archive', 'analyze']), operation_data: z.record(z.any()).optional() }); export const bulkDocumentOperations = requireAuth(async (args) => { const { document_ids, operation, operation_data } = BulkDocumentOperationsSchema.parse(args); logger.info('Performing bulk document operations', { document_count: document_ids.length, operation }); const results = []; const now = new Date().toISOString(); for (const document_id of document_ids) { try { let result; switch (operation) { case 'update_metadata': // metadata field doesn't exist in the database schema result = await supabaseService.updateDocument(document_id, { updated_at: now }); break; case 'add_tags': const doc = await supabaseService.getDocument(document_id); const existingTags = []; // metadata doesn't exist in the database schema const newTags = operation_data?.tags || []; // metadata field doesn't exist in the database schema result = await supabaseService.updateDocument(document_id, { updated_at: now }); break; case 'change_visibility': // visibility field doesn't exist in the database schema result = await supabaseService.updateDocument(document_id, { updated_at: now }); break; case 'archive': // status and metadata fields don't exist in the database schema result = await supabaseService.updateDocument(document_id, { updated_at: now }); break; case 'analyze': result = await analyzeDocumentContent({ document_id, analysis_types: operation_data?.analysis_types || ['readability', 'completeness'], include_suggestions: true }); break; default: throw new Error(`Unknown operation: ${operation}`); } results.push({ document_id, success: true, result }); } catch (error) { logger.error(`Failed operation ${operation} on document ${document_id}:`, error); results.push({ document_id, success: false, error: error instanceof Error ? error.message : 'Unknown error' }); } } return { operation, summary: { total_documents: document_ids.length, successful_operations: results.filter(r => r.success).length, failed_operations: results.filter(r => !r.success).length }, results }; }); // Helper functions for document analysis function analyzeReadability(content) { const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0); const words = content.split(/\s+/).filter(w => w.length > 0); const avgWordsPerSentence = words.length / sentences.length; // Simple readability score (0-100, higher is more readable) let readabilityScore = 100; if (avgWordsPerSentence > 20) readabilityScore -= 20; if (avgWordsPerSentence > 30) readabilityScore -= 20; const longWords = words.filter(w => w.length > 6).length; const longWordPercentage = (longWords / words.length) * 100; if (longWordPercentage > 30) readabilityScore -= 15; return { score: Math.max(0, readabilityScore), metrics: { total_words: words.length, total_sentences: sentences.length, avg_words_per_sentence: Math.round(avgWordsPerSentence * 10) / 10, long_word_percentage: Math.round(longWordPercentage * 10) / 10 }, level: readabilityScore > 80 ? 'easy' : readabilityScore > 60 ? 'moderate' : 'difficult' }; } function analyzeCompleteness(document) { const content = document.content || ''; const frontmatter = {}; // metadata doesn't exist in the database schema let completenessScore = 0; const missingElements = []; // Check for basic elements if (document.title) completenessScore += 20; else missingElements.push('title'); if (content.length > 100) completenessScore += 20; else missingElements.push('substantial_content'); // frontmatter.description removed as metadata doesn't exist missingElements.push('description'); // Check for structure if (content.includes('#')) completenessScore += 15; else missingElements.push('headings'); if (content.includes('```') || content.includes('`')) completenessScore += 10; else missingElements.push('code_examples'); // Check for links if (content.includes('[') && content.includes('](')) completenessScore += 10; else missingElements.push('links'); // Check for metadata // frontmatter.tags removed as metadata doesn't exist missingElements.push('tags'); return { score: completenessScore, level: completenessScore > 80 ? 'complete' : completenessScore > 60 ? 'good' : 'needs_work', missing_elements: missingElements, recommendations: generateCompletenessRecommendations(missingElements) }; } function analyzeAIOptimization(document) { const content = document.content || ''; const frontmatter = {}; // metadata doesn't exist in the database schema let aiScore = 0; const optimizations = []; // Check for AI-specific frontmatter // frontmatter.ai_instructions removed as metadata doesn't exist optimizations.push('add_ai_instructions'); // frontmatter.ai_context removed as metadata doesn't exist optimizations.push('add_ai_context'); // frontmatter.ai_capabilities removed as metadata doesn't exist optimizations.push('define_ai_capabilities'); // Check for structured content if (content.includes('## ') || content.includes('### ')) aiScore += 20; else optimizations.push('improve_structure'); // Check for examples if (content.includes('Example:') || content.includes('```')) aiScore += 20; else optimizations.push('add_examples'); return { score: aiScore, level: aiScore > 80 ? 'highly_optimized' : aiScore > 60 ? 'optimized' : 'needs_optimization', missing_optimizations: optimizations, ai_readiness: aiScore > 60 ? 'ready' : 'not_ready' }; } function analyzeLinkStructure(content) { const internalLinks = (content.match(/\[\[[^\]]+\]\]/g) || []).length; const externalLinks = (content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || []).length; const brokenLinkPatterns = (content.match(/\[([^\]]*)\]\(\)/g) || []).length; return { internal_links: internalLinks, external_links: externalLinks, total_links: internalLinks + externalLinks, broken_links: brokenLinkPatterns, link_density: (internalLinks + externalLinks) / (content.length / 1000), // links per 1000 chars recommendations: generateLinkRecommendations(internalLinks, externalLinks, brokenLinkPatterns) }; } function analyzeDocumentStructure(content) { const h1Count = (content.match(/^# /gm) || []).length; const h2Count = (content.match(/^## /gm) || []).length; const h3Count = (content.match(/^### /gm) || []).length; const codeBlocks = (content.match(/```[\s\S]*?```/g) || []).length; const inlineCode = (content.match(/`[^`]+`/g) || []).length; const lists = (content.match(/^[-*+] /gm) || []).length; const numberedLists = (content.match(/^\d+\. /gm) || []).length; return { heading_structure: { h1: h1Count, h2: h2Count, h3: h3Count, total: h1Count + h2Count + h3Count }, code_elements: { code_blocks: codeBlocks, inline_code: inlineCode }, lists: { bullet_lists: lists, numbered_lists: numberedLists, total: lists + numberedLists }, structure_score: calculateStructureScore(h1Count, h2Count, h3Count, codeBlocks, lists) }; } function generateImprovementSuggestions(results, document) { const suggestions = []; if (results.readability?.score < 70) { suggestions.push('Improve readability by shortening sentences and using simpler words'); } if (results.completeness?.score < 70) { suggestions.push('Add missing content elements: ' + results.completeness.missing_elements.join(', ')); } if (results.ai_optimization?.score < 60) { suggestions.push('Optimize for AI by adding structured metadata and clear examples'); } if (results.link_analysis?.broken_links > 0) { suggestions.push(`Fix ${results.link_analysis.broken_links} broken link(s)`); } if (results.structure_analysis?.heading_structure.total === 0) { suggestions.push('Add headings to improve document structure'); } return suggestions; } function calculateOverallDocumentScore(results) { const scores = []; if (results.readability) scores.push(results.readability.score); if (results.completeness) scores.push(results.completeness.score); if (results.ai_optimization) scores.push(results.ai_optimization.score); if (results.structure_analysis) scores.push(results.structure_analysis.structure_score); return scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0; } async function getDocumentActivity(documentId, timeRange) { // This would query actual activity logs // For now, return placeholder data return [ { timestamp: new Date().toISOString(), user_id: 'user1', action: 'edited', details: 'Content updated' } ]; } function analyzeCollaborationPatterns(collaborations, activity) { return { total_collaborators: collaborations.length, active_collaborators: collaborations.filter(c => c.status === 'active').length, permission_distribution: { read: collaborations.filter(c => c.permission_level === 'read').length, comment: collaborations.filter(c => c.permission_level === 'comment').length, edit: collaborations.filter(c => c.permission_level === 'edit').length, admin: collaborations.filter(c => c.permission_level === 'admin').length }, recent_activity_count: activity.length, collaboration_health: collaborations.length > 0 && activity.length > 0 ? 'active' : 'low' }; } function generateTemplateContent(type, context, aiOptimized, includeExamples) { const templates = { readme: { content: generateReadmeTemplate(context, includeExamples), frontmatter: { document_type: 'readme', template_version: '1.0', ...(aiOptimized && { ai_instructions: 'This README should help users quickly understand and get started with the project', ai_capabilities: ['generate', 'review', 'translate'] }) }, sections: ['Overview', 'Installation', 'Usage', 'Contributing', 'License'], ai_instructions: aiOptimized ? 'Keep content clear, structured, and actionable' : undefined, usage_tips: ['Update installation steps regularly', 'Include code examples', 'Add contribution guidelines'] } // Add more templates... }; return templates[type] || templates.readme; } function generateReadmeTemplate(context, includeExamples) { const projectName = context?.name || 'Your Project Name'; const description = context?.description || 'A brief description of your project'; return `# ${projectName} ${description} ## Overview Brief overview of what this project does and why it exists. ## Installation \`\`\`bash # Installation steps npm install \`\`\` ## Usage ${includeExamples ? ` \`\`\`javascript // Example usage const example = require('${projectName.toLowerCase()}') example.doSomething() \`\`\` ` : 'Basic usage instructions here.'} ## Contributing 1. Fork the repository 2. Create your feature branch 3. Commit your changes 4. Push to the branch 5. Create a Pull Request ## License MIT License - see LICENSE file for details. `; } function generateCompletenessRecommendations(missingElements) { const recommendations = []; if (missingElements.includes('title')) { recommendations.push('Add a clear, descriptive title'); } if (missingElements.includes('description')) { recommendations.push('Add a description in frontmatter'); } if (missingElements.includes('headings')) { recommendations.push('Structure content with headings (##, ###)'); } if (missingElements.includes('code_examples')) { recommendations.push('Include code examples or snippets'); } return recommendations; } function generateLinkRecommendations(internal, external, broken) { const recommendations = []; if (broken > 0) { recommendations.push(`Fix ${broken} broken link(s)`); } if (internal === 0) { recommendations.push('Add internal links to related documents'); } if (external === 0 && internal < 2) { recommendations.push('Consider adding relevant external references'); } return recommendations; } function calculateStructureScore(h1, h2, h3, codeBlocks, lists) { let score = 0; if (h1 > 0) score += 20; if (h2 > 0) score += 30; if (h3 > 0) score += 20; if (codeBlocks > 0) score += 15; if (lists > 0) score += 15; return Math.min(100, score); } export const documentHandlers = { list_documents: listDocuments, create_document: createDocument, get_document: getDocument, update_document: updateDocument, search_documents: searchDocuments, get_document_context: getDocumentContext, add_document_collaborator: addDocumentCollaborator, analyze_document_content: analyzeDocumentContent, get_document_collaboration: getDocumentCollaboration, generate_document_template: generateDocumentTemplate, bulk_document_operations: bulkDocumentOperations }; // Export all document tools export const documentTools = { listDocumentsTool, createDocumentTool, getDocumentTool, updateDocumentTool, searchDocumentsTool, getDocumentContextTool, addDocumentCollaboratorTool, analyzeDocumentContentTool, getDocumentCollaborationTool, generateDocumentTemplateTool, bulkDocumentOperationsTool };

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/jakedx6/helios9-MCP-Server'

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