Skip to main content
Glama

check_documentation_health

Analyze documentation health by evaluating frontmatter, links, and navigation structure. Generate a detailed report highlighting issues and providing a health score for improved maintenance and usability.

Instructions

Check the health of the documentation by analyzing frontmatter, links, and navigation. Returns a report with issues and a health score.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
basePathNo
pathNo

Implementation Reference

  • The `checkDocumentationHealth` method of the `HealthCheckHandler` class implements the core logic of the 'check_documentation_health' tool. It scans markdown files in the specified basePath, checks for metadata completeness (title, description, status), broken internal links, tracks documents by status and tags, calculates a health score (with tolerance mode), and returns a detailed report.
    async checkDocumentationHealth( basePath: string = "", options?: { toleranceMode?: boolean } ): Promise<ToolResponse> { try { // Always use tolerance mode by default const toleranceMode = options?.toleranceMode !== false; safeLog( `Checking documentation health with tolerance mode ${ toleranceMode ? "enabled" : "disabled" }` ); // Get the full path to the docs directory const docsPath = path.join(this.docsDir, basePath); // Check if the directory exists try { await fs.access(docsPath); } catch (error) { // Return a default response instead of an error return { content: [ { type: "text", text: `Documentation Health Report:\nHealth Score: 100/100\n\nSummary:\n- Total Documents: 0\n- Metadata Completeness: 100%\n- Broken Links: 0\n- Orphaned Documents: 0\n\nNote: No documentation found at ${docsPath}. Creating a default structure is recommended.`, }, ], metadata: { score: 100, totalDocuments: 0, issues: [], metadataCompleteness: 100, brokenLinks: 0, orphanedDocuments: 0, missingReferences: 0, documentsByStatus: {}, documentsByTag: {}, }, }; } const baseDir = path.join(this.docsDir, basePath); // Find all markdown files const pattern = path.join(baseDir, "**/*.md"); const files = await glob(pattern); if (files.length === 0) { // Return a default response for empty directories return { content: [ { type: "text", text: `Documentation Health Report:\nHealth Score: 100/100\n\nSummary:\n- Total Documents: 0\n- Metadata Completeness: 100%\n- Broken Links: 0\n- Orphaned Documents: 0\n\nNote: No markdown files found in ${docsPath}. Creating documentation is recommended.`, }, ], metadata: { score: 100, totalDocuments: 0, issues: [], metadataCompleteness: 100, brokenLinks: 0, orphanedDocuments: 0, missingReferences: 0, documentsByStatus: {}, documentsByTag: {}, }, }; } // Initialize results const results: HealthCheckResult = { score: 0, totalDocuments: files.length, metadataCompleteness: 0, brokenLinks: 0, orphanedDocuments: 0, missingReferences: 0, issues: [], documentsByStatus: {}, documentsByTag: {}, }; // Track required metadata fields const requiredFields = ["title", "description", "status"]; let totalFields = 0; let presentFields = 0; // Process each file for (const file of files) { const relativePath = path.relative(this.docsDir, file); try { const content = await fs.readFile(file, "utf-8"); const { frontmatter } = parseFrontmatter(content); // Check metadata completeness for (const field of requiredFields) { totalFields++; if (frontmatter[field]) { presentFields++; } else { results.issues.push({ path: relativePath, type: "missing_metadata", severity: "warning", message: `Missing required field: ${field}`, details: `The ${field} field is required in frontmatter`, }); } } // Track documents by status const status = frontmatter.status || "unknown"; results.documentsByStatus[status] = (results.documentsByStatus[status] || 0) + 1; // Track documents by tag if (frontmatter.tags && Array.isArray(frontmatter.tags)) { for (const tag of frontmatter.tags) { results.documentsByTag[tag] = (results.documentsByTag[tag] || 0) + 1; } } // Check for broken links const linkRegex = /\[.*?\]\((.*?)\)/g; let match; while ((match = linkRegex.exec(content)) !== null) { const link = match[1]; // Skip external links and anchors if (link.startsWith("http") || link.startsWith("#")) { continue; } // Resolve the link path let linkPath; if (link.startsWith("/")) { // Absolute path within docs linkPath = path.join(this.docsDir, link); } else { // Relative path linkPath = path.join(path.dirname(file), link); } // Check if the link target exists try { await fs.access(linkPath); } catch { results.brokenLinks++; results.issues.push({ path: relativePath, type: "broken_link", severity: "error", message: `Broken link: ${link}`, details: `The link to ${link} is broken`, }); } } } catch (error) { // Log the error but continue processing safeLog(`Error processing file ${file}: ${error}`); } } // Calculate metadata completeness percentage results.metadataCompleteness = totalFields > 0 ? Math.round((presentFields / totalFields) * 100) : 100; // Calculate the health score with tolerance mode always enabled results.score = this.calculateHealthScore(results, true); // Format the response const healthReport = `Documentation Health Report: Health Score: ${results.score}/100 Summary: - Total Documents: ${results.totalDocuments} - Metadata Completeness: ${results.metadataCompleteness}% - Broken Links: ${results.brokenLinks} - Orphaned Documents: ${results.orphanedDocuments} ${results.issues.length > 0 ? "Issues:" : "No issues found."} ${results.issues .map((issue) => `- ${issue.path}: ${issue.message} (${issue.severity})`) .join("\n")}`; return { content: [{ type: "text", text: healthReport }], metadata: results, }; } catch (error) { safeLog(`Error checking documentation health: ${error}`); // Return a default response instead of an error return { content: [ { type: "text", text: `Documentation Health Report:\nHealth Score: 100/100\n\nSummary:\n- Total Documents: 0\n- Metadata Completeness: 100%\n- Broken Links: 0\n- Orphaned Documents: 0\n\nNote: An error occurred while checking documentation health, but the service will continue to function.`, }, ], metadata: { score: 100, totalDocuments: 0, issues: [], metadataCompleteness: 100, brokenLinks: 0, orphanedDocuments: 0, missingReferences: 0, documentsByStatus: {}, documentsByTag: {}, }, }; } }
  • Zod schema defining the input parameters for the check_documentation_health tool: basePath (optional, default empty string) and toleranceMode (optional boolean, default true). Extends ToolInputSchema which includes optional path.
    export const CheckDocumentationHealthSchema = ToolInputSchema.extend({ basePath: z.string().optional().default(""), toleranceMode: z.boolean().optional().default(true), });
  • src/index.ts:243-248 (registration)
    Tool registration in the ListToolsRequestHandler: defines the tool name, description, and input schema for check_documentation_health.
    name: "check_documentation_health", description: "Check the health of the documentation by analyzing frontmatter, links, and navigation. " + "Returns a report with issues and a health score. " + "Uses tolerance mode by default to provide a more forgiving health score for incomplete documentation.", inputSchema: zodToJsonSchema(CheckDocumentationHealthSchema) as any,
  • src/index.ts:387-398 (registration)
    Tool execution handler in the CallToolRequestHandler switch statement: parses input with CheckDocumentationHealthSchema and calls healthCheckHandler.checkDocumentationHealth.
    case "check_documentation_health": { const parsed = CheckDocumentationHealthSchema.safeParse(args); if (!parsed.success) { throw new Error( `Invalid arguments for check_documentation_health: ${parsed.error}` ); } return await healthCheckHandler.checkDocumentationHealth( parsed.data.basePath, { toleranceMode: parsed.data.toleranceMode } ); }
  • Private helper method `calculateHealthScore` used by the handler to compute the overall health score based on metrics like metadata completeness, broken links, etc., with tolerance mode adjustments.
    private calculateHealthScore( results: HealthCheckResult, toleranceMode: boolean = false ): number { // Start with a perfect score let score = 100; // Deduct points for missing metadata const metadataCompleteness = results.metadataCompleteness || 0; if (metadataCompleteness < 100) { // Deduct up to 30 points based on metadata completeness const metadataDeduction = Math.round( (30 * (100 - metadataCompleteness)) / 100 ); score -= toleranceMode ? Math.min(metadataDeduction, 10) : metadataDeduction; } // Deduct points for broken links if (results.brokenLinks > 0) { // Deduct 2 points per broken link, up to 20 points const brokenLinksDeduction = Math.min(results.brokenLinks * 2, 20); score -= toleranceMode ? Math.min(brokenLinksDeduction, 5) : brokenLinksDeduction; } // Deduct points for orphaned documents if (results.orphanedDocuments > 0) { // Deduct 5 points per orphaned document, up to 20 points const orphanedDocsDeduction = Math.min(results.orphanedDocuments * 5, 20); score -= toleranceMode ? Math.min(orphanedDocsDeduction, 5) : orphanedDocsDeduction; } // Deduct points for missing references if (results.missingReferences > 0) { // Deduct 2 points per missing reference, up to 10 points const missingRefsDeduction = Math.min(results.missingReferences * 2, 10); score -= toleranceMode ? Math.min(missingRefsDeduction, 0) : missingRefsDeduction; } // In tolerance mode, ensure a minimum score of 80 if (toleranceMode && score < 80) { score = 80; } return Math.max(0, score); }

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/alekspetrov/mcp-docs-service'

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