Skip to main content
Glama

check_documentation_health

Analyze documentation health by checking frontmatter, links, and navigation to identify issues and generate a health report.

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
pathNo
basePathNo

Implementation Reference

  • Main handler function that implements the check_documentation_health tool. Scans markdown files, checks frontmatter metadata completeness, broken internal links, calculates health score with tolerance mode.
      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 input parameters for the tool: basePath (optional) and toleranceMode (optional boolean).
    export const CheckDocumentationHealthSchema = ToolInputSchema.extend({
      basePath: z.string().optional().default(""),
      toleranceMode: z.boolean().optional().default(true),
    });
  • src/index.ts:243-249 (registration)
    Tool registration in listToolsRequestHandler: defines name, description, and input schema.
      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)
    Dispatch handler in CallToolRequestSchema: parses input with schema and calls the healthCheckHandler method.
    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 to calculate the health score based on metrics like metadata completeness, broken links, etc., with tolerance mode adjustment.
    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