validate_documentation_metadata
Validate that documentation files contain required metadata fields to ensure completeness and consistency across documents.
Instructions
Ensure all documents have required metadata fields.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | ||
| basePath | No | ||
| requiredFields | No |
Implementation Reference
- src/handlers/documents.ts:881-960 (handler)The core handler function that implements the tool logic: scans all .md files in the basePath (default docs dir), parses frontmatter using parseFrontmatter helper, checks for required metadata fields (default: title, description, status), collects missing ones, computes completeness percentage, and returns a ToolResponse with results./** * Validate metadata in documentation */ async validateMetadata( basePath = "", requiredFields?: string[] ): Promise<ToolResponse> { try { const validBasePath = await this.validatePath(basePath || this.docsDir); // Default required fields if not specified const fields = requiredFields || ["title", "description", "status"]; // Find all markdown files const files = await glob("**/*.md", { cwd: validBasePath }); const missingMetadata: Array<{ file: string; missingFields: string[]; }> = []; // Check each file for metadata for (const file of files) { const filePath = path.join(validBasePath, file); const content = await fs.readFile(filePath, "utf-8"); // Parse frontmatter const { frontmatter } = parseFrontmatter(content); // Check for required fields const missing = fields.filter((field) => !frontmatter[field]); if (missing.length > 0) { missingMetadata.push({ file: path.relative(this.docsDir, filePath), missingFields: missing, }); } } // Calculate completeness percentage const totalFields = files.length * fields.length; const missingFields = missingMetadata.reduce( (sum, item) => sum + item.missingFields.length, 0 ); const completenessPercentage = totalFields > 0 ? Math.round(((totalFields - missingFields) / totalFields) * 100) : 100; return { content: [ { type: "text", text: missingMetadata.length > 0 ? `Found ${missingMetadata.length} files with missing metadata. Completeness: ${completenessPercentage}%` : `All ${files.length} files have complete metadata. Completeness: 100%`, }, ], metadata: { missingMetadata, filesChecked: files.length, requiredFields: fields, completenessPercentage, basePath: path.relative(this.docsDir, validBasePath), }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error validating metadata: ${errorMessage}` }, ], isError: true, }; } }
- src/schemas/tools.ts:98-101 (schema)Zod schema defining the input parameters for the tool: optional basePath (defaults to root) and optional array of requiredFields.export const ValidateMetadataSchema = ToolInputSchema.extend({ basePath: z.string().optional().default(""), requiredFields: z.array(z.string()).optional(), });
- src/index.ts:290-294 (registration)Tool registration in the MCP server's listTools handler, specifying name, description, and input schema.{ name: "validate_documentation_metadata", description: "Ensure all documents have required metadata fields.", inputSchema: zodToJsonSchema(ValidateMetadataSchema) as any, },
- src/index.ts:483-494 (handler)Dispatch handler in the main CallToolRequestSchema switch statement that validates input and calls the DocumentHandler's validateMetadata method.case "validate_documentation_metadata": { const parsed = ValidateMetadataSchema.safeParse(args); if (!parsed.success) { throw new Error( `Invalid arguments for validate_metadata: ${parsed.error}` ); } return await documentHandler.validateMetadata( parsed.data.basePath, parsed.data.requiredFields ); }
- src/handlers/documents.ts:40-83 (helper)Helper function used by validateMetadata to parse YAML frontmatter from markdown files and extract metadata fields.export function parseFrontmatter(content: string): { frontmatter: Record<string, any>; content: string; } { const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/; const match = content.match(frontmatterRegex); if (!match) { return { frontmatter: {}, content }; } const frontmatterStr = match[1]; const contentWithoutFrontmatter = content.slice(match[0].length); // Parse frontmatter as key-value pairs const frontmatter: Record<string, any> = {}; const lines = frontmatterStr.split("\n"); for (const line of lines) { const colonIndex = line.indexOf(":"); if (colonIndex !== -1) { const key = line.slice(0, colonIndex).trim(); let value = line.slice(colonIndex + 1).trim(); // Handle quoted values if (value.startsWith('"') && value.endsWith('"')) { value = value.slice(1, -1); } // Handle arrays if (value.startsWith("[") && value.endsWith("]")) { try { value = JSON.parse(value); } catch { // Keep as string if parsing fails } } frontmatter[key] = value; } } return { frontmatter, content: contentWithoutFrontmatter }; }