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,196 @@
1
+ /**
2
+ * Health check handlers for the MCP Docs Service
3
+ *
4
+ * These handlers implement the documentation health check functionality.
5
+ */
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { glob } from "glob";
9
+ import { parseFrontmatter } from "./documents.js";
10
+ import { NavigationHandler } from "./navigation.js";
11
+ export class HealthCheckHandler {
12
+ docsDir;
13
+ navigationHandler;
14
+ constructor(docsDir) {
15
+ this.docsDir = docsDir;
16
+ this.navigationHandler = new NavigationHandler(docsDir);
17
+ }
18
+ /**
19
+ * Check documentation health
20
+ */
21
+ async checkDocumentationHealth(basePath = "") {
22
+ try {
23
+ const baseDir = path.join(this.docsDir, basePath);
24
+ const pattern = path.join(baseDir, "**/*.md");
25
+ const files = await glob(pattern);
26
+ const results = {
27
+ score: 0,
28
+ totalDocuments: files.length,
29
+ issues: [],
30
+ metadataCompleteness: 0,
31
+ brokenLinks: 0,
32
+ orphanedDocuments: 0,
33
+ missingReferences: 0,
34
+ documentsByStatus: {},
35
+ documentsByTag: {},
36
+ };
37
+ // Check frontmatter and content
38
+ let totalMetadataFields = 0;
39
+ let presentMetadataFields = 0;
40
+ for (const file of files) {
41
+ const relativePath = path.relative(this.docsDir, file);
42
+ const content = await fs.readFile(file, "utf-8");
43
+ const { frontmatter } = parseFrontmatter(content);
44
+ // Check for required metadata
45
+ const requiredFields = ["title", "description"];
46
+ totalMetadataFields += requiredFields.length;
47
+ if (Object.keys(frontmatter).length === 0) {
48
+ results.issues.push({
49
+ path: relativePath,
50
+ type: "missing_metadata",
51
+ severity: "error",
52
+ message: "Missing frontmatter",
53
+ });
54
+ }
55
+ for (const field of requiredFields) {
56
+ if (!frontmatter[field]) {
57
+ results.issues.push({
58
+ path: relativePath,
59
+ type: "missing_metadata",
60
+ severity: "warning",
61
+ message: `Missing ${field} in frontmatter`,
62
+ });
63
+ }
64
+ else {
65
+ presentMetadataFields++;
66
+ }
67
+ }
68
+ // Track documents by status
69
+ if (frontmatter.status) {
70
+ results.documentsByStatus[frontmatter.status] =
71
+ (results.documentsByStatus[frontmatter.status] || 0) + 1;
72
+ }
73
+ // Track documents by tags
74
+ if (frontmatter.tags && Array.isArray(frontmatter.tags)) {
75
+ for (const tag of frontmatter.tags) {
76
+ results.documentsByTag[tag] =
77
+ (results.documentsByTag[tag] || 0) + 1;
78
+ }
79
+ }
80
+ // Check for internal links
81
+ const linkRegex = /\[.*?\]\((.*?)\)/g;
82
+ let match;
83
+ while ((match = linkRegex.exec(content)) !== null) {
84
+ const link = match[1];
85
+ // Only check relative links to markdown files
86
+ if (!link.startsWith("http") &&
87
+ !link.startsWith("#") &&
88
+ link.endsWith(".md")) {
89
+ const linkPath = path.join(path.dirname(file), link);
90
+ try {
91
+ await fs.access(linkPath);
92
+ }
93
+ catch {
94
+ results.brokenLinks++;
95
+ results.issues.push({
96
+ path: relativePath,
97
+ type: "broken_link",
98
+ severity: "error",
99
+ message: `Broken link to ${link}`,
100
+ });
101
+ }
102
+ }
103
+ }
104
+ }
105
+ // Calculate metadata completeness percentage
106
+ results.metadataCompleteness =
107
+ totalMetadataFields > 0
108
+ ? Math.round((presentMetadataFields / totalMetadataFields) * 100)
109
+ : 100;
110
+ // Generate navigation to check for orphaned documents
111
+ const navResponse = await this.navigationHandler.generateNavigation(basePath);
112
+ if (!navResponse.isError && navResponse.content[0].text) {
113
+ const navigation = JSON.parse(navResponse.content[0].text);
114
+ function collectPaths(items) {
115
+ let paths = [];
116
+ for (const item of items) {
117
+ if (item.path) {
118
+ paths.push(item.path);
119
+ }
120
+ if (item.children && item.children.length > 0) {
121
+ paths = paths.concat(collectPaths(item.children));
122
+ }
123
+ }
124
+ return paths;
125
+ }
126
+ const navigationPaths = collectPaths(navigation);
127
+ for (const file of files) {
128
+ const relativePath = path.relative(this.docsDir, file);
129
+ if (!navigationPaths.includes(relativePath)) {
130
+ results.orphanedDocuments++;
131
+ results.issues.push({
132
+ path: relativePath,
133
+ type: "orphaned",
134
+ severity: "warning",
135
+ message: "Orphaned document (not in navigation)",
136
+ });
137
+ }
138
+ }
139
+ }
140
+ // Calculate health score (0-100)
141
+ const issueWeights = {
142
+ missing_metadata: 1,
143
+ broken_link: 2,
144
+ orphaned: 1,
145
+ missing_reference: 1,
146
+ };
147
+ let weightedIssueCount = 0;
148
+ for (const issue of results.issues) {
149
+ weightedIssueCount += issueWeights[issue.type] || 1;
150
+ }
151
+ const maxIssues = results.totalDocuments * 5; // 5 possible issues per document
152
+ results.score = Math.max(0, 100 - Math.round((weightedIssueCount / maxIssues) * 100));
153
+ // Format the response
154
+ const formattedResponse = `Documentation Health Report:
155
+ Health Score: ${results.score}/100
156
+
157
+ Summary:
158
+ - Total Documents: ${results.totalDocuments}
159
+ - Metadata Completeness: ${results.metadataCompleteness}%
160
+ - Broken Links: ${results.brokenLinks}
161
+ - Orphaned Documents: ${results.orphanedDocuments}
162
+
163
+ Issues:
164
+ ${results.issues
165
+ .map((issue) => `- ${issue.path}: ${issue.message} (${issue.severity})`)
166
+ .join("\n")}
167
+
168
+ Document Status:
169
+ ${Object.entries(results.documentsByStatus)
170
+ .map(([status, count]) => `- ${status}: ${count}`)
171
+ .join("\n") || "- No status information available"}
172
+
173
+ Document Tags:
174
+ ${Object.entries(results.documentsByTag)
175
+ .map(([tag, count]) => `- ${tag}: ${count}`)
176
+ .join("\n") || "- No tag information available"}
177
+ `;
178
+ return {
179
+ content: [{ type: "text", text: formattedResponse }],
180
+ metadata: results,
181
+ };
182
+ }
183
+ catch (error) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ return {
186
+ content: [
187
+ {
188
+ type: "text",
189
+ text: `Error checking documentation health: ${errorMessage}`,
190
+ },
191
+ ],
192
+ isError: true,
193
+ };
194
+ }
195
+ }
196
+ }
@@ -0,0 +1 @@
1
+ export * from "./docs.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Handlers index for the MCP Docs Service
3
+ *
4
+ * This file exports all handler classes.
5
+ */
6
+ export * from "./documents.js";
7
+ export * from "./navigation.js";
8
+ export * from "./health.js";
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,cAAc,WAAW,CAAC"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Navigation handlers for the MCP Docs Service
3
+ *
4
+ * These handlers implement the navigation structure generation.
5
+ */
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { glob } from "glob";
9
+ import { parseFrontmatter } from "./documents.js";
10
+ export class NavigationHandler {
11
+ docsDir;
12
+ constructor(docsDir) {
13
+ this.docsDir = docsDir;
14
+ }
15
+ /**
16
+ * Generate navigation structure from documents
17
+ */
18
+ async generateNavigation(basePath = "") {
19
+ try {
20
+ const baseDir = path.join(this.docsDir, basePath);
21
+ const pattern = path.join(baseDir, "**/*.md");
22
+ const files = await glob(pattern);
23
+ // Sort files to ensure consistent order and process index.md files first
24
+ files.sort((a, b) => {
25
+ const aIsIndex = path.basename(a) === "index.md";
26
+ const bIsIndex = path.basename(b) === "index.md";
27
+ if (aIsIndex && !bIsIndex)
28
+ return -1;
29
+ if (!aIsIndex && bIsIndex)
30
+ return 1;
31
+ return a.localeCompare(b);
32
+ });
33
+ const navigation = [];
34
+ const directoryMap = {};
35
+ for (const file of files) {
36
+ const relativePath = path.relative(this.docsDir, file);
37
+ const content = await fs.readFile(file, "utf-8");
38
+ const { frontmatter } = parseFrontmatter(content);
39
+ const title = frontmatter.title || path.basename(file, ".md");
40
+ const order = frontmatter.order !== undefined ? Number(frontmatter.order) : 999;
41
+ const item = {
42
+ title,
43
+ path: relativePath,
44
+ order,
45
+ children: [],
46
+ };
47
+ const dirPath = path.dirname(relativePath);
48
+ if (dirPath === "." || dirPath === basePath) {
49
+ navigation.push(item);
50
+ }
51
+ else {
52
+ // Create parent directories if they don't exist in the navigation
53
+ const pathParts = dirPath.split(path.sep);
54
+ let currentPath = "";
55
+ let currentNavigation = navigation;
56
+ for (const part of pathParts) {
57
+ currentPath = currentPath ? path.join(currentPath, part) : part;
58
+ if (!directoryMap[currentPath]) {
59
+ const dirItem = {
60
+ title: part,
61
+ path: currentPath,
62
+ order: 0,
63
+ children: [],
64
+ };
65
+ directoryMap[currentPath] = dirItem;
66
+ currentNavigation.push(dirItem);
67
+ }
68
+ currentNavigation = directoryMap[currentPath].children;
69
+ }
70
+ currentNavigation.push(item);
71
+ }
72
+ }
73
+ // Sort navigation items by order
74
+ function sortNavigation(items) {
75
+ items.sort((a, b) => a.order - b.order);
76
+ for (const item of items) {
77
+ if (item.children && item.children.length > 0) {
78
+ sortNavigation(item.children);
79
+ }
80
+ }
81
+ }
82
+ sortNavigation(navigation);
83
+ return {
84
+ content: [{ type: "text", text: JSON.stringify(navigation, null, 2) }],
85
+ };
86
+ }
87
+ catch (error) {
88
+ const errorMessage = error instanceof Error ? error.message : String(error);
89
+ return {
90
+ content: [
91
+ {
92
+ type: "text",
93
+ text: `Error generating navigation: ${errorMessage}`,
94
+ },
95
+ ],
96
+ isError: true,
97
+ };
98
+ }
99
+ }
100
+ /**
101
+ * Save navigation structure to a file
102
+ */
103
+ async saveNavigation(basePath = "", outputPath = "navigation.json") {
104
+ try {
105
+ const result = await this.generateNavigation(basePath);
106
+ if (result.isError) {
107
+ return result;
108
+ }
109
+ const navigation = JSON.parse(result.content[0].text);
110
+ const outputFilePath = path.join(this.docsDir, outputPath);
111
+ await fs.writeFile(outputFilePath, JSON.stringify(navigation, null, 2), "utf-8");
112
+ return {
113
+ content: [
114
+ { type: "text", text: `Navigation structure saved to ${outputPath}` },
115
+ ],
116
+ };
117
+ }
118
+ catch (error) {
119
+ const errorMessage = error instanceof Error ? error.message : String(error);
120
+ return {
121
+ content: [
122
+ { type: "text", text: `Error saving navigation: ${errorMessage}` },
123
+ ],
124
+ isError: true,
125
+ };
126
+ }
127
+ }
128
+ }