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);
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It mentions the tool 'Returns a report with issues and a health score,' which adds some behavioral context about output. However, it lacks details on permissions, rate limits, side effects, or error handling. For a tool with no annotations, this is insufficient to fully understand its behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized with two sentences that are front-loaded and efficient. The first sentence states the purpose, and the second describes the output, with no wasted words. However, it could be slightly more structured by explicitly separating purpose and parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (2 parameters, no annotations, no output schema), the description is incomplete. It explains the purpose and output but lacks parameter details, usage guidelines, and behavioral traits. Without annotations or an output schema, more context is needed to fully understand the tool's operation and results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It provides no information about the parameters 'path' and 'basePath', such as their meanings, formats, or usage. The description adds no value beyond what the schema provides, failing to address the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Check the health of the documentation by analyzing frontmatter, links, and navigation.' It specifies the verb ('check'), resource ('documentation'), and scope ('frontmatter, links, and navigation'). However, it doesn't explicitly differentiate from sibling tools like 'validate_documentation_links' or 'validate_documentation_metadata', which perform similar validation tasks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites, context, or exclusions, nor does it reference sibling tools like 'validate_documentation_links' or 'validate_documentation_metadata' that might overlap in functionality. Usage is implied but not explicitly stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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