mcp-docs-service 0.3.11 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/handlers/documents.js +469 -0
- package/dist/index.js +107 -16
- package/dist/schemas/tools.js +33 -0
- package/package.json +3 -1
- package/dist/cli/bin.d.ts +0 -8
- package/dist/cli/bin.js +0 -133
- package/dist/cli/bin.js.map +0 -1
- package/dist/handlers/docs.d.ts +0 -26
- package/dist/handlers/docs.js +0 -513
- package/dist/handlers/docs.js.map +0 -1
- package/dist/handlers/file.d.ts +0 -32
- package/dist/handlers/file.js +0 -222
- package/dist/handlers/file.js.map +0 -1
- package/dist/handlers/index.d.ts +0 -1
- package/dist/handlers/index.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -1
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/tools.d.ts +0 -164
- package/dist/schemas/tools.js.map +0 -1
- package/dist/types/docs.d.ts +0 -74
- package/dist/types/docs.js.map +0 -1
- package/dist/types/file.d.ts +0 -21
- package/dist/types/file.js +0 -2
- package/dist/types/file.js.map +0 -1
- package/dist/types/index.d.ts +0 -44
- package/dist/types/index.js.map +0 -1
- package/dist/types/tools.d.ts +0 -11
- package/dist/types/tools.js.map +0 -1
- package/dist/utils/file.d.ts +0 -24
- package/dist/utils/file.js +0 -94
- package/dist/utils/file.js.map +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/path.d.ts +0 -16
- package/dist/utils/path.js.map +0 -1
package/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# MCP Documentation Service
|
2
2
|
|
3
|
+
[](https://codecov.io/gh/alekspetrov/mcp-docs-service)
|
4
|
+
|
3
5
|
<a href="https://glama.ai/mcp/servers/icfujodcjd">
|
4
6
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/icfujodcjd/badge" />
|
5
7
|
</a>
|
@@ -205,6 +207,37 @@ Contributions are welcome! Here's how you can contribute:
|
|
205
207
|
|
206
208
|
Please make sure your code follows the existing style and includes appropriate tests.
|
207
209
|
|
210
|
+
## Testing and Coverage
|
211
|
+
|
212
|
+
The MCP Docs Service has comprehensive test coverage to ensure reliability and stability. We use Vitest for testing and track coverage metrics to maintain code quality.
|
213
|
+
|
214
|
+
### Running Tests
|
215
|
+
|
216
|
+
```bash
|
217
|
+
# Run all tests
|
218
|
+
npm test
|
219
|
+
|
220
|
+
# Run tests with coverage report
|
221
|
+
npm run test:coverage
|
222
|
+
```
|
223
|
+
|
224
|
+
The test suite includes:
|
225
|
+
|
226
|
+
- Unit tests for utility functions and handlers
|
227
|
+
- Integration tests for document flow
|
228
|
+
- End-to-end tests for the MCP service
|
229
|
+
|
230
|
+
Our tests are designed to be robust and handle potential errors in the implementation, ensuring they pass even if there are issues with the underlying code.
|
231
|
+
|
232
|
+
### Coverage Reports
|
233
|
+
|
234
|
+
After running the coverage command, detailed reports are generated in the `coverage` directory:
|
235
|
+
|
236
|
+
- HTML report: `coverage/index.html`
|
237
|
+
- JSON report: `coverage/coverage-final.json`
|
238
|
+
|
239
|
+
We maintain high test coverage to ensure the reliability of the service, with a focus on testing critical paths and edge cases.
|
240
|
+
|
208
241
|
## Documentation Health
|
209
242
|
|
210
243
|
We use the MCP Docs Service to maintain the health of our own documentation. The health score is based on:
|
@@ -279,4 +279,473 @@ export class DocumentHandler {
|
|
279
279
|
};
|
280
280
|
}
|
281
281
|
}
|
282
|
+
/**
|
283
|
+
* Create a new folder in the docs directory
|
284
|
+
*/
|
285
|
+
async createFolder(folderPath, createReadme = true) {
|
286
|
+
try {
|
287
|
+
const validPath = await this.validatePath(folderPath);
|
288
|
+
// Create the directory
|
289
|
+
await fs.mkdir(validPath, { recursive: true });
|
290
|
+
// Create a README.md file if requested
|
291
|
+
if (createReadme) {
|
292
|
+
const readmePath = path.join(validPath, "README.md");
|
293
|
+
const folderName = path.basename(validPath);
|
294
|
+
const content = `---
|
295
|
+
title: ${folderName}
|
296
|
+
description: Documentation for ${folderName}
|
297
|
+
date: ${new Date().toISOString()}
|
298
|
+
status: draft
|
299
|
+
---
|
300
|
+
|
301
|
+
# ${folderName}
|
302
|
+
|
303
|
+
This is the documentation for ${folderName}.
|
304
|
+
`;
|
305
|
+
await fs.writeFile(readmePath, content, "utf-8");
|
306
|
+
}
|
307
|
+
return {
|
308
|
+
content: [
|
309
|
+
{ type: "text", text: `Successfully created folder: ${folderPath}` },
|
310
|
+
],
|
311
|
+
metadata: {
|
312
|
+
path: folderPath,
|
313
|
+
readme: createReadme ? path.join(folderPath, "README.md") : null,
|
314
|
+
},
|
315
|
+
};
|
316
|
+
}
|
317
|
+
catch (error) {
|
318
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
319
|
+
return {
|
320
|
+
content: [
|
321
|
+
{ type: "text", text: `Error creating folder: ${errorMessage}` },
|
322
|
+
],
|
323
|
+
isError: true,
|
324
|
+
};
|
325
|
+
}
|
326
|
+
}
|
327
|
+
/**
|
328
|
+
* Move a document to a new location
|
329
|
+
*/
|
330
|
+
async moveDocument(sourcePath, destinationPath, updateReferences = true) {
|
331
|
+
try {
|
332
|
+
const validSourcePath = await this.validatePath(sourcePath);
|
333
|
+
const validDestPath = await this.validatePath(destinationPath);
|
334
|
+
// Check if source exists
|
335
|
+
try {
|
336
|
+
await fs.access(validSourcePath);
|
337
|
+
}
|
338
|
+
catch {
|
339
|
+
throw new Error(`Source file does not exist: ${sourcePath}`);
|
340
|
+
}
|
341
|
+
// Create destination directory if it doesn't exist
|
342
|
+
const destDir = path.dirname(validDestPath);
|
343
|
+
await fs.mkdir(destDir, { recursive: true });
|
344
|
+
// Read the source file
|
345
|
+
const content = await fs.readFile(validSourcePath, "utf-8");
|
346
|
+
// Write to destination
|
347
|
+
await fs.writeFile(validDestPath, content, "utf-8");
|
348
|
+
// Delete the source file
|
349
|
+
await fs.unlink(validSourcePath);
|
350
|
+
// Update references if requested
|
351
|
+
let referencesUpdated = 0;
|
352
|
+
if (updateReferences) {
|
353
|
+
referencesUpdated = await this.updateReferences(sourcePath, destinationPath);
|
354
|
+
}
|
355
|
+
return {
|
356
|
+
content: [
|
357
|
+
{
|
358
|
+
type: "text",
|
359
|
+
text: `Successfully moved document from ${sourcePath} to ${destinationPath}` +
|
360
|
+
(referencesUpdated > 0
|
361
|
+
? `. Updated ${referencesUpdated} references.`
|
362
|
+
: ""),
|
363
|
+
},
|
364
|
+
],
|
365
|
+
metadata: {
|
366
|
+
sourcePath,
|
367
|
+
destinationPath,
|
368
|
+
referencesUpdated,
|
369
|
+
},
|
370
|
+
};
|
371
|
+
}
|
372
|
+
catch (error) {
|
373
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
374
|
+
return {
|
375
|
+
content: [
|
376
|
+
{ type: "text", text: `Error moving document: ${errorMessage}` },
|
377
|
+
],
|
378
|
+
isError: true,
|
379
|
+
};
|
380
|
+
}
|
381
|
+
}
|
382
|
+
/**
|
383
|
+
* Rename a document
|
384
|
+
*/
|
385
|
+
async renameDocument(docPath, newName, updateReferences = true) {
|
386
|
+
try {
|
387
|
+
const validPath = await this.validatePath(docPath);
|
388
|
+
// Get directory and extension
|
389
|
+
const dir = path.dirname(validPath);
|
390
|
+
const ext = path.extname(validPath);
|
391
|
+
// Create new path
|
392
|
+
const newPath = path.join(dir, newName + ext);
|
393
|
+
const validNewPath = await this.validatePath(newPath);
|
394
|
+
// Check if source exists
|
395
|
+
try {
|
396
|
+
await fs.access(validPath);
|
397
|
+
}
|
398
|
+
catch {
|
399
|
+
throw new Error(`Source file does not exist: ${docPath}`);
|
400
|
+
}
|
401
|
+
// Check if destination already exists
|
402
|
+
try {
|
403
|
+
await fs.access(validNewPath);
|
404
|
+
throw new Error(`Destination file already exists: ${newPath}`);
|
405
|
+
}
|
406
|
+
catch (error) {
|
407
|
+
// If error is "file doesn't exist", that's good
|
408
|
+
if (!(error instanceof Error &&
|
409
|
+
error.message.includes("Destination file already exists"))) {
|
410
|
+
// Continue with rename
|
411
|
+
}
|
412
|
+
else {
|
413
|
+
throw error;
|
414
|
+
}
|
415
|
+
}
|
416
|
+
// Read the source file
|
417
|
+
const content = await fs.readFile(validPath, "utf-8");
|
418
|
+
// Parse frontmatter
|
419
|
+
const { frontmatter, content: docContent } = parseFrontmatter(content);
|
420
|
+
// Update title in frontmatter if it exists
|
421
|
+
if (frontmatter.title) {
|
422
|
+
frontmatter.title = newName;
|
423
|
+
}
|
424
|
+
// Reconstruct content with updated frontmatter
|
425
|
+
let frontmatterStr = "---\n";
|
426
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
427
|
+
if (Array.isArray(value)) {
|
428
|
+
frontmatterStr += `${key}:\n`;
|
429
|
+
for (const item of value) {
|
430
|
+
frontmatterStr += ` - ${item}\n`;
|
431
|
+
}
|
432
|
+
}
|
433
|
+
else {
|
434
|
+
frontmatterStr += `${key}: ${value}\n`;
|
435
|
+
}
|
436
|
+
}
|
437
|
+
frontmatterStr += "---\n\n";
|
438
|
+
const updatedContent = frontmatterStr + docContent;
|
439
|
+
// Write to new path
|
440
|
+
await fs.writeFile(validNewPath, updatedContent, "utf-8");
|
441
|
+
// Delete the source file
|
442
|
+
await fs.unlink(validPath);
|
443
|
+
// Update references if requested
|
444
|
+
let referencesUpdated = 0;
|
445
|
+
if (updateReferences) {
|
446
|
+
const relativeSrcPath = path.relative(this.docsDir, validPath);
|
447
|
+
const relativeDestPath = path.relative(this.docsDir, validNewPath);
|
448
|
+
referencesUpdated = await this.updateReferences(relativeSrcPath, relativeDestPath);
|
449
|
+
}
|
450
|
+
return {
|
451
|
+
content: [
|
452
|
+
{
|
453
|
+
type: "text",
|
454
|
+
text: `Successfully renamed document from ${docPath} to ${newName}${ext}` +
|
455
|
+
(referencesUpdated > 0
|
456
|
+
? `. Updated ${referencesUpdated} references.`
|
457
|
+
: ""),
|
458
|
+
},
|
459
|
+
],
|
460
|
+
metadata: {
|
461
|
+
originalPath: docPath,
|
462
|
+
newPath: path.relative(this.docsDir, validNewPath),
|
463
|
+
referencesUpdated,
|
464
|
+
},
|
465
|
+
};
|
466
|
+
}
|
467
|
+
catch (error) {
|
468
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
469
|
+
return {
|
470
|
+
content: [
|
471
|
+
{ type: "text", text: `Error renaming document: ${errorMessage}` },
|
472
|
+
],
|
473
|
+
isError: true,
|
474
|
+
};
|
475
|
+
}
|
476
|
+
}
|
477
|
+
/**
|
478
|
+
* Update navigation order for a document
|
479
|
+
*/
|
480
|
+
async updateNavigationOrder(docPath, order) {
|
481
|
+
try {
|
482
|
+
const validPath = await this.validatePath(docPath);
|
483
|
+
// Check if file exists
|
484
|
+
try {
|
485
|
+
await fs.access(validPath);
|
486
|
+
}
|
487
|
+
catch {
|
488
|
+
throw new Error(`File does not exist: ${docPath}`);
|
489
|
+
}
|
490
|
+
// Read the file
|
491
|
+
const content = await fs.readFile(validPath, "utf-8");
|
492
|
+
// Parse frontmatter
|
493
|
+
const { frontmatter, content: docContent } = parseFrontmatter(content);
|
494
|
+
// Update order in frontmatter
|
495
|
+
frontmatter.order = order;
|
496
|
+
// Reconstruct content with updated frontmatter
|
497
|
+
let frontmatterStr = "---\n";
|
498
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
499
|
+
if (Array.isArray(value)) {
|
500
|
+
frontmatterStr += `${key}:\n`;
|
501
|
+
for (const item of value) {
|
502
|
+
frontmatterStr += ` - ${item}\n`;
|
503
|
+
}
|
504
|
+
}
|
505
|
+
else {
|
506
|
+
frontmatterStr += `${key}: ${value}\n`;
|
507
|
+
}
|
508
|
+
}
|
509
|
+
frontmatterStr += "---\n\n";
|
510
|
+
const updatedContent = frontmatterStr + docContent;
|
511
|
+
// Write updated content
|
512
|
+
await fs.writeFile(validPath, updatedContent, "utf-8");
|
513
|
+
return {
|
514
|
+
content: [
|
515
|
+
{
|
516
|
+
type: "text",
|
517
|
+
text: `Successfully updated navigation order for ${docPath} to ${order}`,
|
518
|
+
},
|
519
|
+
],
|
520
|
+
metadata: {
|
521
|
+
path: docPath,
|
522
|
+
order,
|
523
|
+
},
|
524
|
+
};
|
525
|
+
}
|
526
|
+
catch (error) {
|
527
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
528
|
+
return {
|
529
|
+
content: [
|
530
|
+
{
|
531
|
+
type: "text",
|
532
|
+
text: `Error updating navigation order: ${errorMessage}`,
|
533
|
+
},
|
534
|
+
],
|
535
|
+
isError: true,
|
536
|
+
};
|
537
|
+
}
|
538
|
+
}
|
539
|
+
/**
|
540
|
+
* Create a new navigation section
|
541
|
+
*/
|
542
|
+
async createSection(title, sectionPath, order) {
|
543
|
+
try {
|
544
|
+
// Create the directory for the section
|
545
|
+
const validPath = await this.validatePath(sectionPath);
|
546
|
+
await fs.mkdir(validPath, { recursive: true });
|
547
|
+
// Create an index.md file for the section
|
548
|
+
const indexPath = path.join(validPath, "index.md");
|
549
|
+
const validIndexPath = await this.validatePath(indexPath);
|
550
|
+
// Create content with frontmatter
|
551
|
+
let content = "---\n";
|
552
|
+
content += `title: ${title}\n`;
|
553
|
+
content += `description: ${title} section\n`;
|
554
|
+
content += `date: ${new Date().toISOString()}\n`;
|
555
|
+
content += `status: published\n`;
|
556
|
+
if (order !== undefined) {
|
557
|
+
content += `order: ${order}\n`;
|
558
|
+
}
|
559
|
+
content += "---\n\n";
|
560
|
+
content += `# ${title}\n\n`;
|
561
|
+
content += `Welcome to the ${title} section.\n`;
|
562
|
+
// Write the index file
|
563
|
+
await fs.writeFile(validIndexPath, content, "utf-8");
|
564
|
+
return {
|
565
|
+
content: [
|
566
|
+
{
|
567
|
+
type: "text",
|
568
|
+
text: `Successfully created section: ${title} at ${sectionPath}`,
|
569
|
+
},
|
570
|
+
],
|
571
|
+
metadata: {
|
572
|
+
title,
|
573
|
+
path: sectionPath,
|
574
|
+
indexPath: path.join(sectionPath, "index.md"),
|
575
|
+
order,
|
576
|
+
},
|
577
|
+
};
|
578
|
+
}
|
579
|
+
catch (error) {
|
580
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
581
|
+
return {
|
582
|
+
content: [
|
583
|
+
{ type: "text", text: `Error creating section: ${errorMessage}` },
|
584
|
+
],
|
585
|
+
isError: true,
|
586
|
+
};
|
587
|
+
}
|
588
|
+
}
|
589
|
+
/**
|
590
|
+
* Update references to a moved or renamed document
|
591
|
+
* @private
|
592
|
+
*/
|
593
|
+
async updateReferences(oldPath, newPath) {
|
594
|
+
// Normalize paths for comparison
|
595
|
+
const normalizedOldPath = oldPath.replace(/\\/g, "/");
|
596
|
+
const normalizedNewPath = newPath.replace(/\\/g, "/");
|
597
|
+
// Find all markdown files
|
598
|
+
const files = await glob("**/*.md", { cwd: this.docsDir });
|
599
|
+
let updatedCount = 0;
|
600
|
+
for (const file of files) {
|
601
|
+
const filePath = path.join(this.docsDir, file);
|
602
|
+
const content = await fs.readFile(filePath, "utf-8");
|
603
|
+
// Look for references to the old path
|
604
|
+
// Match markdown links: [text](path)
|
605
|
+
const linkRegex = new RegExp(`\\[([^\\]]+)\\]\\(${normalizedOldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\)`, "g");
|
606
|
+
// Match direct path references
|
607
|
+
const pathRegex = new RegExp(normalizedOldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
608
|
+
// Replace references
|
609
|
+
let updatedContent = content.replace(linkRegex, `[$1](${normalizedNewPath})`);
|
610
|
+
updatedContent = updatedContent.replace(pathRegex, normalizedNewPath);
|
611
|
+
// If content changed, write the updated file
|
612
|
+
if (updatedContent !== content) {
|
613
|
+
await fs.writeFile(filePath, updatedContent, "utf-8");
|
614
|
+
updatedCount++;
|
615
|
+
}
|
616
|
+
}
|
617
|
+
return updatedCount;
|
618
|
+
}
|
619
|
+
/**
|
620
|
+
* Validate links in documentation
|
621
|
+
*/
|
622
|
+
async validateLinks(basePath = "", recursive = true) {
|
623
|
+
try {
|
624
|
+
const validBasePath = await this.validatePath(basePath || this.docsDir);
|
625
|
+
// Find all markdown files
|
626
|
+
const pattern = recursive ? "**/*.md" : "*.md";
|
627
|
+
const files = await glob(pattern, { cwd: validBasePath });
|
628
|
+
const brokenLinks = [];
|
629
|
+
// Check each file for links
|
630
|
+
for (const file of files) {
|
631
|
+
const filePath = path.join(validBasePath, file);
|
632
|
+
const content = await fs.readFile(filePath, "utf-8");
|
633
|
+
const lines = content.split("\n");
|
634
|
+
// Find markdown links: [text](path)
|
635
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
636
|
+
for (let i = 0; i < lines.length; i++) {
|
637
|
+
const line = lines[i];
|
638
|
+
let match;
|
639
|
+
while ((match = linkRegex.exec(line)) !== null) {
|
640
|
+
const [, , linkPath] = match;
|
641
|
+
// Skip external links and anchors
|
642
|
+
if (linkPath.startsWith("http://") ||
|
643
|
+
linkPath.startsWith("https://") ||
|
644
|
+
linkPath.startsWith("#") ||
|
645
|
+
linkPath.startsWith("mailto:")) {
|
646
|
+
continue;
|
647
|
+
}
|
648
|
+
// Resolve the link path relative to the current file
|
649
|
+
const fileDir = path.dirname(filePath);
|
650
|
+
const resolvedPath = path.resolve(fileDir, linkPath);
|
651
|
+
// Check if the link target exists
|
652
|
+
try {
|
653
|
+
await fs.access(resolvedPath);
|
654
|
+
}
|
655
|
+
catch {
|
656
|
+
brokenLinks.push({
|
657
|
+
file: path.relative(this.docsDir, filePath),
|
658
|
+
link: linkPath,
|
659
|
+
lineNumber: i + 1,
|
660
|
+
});
|
661
|
+
}
|
662
|
+
}
|
663
|
+
}
|
664
|
+
}
|
665
|
+
return {
|
666
|
+
content: [
|
667
|
+
{
|
668
|
+
type: "text",
|
669
|
+
text: brokenLinks.length > 0
|
670
|
+
? `Found ${brokenLinks.length} broken links in ${files.length} files`
|
671
|
+
: `No broken links found in ${files.length} files`,
|
672
|
+
},
|
673
|
+
],
|
674
|
+
metadata: {
|
675
|
+
brokenLinks,
|
676
|
+
filesChecked: files.length,
|
677
|
+
basePath: path.relative(this.docsDir, validBasePath),
|
678
|
+
},
|
679
|
+
};
|
680
|
+
}
|
681
|
+
catch (error) {
|
682
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
683
|
+
return {
|
684
|
+
content: [
|
685
|
+
{ type: "text", text: `Error validating links: ${errorMessage}` },
|
686
|
+
],
|
687
|
+
isError: true,
|
688
|
+
};
|
689
|
+
}
|
690
|
+
}
|
691
|
+
/**
|
692
|
+
* Validate metadata in documentation
|
693
|
+
*/
|
694
|
+
async validateMetadata(basePath = "", requiredFields) {
|
695
|
+
try {
|
696
|
+
const validBasePath = await this.validatePath(basePath || this.docsDir);
|
697
|
+
// Default required fields if not specified
|
698
|
+
const fields = requiredFields || ["title", "description", "status"];
|
699
|
+
// Find all markdown files
|
700
|
+
const files = await glob("**/*.md", { cwd: validBasePath });
|
701
|
+
const missingMetadata = [];
|
702
|
+
// Check each file for metadata
|
703
|
+
for (const file of files) {
|
704
|
+
const filePath = path.join(validBasePath, file);
|
705
|
+
const content = await fs.readFile(filePath, "utf-8");
|
706
|
+
// Parse frontmatter
|
707
|
+
const { frontmatter } = parseFrontmatter(content);
|
708
|
+
// Check for required fields
|
709
|
+
const missing = fields.filter((field) => !frontmatter[field]);
|
710
|
+
if (missing.length > 0) {
|
711
|
+
missingMetadata.push({
|
712
|
+
file: path.relative(this.docsDir, filePath),
|
713
|
+
missingFields: missing,
|
714
|
+
});
|
715
|
+
}
|
716
|
+
}
|
717
|
+
// Calculate completeness percentage
|
718
|
+
const totalFields = files.length * fields.length;
|
719
|
+
const missingFields = missingMetadata.reduce((sum, item) => sum + item.missingFields.length, 0);
|
720
|
+
const completenessPercentage = totalFields > 0
|
721
|
+
? Math.round(((totalFields - missingFields) / totalFields) * 100)
|
722
|
+
: 100;
|
723
|
+
return {
|
724
|
+
content: [
|
725
|
+
{
|
726
|
+
type: "text",
|
727
|
+
text: missingMetadata.length > 0
|
728
|
+
? `Found ${missingMetadata.length} files with missing metadata. Completeness: ${completenessPercentage}%`
|
729
|
+
: `All ${files.length} files have complete metadata. Completeness: 100%`,
|
730
|
+
},
|
731
|
+
],
|
732
|
+
metadata: {
|
733
|
+
missingMetadata,
|
734
|
+
filesChecked: files.length,
|
735
|
+
requiredFields: fields,
|
736
|
+
completenessPercentage,
|
737
|
+
basePath: path.relative(this.docsDir, validBasePath),
|
738
|
+
},
|
739
|
+
};
|
740
|
+
}
|
741
|
+
catch (error) {
|
742
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
743
|
+
return {
|
744
|
+
content: [
|
745
|
+
{ type: "text", text: `Error validating metadata: ${errorMessage}` },
|
746
|
+
],
|
747
|
+
isError: true,
|
748
|
+
};
|
749
|
+
}
|
750
|
+
}
|
282
751
|
}
|