mcp-docs-service 0.3.9 → 0.4.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.
Files changed (47) hide show
  1. package/dist/cli/bin.d.ts +8 -0
  2. package/dist/cli/bin.js +133 -0
  3. package/dist/cli/bin.js.map +1 -0
  4. package/dist/handlers/docs.d.ts +26 -0
  5. package/dist/handlers/docs.js +513 -0
  6. package/dist/handlers/docs.js.map +1 -0
  7. package/dist/handlers/documents.js +282 -0
  8. package/dist/handlers/file.d.ts +32 -0
  9. package/dist/handlers/file.js +222 -0
  10. package/dist/handlers/file.js.map +1 -0
  11. package/dist/handlers/health.js +196 -0
  12. package/dist/handlers/index.d.ts +1 -0
  13. package/dist/handlers/index.js +8 -0
  14. package/dist/handlers/index.js.map +1 -0
  15. package/dist/handlers/navigation.js +128 -0
  16. package/dist/index.js +108 -550
  17. package/dist/schemas/index.d.ts +1 -0
  18. package/dist/schemas/index.js +1 -0
  19. package/dist/schemas/index.js.map +1 -0
  20. package/dist/schemas/tools.d.ts +164 -0
  21. package/dist/schemas/tools.js +47 -0
  22. package/dist/schemas/tools.js.map +1 -0
  23. package/dist/types/docs.d.ts +74 -0
  24. package/dist/types/docs.js +1 -0
  25. package/dist/types/docs.js.map +1 -0
  26. package/dist/types/file.d.ts +21 -0
  27. package/dist/types/file.js +2 -0
  28. package/dist/types/file.js.map +1 -0
  29. package/dist/types/index.d.ts +44 -0
  30. package/dist/types/index.js +2 -0
  31. package/dist/types/index.js.map +1 -0
  32. package/dist/types/tools.d.ts +11 -0
  33. package/dist/types/tools.js +1 -0
  34. package/dist/types/tools.js.map +1 -0
  35. package/dist/utils/file.d.ts +24 -0
  36. package/dist/utils/file.js +94 -0
  37. package/dist/utils/file.js.map +1 -0
  38. package/dist/utils/index.d.ts +1 -0
  39. package/dist/utils/index.js +2 -0
  40. package/dist/utils/index.js.map +1 -0
  41. package/dist/utils/logging.js +27 -0
  42. package/dist/utils/path.d.ts +16 -0
  43. package/dist/utils/path.js +69 -0
  44. package/dist/utils/path.js.map +1 -0
  45. package/package.json +5 -9
  46. package/cursor-wrapper.cjs +0 -111
  47. package/npx-wrapper.cjs +0 -160
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Docs Service CLI
4
+ *
5
+ * This is the entry point for the CLI version of the MCP Docs Service.
6
+ * It simply imports and runs the main service.
7
+ */
8
+ import "../index.js";
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Docs Service CLI
4
+ *
5
+ * This is the entry point for the CLI version of the MCP Docs Service.
6
+ * It simply imports and runs the main service.
7
+ */
8
+ import path from "path";
9
+ import fs from "fs";
10
+ import { fileURLToPath } from "url";
11
+ // Get the current directory
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ // Check if we're running under MCP Inspector
15
+ const isMCPInspector = process.env.MCP_INSPECTOR === "true" ||
16
+ process.argv.some((arg) => arg.includes("modelcontextprotocol/inspector"));
17
+ // Create a logging function that respects MCP Inspector mode
18
+ const log = (...args) => {
19
+ if (!isMCPInspector) {
20
+ console.log(...args);
21
+ }
22
+ };
23
+ const errorLog = (...args) => {
24
+ console.error(...args);
25
+ };
26
+ // Parse command line arguments
27
+ const args = process.argv.slice(2);
28
+ log("CLI Arguments:", JSON.stringify(args));
29
+ let docsDir = path.join(process.cwd(), "docs");
30
+ let createDir = false;
31
+ let healthCheck = false;
32
+ let showHelp = false;
33
+ // MCP Inspector specific handling
34
+ // When run through MCP Inspector, it might pass arguments in a different format
35
+ if (isMCPInspector) {
36
+ log("Detected MCP Inspector environment");
37
+ // Try to find a valid docs directory in all arguments
38
+ // This is a more aggressive approach but should work with various argument formats
39
+ for (const arg of process.argv) {
40
+ if (arg.endsWith("/docs") || arg.includes("/docs ")) {
41
+ const potentialPath = arg.split(" ")[0];
42
+ log("Found potential docs path in MCP Inspector args:", potentialPath);
43
+ if (fs.existsSync(potentialPath)) {
44
+ docsDir = path.resolve(potentialPath);
45
+ log("Using docs directory from MCP Inspector:", docsDir);
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ // If we couldn't find a valid docs directory, use the default
51
+ log("Using docs directory:", docsDir);
52
+ }
53
+ else {
54
+ // Standard argument parsing
55
+ for (let i = 0; i < args.length; i++) {
56
+ log(`Processing arg[${i}]:`, args[i]);
57
+ if (args[i] === "--docs-dir" && i + 1 < args.length) {
58
+ docsDir = path.resolve(args[i + 1]);
59
+ log("Setting docs dir from --docs-dir flag:", docsDir);
60
+ i++; // Skip the next argument
61
+ }
62
+ else if (args[i] === "--create-dir") {
63
+ createDir = true;
64
+ }
65
+ else if (args[i] === "--health-check") {
66
+ healthCheck = true;
67
+ }
68
+ else if (args[i] === "--help" || args[i] === "-h") {
69
+ showHelp = true;
70
+ }
71
+ else if (!args[i].startsWith("--")) {
72
+ // Handle positional argument as docs directory
73
+ const potentialPath = path.resolve(args[i]);
74
+ log("Potential positional path:", potentialPath);
75
+ log("Path exists?", fs.existsSync(potentialPath));
76
+ if (fs.existsSync(potentialPath)) {
77
+ docsDir = potentialPath;
78
+ log("Setting docs dir from positional argument:", docsDir);
79
+ }
80
+ else {
81
+ log("Path doesn't exist, not using as docs dir:", potentialPath);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ log("Final docs dir:", docsDir);
87
+ // Show help if requested
88
+ if (showHelp) {
89
+ console.log(`
90
+ MCP Docs Service - Documentation Management Service
91
+
92
+ Usage:
93
+ mcp-docs-service [options]
94
+ mcp-docs-service <docs-directory> [options]
95
+
96
+ Options:
97
+ --docs-dir <path> Specify the docs directory (default: ./docs)
98
+ --create-dir Create the docs directory if it doesn't exist
99
+ --health-check Run a health check on the documentation
100
+ --help, -h Show this help message
101
+ `);
102
+ process.exit(0);
103
+ }
104
+ // Create docs directory if it doesn't exist and --create-dir is specified
105
+ if (createDir) {
106
+ try {
107
+ if (!fs.existsSync(docsDir)) {
108
+ fs.mkdirSync(docsDir, { recursive: true });
109
+ log(`Created docs directory: ${docsDir}`);
110
+ }
111
+ }
112
+ catch (error) {
113
+ errorLog(`Error creating docs directory: ${error}`);
114
+ process.exit(1);
115
+ }
116
+ }
117
+ // Ensure the docs directory exists
118
+ if (!fs.existsSync(docsDir)) {
119
+ errorLog(`Error: Docs directory does not exist: ${docsDir}`);
120
+ errorLog(`Use --create-dir to create it automatically`);
121
+ process.exit(1);
122
+ }
123
+ // Add the docs directory to process.argv so it's available to the main service
124
+ process.argv.push(docsDir);
125
+ // Add health check flag to process.argv if specified
126
+ if (healthCheck) {
127
+ process.argv.push("--health-check");
128
+ }
129
+ // Import the main service
130
+ import "../index.js";
131
+ // The main service will handle the CLI arguments and execution
132
+ // No additional code needed here as the main index.ts already has CLI functionality
133
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { ToolResponse } from "../types/tools.js";
2
+ /**
3
+ * Reads a markdown document and extracts its content and metadata
4
+ */
5
+ export declare function readDocument(docPath: string, allowedDirectories: string[]): Promise<ToolResponse>;
6
+ /**
7
+ * Lists all markdown documents in a directory
8
+ */
9
+ export declare function listDocuments(basePath: string, allowedDirectories: string[]): Promise<ToolResponse>;
10
+ /**
11
+ * Gets the structure of the documentation directory
12
+ */
13
+ export declare function getStructure(basePath: string, allowedDirectories: string[]): Promise<ToolResponse>;
14
+ /**
15
+ * Gets the navigation structure for the documentation
16
+ */
17
+ export declare function getNavigation(basePath: string, allowedDirectories: string[]): Promise<ToolResponse>;
18
+ /**
19
+ * Checks the health of documentation
20
+ */
21
+ export declare function checkDocumentationHealth(basePath: string, options: {
22
+ checkLinks?: boolean;
23
+ checkMetadata?: boolean;
24
+ checkOrphans?: boolean;
25
+ requiredMetadataFields?: string[];
26
+ }, allowedDirectories: string[]): Promise<ToolResponse>;
@@ -0,0 +1,513 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { validatePath } from "../utils/path.js";
4
+ import matter from "gray-matter";
5
+ /**
6
+ * Reads a markdown document and extracts its content and metadata
7
+ */
8
+ export async function readDocument(docPath, allowedDirectories) {
9
+ try {
10
+ const normalizedPath = await validatePath(docPath, allowedDirectories);
11
+ // Read the file
12
+ const content = await fs.readFile(normalizedPath, "utf-8");
13
+ // Parse frontmatter
14
+ const { data: metadata, content: markdownContent } = matter(content);
15
+ return {
16
+ content: [{ type: "text", text: "Document read successfully" }],
17
+ metadata: {
18
+ path: docPath,
19
+ content: markdownContent,
20
+ metadata,
21
+ },
22
+ };
23
+ }
24
+ catch (error) {
25
+ return {
26
+ content: [
27
+ { type: "text", text: `Error reading document: ${error.message}` },
28
+ ],
29
+ isError: true,
30
+ };
31
+ }
32
+ }
33
+ /**
34
+ * Lists all markdown documents in a directory
35
+ */
36
+ export async function listDocuments(basePath, allowedDirectories) {
37
+ try {
38
+ const normalizedBasePath = basePath
39
+ ? await validatePath(basePath, allowedDirectories)
40
+ : allowedDirectories[0];
41
+ const documents = [];
42
+ async function processDirectory(dirPath) {
43
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const entryPath = path.join(dirPath, entry.name);
46
+ if (entry.isDirectory()) {
47
+ await processDirectory(entryPath);
48
+ }
49
+ else if (entry.name.endsWith(".md")) {
50
+ try {
51
+ const content = await fs.readFile(entryPath, "utf-8");
52
+ const { data: metadata } = matter(content);
53
+ documents.push({
54
+ path: entryPath,
55
+ name: entry.name,
56
+ metadata: metadata,
57
+ });
58
+ }
59
+ catch (error) {
60
+ console.error(`Error processing ${entryPath}: ${error.message}`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ await processDirectory(normalizedBasePath);
66
+ return {
67
+ content: [{ type: "text", text: `Found ${documents.length} documents` }],
68
+ metadata: {
69
+ documents,
70
+ },
71
+ };
72
+ }
73
+ catch (error) {
74
+ return {
75
+ content: [
76
+ { type: "text", text: `Error listing documents: ${error.message}` },
77
+ ],
78
+ isError: true,
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Gets the structure of the documentation directory
84
+ */
85
+ export async function getStructure(basePath, allowedDirectories) {
86
+ try {
87
+ const normalizedBasePath = basePath
88
+ ? await validatePath(basePath, allowedDirectories)
89
+ : allowedDirectories[0];
90
+ async function buildStructure(dirPath, relativePath = "") {
91
+ try {
92
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
93
+ const children = [];
94
+ let metadata;
95
+ // Check if there's an index.md file to get directory metadata
96
+ const indexPath = path.join(dirPath, "index.md");
97
+ try {
98
+ const indexStat = await fs.stat(indexPath);
99
+ if (indexStat.isFile()) {
100
+ const content = await fs.readFile(indexPath, "utf-8");
101
+ const { data } = matter(content);
102
+ metadata = data;
103
+ }
104
+ }
105
+ catch (error) {
106
+ // No index.md file, that's fine
107
+ }
108
+ // Process all entries
109
+ for (const entry of entries) {
110
+ const entryPath = path.join(dirPath, entry.name);
111
+ const entryRelativePath = path.join(relativePath, entry.name);
112
+ if (entry.isDirectory()) {
113
+ const subDir = await buildStructure(entryPath, entryRelativePath);
114
+ children.push(subDir);
115
+ }
116
+ else if (entry.name.endsWith(".md") && entry.name !== "index.md") {
117
+ try {
118
+ const content = await fs.readFile(entryPath, "utf-8");
119
+ const { data } = matter(content);
120
+ children.push({
121
+ name: entry.name,
122
+ path: entryRelativePath,
123
+ type: "file",
124
+ metadata: data,
125
+ children: [],
126
+ });
127
+ }
128
+ catch (error) {
129
+ children.push({
130
+ name: entry.name,
131
+ path: entryRelativePath,
132
+ type: "file",
133
+ error: error.message,
134
+ children: [],
135
+ });
136
+ }
137
+ }
138
+ }
139
+ // Sort children by order metadata if available, then by name
140
+ children.sort((a, b) => {
141
+ const orderA = a.metadata?.order ?? Infinity;
142
+ const orderB = b.metadata?.order ?? Infinity;
143
+ if (orderA !== orderB) {
144
+ return orderA - orderB;
145
+ }
146
+ return a.name.localeCompare(b.name);
147
+ });
148
+ return {
149
+ name: path.basename(dirPath),
150
+ path: relativePath,
151
+ type: "directory",
152
+ metadata,
153
+ children,
154
+ };
155
+ }
156
+ catch (error) {
157
+ return {
158
+ name: path.basename(dirPath),
159
+ path: relativePath,
160
+ type: "directory",
161
+ error: error.message,
162
+ children: [],
163
+ };
164
+ }
165
+ }
166
+ const structure = await buildStructure(normalizedBasePath);
167
+ return {
168
+ content: [
169
+ {
170
+ type: "text",
171
+ text: "Documentation structure retrieved successfully",
172
+ },
173
+ ],
174
+ metadata: {
175
+ structure,
176
+ },
177
+ };
178
+ }
179
+ catch (error) {
180
+ return {
181
+ content: [
182
+ { type: "text", text: `Error getting structure: ${error.message}` },
183
+ ],
184
+ isError: true,
185
+ };
186
+ }
187
+ }
188
+ /**
189
+ * Gets the navigation structure for the documentation
190
+ */
191
+ export async function getNavigation(basePath, allowedDirectories) {
192
+ try {
193
+ const normalizedBasePath = basePath
194
+ ? await validatePath(basePath, allowedDirectories)
195
+ : allowedDirectories[0];
196
+ // First try to load navigation from .navigation file
197
+ const navigationFilePath = path.join(normalizedBasePath, ".navigation");
198
+ const navigationJsonPath = path.join(normalizedBasePath, "_navigation.json");
199
+ const navigationYmlPath = path.join(normalizedBasePath, "_navigation.yml");
200
+ let navigation = [];
201
+ // Try to load from .navigation file
202
+ try {
203
+ const navigationContent = await fs.readFile(navigationFilePath, "utf-8");
204
+ navigation = JSON.parse(navigationContent);
205
+ console.log("Loaded navigation from .navigation file");
206
+ return {
207
+ content: [
208
+ {
209
+ type: "text",
210
+ text: "Navigation structure loaded from .navigation file",
211
+ },
212
+ ],
213
+ metadata: {
214
+ navigation,
215
+ },
216
+ };
217
+ }
218
+ catch (error) {
219
+ console.log("No .navigation file found or error loading it:", error.message);
220
+ }
221
+ // Try to load from _navigation.json file
222
+ try {
223
+ const navigationContent = await fs.readFile(navigationJsonPath, "utf-8");
224
+ navigation = JSON.parse(navigationContent);
225
+ console.log("Loaded navigation from _navigation.json file");
226
+ return {
227
+ content: [
228
+ {
229
+ type: "text",
230
+ text: "Navigation structure loaded from _navigation.json file",
231
+ },
232
+ ],
233
+ metadata: {
234
+ navigation,
235
+ },
236
+ };
237
+ }
238
+ catch (error) {
239
+ console.log("No _navigation.json file found or error loading it:", error.message);
240
+ }
241
+ // If no navigation file found, build from structure
242
+ console.log("Building navigation from directory structure");
243
+ // Get the structure
244
+ const structureResponse = await getStructure(basePath, allowedDirectories);
245
+ if (structureResponse.isError) {
246
+ return structureResponse;
247
+ }
248
+ const structure = structureResponse.metadata?.structure;
249
+ // Build navigation from structure
250
+ function buildNavigation(structure) {
251
+ const sections = [];
252
+ function processNode(node, parentPath = []) {
253
+ // Skip nodes with errors
254
+ if (node.error) {
255
+ return;
256
+ }
257
+ if (node.type === "directory") {
258
+ // Create a section for this directory
259
+ const section = {
260
+ title: node.metadata?.title || node.name,
261
+ path: node.path ? `/${node.path}` : null,
262
+ items: [],
263
+ order: node.metadata?.order ?? Infinity,
264
+ };
265
+ // Process children
266
+ for (const child of node.children) {
267
+ if (child.type === "file") {
268
+ // Add file as an item
269
+ section.items.push({
270
+ title: child.metadata?.title || child.name.replace(/\.md$/, ""),
271
+ path: `/${child.path}`,
272
+ order: child.metadata?.order ?? Infinity,
273
+ });
274
+ }
275
+ else if (child.type === "directory") {
276
+ // Process subdirectory
277
+ const childSections = processNode(child, [
278
+ ...parentPath,
279
+ node.name,
280
+ ]);
281
+ if (childSections) {
282
+ sections.push(...childSections);
283
+ }
284
+ }
285
+ }
286
+ // Sort items by order
287
+ section.items.sort((a, b) => {
288
+ if (a.order !== b.order) {
289
+ return a.order - b.order;
290
+ }
291
+ return a.title.localeCompare(b.title);
292
+ });
293
+ // Only add section if it has items
294
+ if (section.items.length > 0) {
295
+ sections.push(section);
296
+ }
297
+ return sections;
298
+ }
299
+ return null;
300
+ }
301
+ processNode(structure);
302
+ // Sort sections by order
303
+ sections.sort((a, b) => {
304
+ if (a.order !== b.order) {
305
+ return a.order - b.order;
306
+ }
307
+ return a.title.localeCompare(b.title);
308
+ });
309
+ return sections;
310
+ }
311
+ navigation = buildNavigation(structure);
312
+ // Add debug logging
313
+ console.log("Navigation structure:", JSON.stringify(navigation, null, 2));
314
+ return {
315
+ content: [
316
+ { type: "text", text: "Navigation structure retrieved successfully" },
317
+ ],
318
+ metadata: {
319
+ navigation,
320
+ },
321
+ };
322
+ }
323
+ catch (error) {
324
+ return {
325
+ content: [
326
+ { type: "text", text: `Error getting navigation: ${error.message}` },
327
+ ],
328
+ isError: true,
329
+ };
330
+ }
331
+ }
332
+ /**
333
+ * Checks the health of documentation
334
+ */
335
+ export async function checkDocumentationHealth(basePath, options, allowedDirectories) {
336
+ try {
337
+ // Set default options
338
+ const checkLinks = options.checkLinks !== false;
339
+ const checkMetadata = options.checkMetadata !== false;
340
+ const checkOrphans = options.checkOrphans !== false;
341
+ const requiredMetadataFields = options.requiredMetadataFields || [
342
+ "title",
343
+ "description",
344
+ "status",
345
+ ];
346
+ // Use the first allowed directory if basePath is empty
347
+ const effectiveBasePath = basePath || allowedDirectories[0];
348
+ // Get all documents
349
+ const docsResult = await listDocuments(effectiveBasePath, allowedDirectories);
350
+ if (docsResult.isError) {
351
+ return docsResult;
352
+ }
353
+ const documents = docsResult.metadata?.documents || [];
354
+ // Get navigation if checking for orphans
355
+ let navigation = [];
356
+ if (checkOrphans) {
357
+ const navResult = await getNavigation(effectiveBasePath, allowedDirectories);
358
+ if (!navResult.isError && navResult.metadata?.navigation) {
359
+ navigation = navResult.metadata.navigation;
360
+ }
361
+ }
362
+ // Initialize health check result
363
+ const healthResult = {
364
+ score: 0,
365
+ totalDocuments: documents.length,
366
+ issues: [],
367
+ metadataCompleteness: 0,
368
+ brokenLinks: 0,
369
+ orphanedDocuments: 0,
370
+ missingReferences: 0,
371
+ documentsByStatus: {},
372
+ documentsByTag: {},
373
+ };
374
+ // Track documents by status and tags
375
+ documents.forEach((doc) => {
376
+ // Track by status
377
+ if (doc.metadata?.status) {
378
+ const status = doc.metadata.status;
379
+ healthResult.documentsByStatus[status] =
380
+ (healthResult.documentsByStatus[status] || 0) + 1;
381
+ }
382
+ // Track by tags
383
+ if (doc.metadata?.tags && Array.isArray(doc.metadata.tags)) {
384
+ doc.metadata.tags.forEach((tag) => {
385
+ healthResult.documentsByTag[tag] =
386
+ (healthResult.documentsByTag[tag] || 0) + 1;
387
+ });
388
+ }
389
+ });
390
+ // Check metadata completeness
391
+ if (checkMetadata) {
392
+ let totalFields = 0;
393
+ let missingFields = 0;
394
+ for (const doc of documents) {
395
+ const metadata = doc.metadata || {};
396
+ for (const field of requiredMetadataFields) {
397
+ totalFields++;
398
+ if (!metadata[field]) {
399
+ missingFields++;
400
+ healthResult.issues.push({
401
+ path: doc.path,
402
+ type: "missing_metadata",
403
+ severity: "error",
404
+ message: `Missing required metadata field: ${field}`,
405
+ details: { field },
406
+ });
407
+ }
408
+ }
409
+ }
410
+ // Calculate metadata completeness percentage
411
+ healthResult.metadataCompleteness =
412
+ totalFields > 0
413
+ ? Math.round(((totalFields - missingFields) / totalFields) * 100)
414
+ : 100;
415
+ }
416
+ // Check for orphaned documents (not in navigation)
417
+ if (checkOrphans) {
418
+ // Completely disable orphaned documents check
419
+ console.log("Orphaned documents check is disabled");
420
+ healthResult.orphanedDocuments = 0;
421
+ // Ensure we don't have any orphaned document issues in the result
422
+ healthResult.issues = healthResult.issues.filter((issue) => issue.type !== "orphaned");
423
+ }
424
+ // Check for broken links
425
+ if (checkLinks) {
426
+ // Create a set of all valid document paths
427
+ const validPaths = new Set();
428
+ for (const doc of documents) {
429
+ validPaths.add(doc.path);
430
+ // Also add without .md extension
431
+ validPaths.add(doc.path.replace(/\.md$/, ""));
432
+ }
433
+ // Check each document for links
434
+ for (const doc of documents) {
435
+ try {
436
+ const content = await fs.readFile(doc.path, "utf-8");
437
+ // Find markdown links [text](link)
438
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
439
+ let match;
440
+ while ((match = linkRegex.exec(content)) !== null) {
441
+ const link = match[2];
442
+ // Only check internal links (not external URLs)
443
+ if (!link.startsWith("http://") && !link.startsWith("https://")) {
444
+ // Resolve the link relative to the document
445
+ const docDir = path.dirname(doc.path);
446
+ const resolvedPath = path.resolve(docDir, link);
447
+ // Check if the link target exists
448
+ if (!validPaths.has(resolvedPath) &&
449
+ !validPaths.has(resolvedPath + ".md")) {
450
+ healthResult.brokenLinks++;
451
+ healthResult.issues.push({
452
+ path: doc.path,
453
+ type: "broken_link",
454
+ severity: "error",
455
+ message: `Broken link: ${link}`,
456
+ details: { link, linkText: match[1] },
457
+ });
458
+ }
459
+ }
460
+ }
461
+ }
462
+ catch (error) {
463
+ // Skip files that can't be read
464
+ console.error(`Error reading file ${doc.path}:`, error);
465
+ }
466
+ }
467
+ }
468
+ // Calculate health score
469
+ // The score is based on:
470
+ // - Metadata completeness (70%)
471
+ // - No broken links (30%)
472
+ // - Orphaned documents check is disabled
473
+ const metadataScore = healthResult.metadataCompleteness * 0.7;
474
+ const brokenLinksScore = healthResult.brokenLinks === 0
475
+ ? 30
476
+ : Math.max(0, 30 - (healthResult.brokenLinks / healthResult.totalDocuments) * 100);
477
+ // Calculate the final score
478
+ healthResult.score = Math.round(metadataScore + brokenLinksScore);
479
+ // Create a clean result object to ensure proper JSON formatting
480
+ const finalResult = {
481
+ score: healthResult.score,
482
+ totalDocuments: healthResult.totalDocuments,
483
+ issues: healthResult.issues,
484
+ metadataCompleteness: healthResult.metadataCompleteness,
485
+ brokenLinks: healthResult.brokenLinks,
486
+ orphanedDocuments: 0,
487
+ missingReferences: healthResult.missingReferences,
488
+ documentsByStatus: healthResult.documentsByStatus,
489
+ documentsByTag: healthResult.documentsByTag,
490
+ };
491
+ return {
492
+ content: [
493
+ {
494
+ type: "text",
495
+ text: `Documentation health check completed. Overall health score: ${finalResult.score}%`,
496
+ },
497
+ ],
498
+ metadata: finalResult,
499
+ };
500
+ }
501
+ catch (error) {
502
+ return {
503
+ content: [
504
+ {
505
+ type: "text",
506
+ text: `Error checking documentation health: ${error.message}`,
507
+ },
508
+ ],
509
+ isError: true,
510
+ };
511
+ }
512
+ }
513
+ //# sourceMappingURL=docs.js.map