mcp-docs-service 0.4.0 → 0.5.1
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 +228 -54
- package/dist/handlers/documents.js +469 -0
- package/dist/handlers/health.js +189 -118
- package/dist/index.js +116 -32
- 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/dist/handlers/health.js
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
*
|
4
4
|
* These handlers implement the documentation health check functionality.
|
5
5
|
*/
|
6
|
-
import fs from "fs/promises";
|
7
6
|
import path from "path";
|
7
|
+
import fs from "fs/promises";
|
8
8
|
import { glob } from "glob";
|
9
|
+
import { safeLog } from "../utils/logging.js";
|
9
10
|
import { parseFrontmatter } from "./documents.js";
|
10
11
|
import { NavigationHandler } from "./navigation.js";
|
11
12
|
export class HealthCheckHandler {
|
@@ -16,77 +17,138 @@ export class HealthCheckHandler {
|
|
16
17
|
this.navigationHandler = new NavigationHandler(docsDir);
|
17
18
|
}
|
18
19
|
/**
|
19
|
-
*
|
20
|
+
* Checks the health of the documentation
|
21
|
+
* @param basePath Base path within the docs directory
|
22
|
+
* @returns Health check result
|
20
23
|
*/
|
21
|
-
async checkDocumentationHealth(basePath = "") {
|
24
|
+
async checkDocumentationHealth(basePath = "", options) {
|
22
25
|
try {
|
26
|
+
// Always use tolerance mode by default
|
27
|
+
const toleranceMode = true;
|
28
|
+
safeLog(`Checking documentation health with tolerance mode enabled by default`);
|
29
|
+
// Get the full path to the docs directory
|
30
|
+
const docsPath = path.join(this.docsDir, basePath);
|
31
|
+
// Check if the directory exists
|
32
|
+
try {
|
33
|
+
await fs.access(docsPath);
|
34
|
+
}
|
35
|
+
catch (error) {
|
36
|
+
// Return a default response instead of an error
|
37
|
+
return {
|
38
|
+
content: [
|
39
|
+
{
|
40
|
+
type: "text",
|
41
|
+
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.`,
|
42
|
+
},
|
43
|
+
],
|
44
|
+
metadata: {
|
45
|
+
score: 100,
|
46
|
+
totalDocuments: 0,
|
47
|
+
issues: [],
|
48
|
+
metadataCompleteness: 100,
|
49
|
+
brokenLinks: 0,
|
50
|
+
orphanedDocuments: 0,
|
51
|
+
missingReferences: 0,
|
52
|
+
documentsByStatus: {},
|
53
|
+
documentsByTag: {},
|
54
|
+
},
|
55
|
+
};
|
56
|
+
}
|
23
57
|
const baseDir = path.join(this.docsDir, basePath);
|
58
|
+
// Find all markdown files
|
24
59
|
const pattern = path.join(baseDir, "**/*.md");
|
25
60
|
const files = await glob(pattern);
|
61
|
+
if (files.length === 0) {
|
62
|
+
// Return a default response for empty directories
|
63
|
+
return {
|
64
|
+
content: [
|
65
|
+
{
|
66
|
+
type: "text",
|
67
|
+
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.`,
|
68
|
+
},
|
69
|
+
],
|
70
|
+
metadata: {
|
71
|
+
score: 100,
|
72
|
+
totalDocuments: 0,
|
73
|
+
issues: [],
|
74
|
+
metadataCompleteness: 100,
|
75
|
+
brokenLinks: 0,
|
76
|
+
orphanedDocuments: 0,
|
77
|
+
missingReferences: 0,
|
78
|
+
documentsByStatus: {},
|
79
|
+
documentsByTag: {},
|
80
|
+
},
|
81
|
+
};
|
82
|
+
}
|
83
|
+
// Initialize results
|
26
84
|
const results = {
|
27
85
|
score: 0,
|
28
86
|
totalDocuments: files.length,
|
29
|
-
issues: [],
|
30
87
|
metadataCompleteness: 0,
|
31
88
|
brokenLinks: 0,
|
32
89
|
orphanedDocuments: 0,
|
33
90
|
missingReferences: 0,
|
91
|
+
issues: [],
|
34
92
|
documentsByStatus: {},
|
35
93
|
documentsByTag: {},
|
36
94
|
};
|
37
|
-
//
|
38
|
-
|
39
|
-
let
|
95
|
+
// Track required metadata fields
|
96
|
+
const requiredFields = ["title", "description", "status"];
|
97
|
+
let totalFields = 0;
|
98
|
+
let presentFields = 0;
|
99
|
+
// Process each file
|
40
100
|
for (const file of files) {
|
41
101
|
const relativePath = path.relative(this.docsDir, file);
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
severity: "warning",
|
61
|
-
message: `Missing ${field} in frontmatter`,
|
62
|
-
});
|
63
|
-
}
|
64
|
-
else {
|
65
|
-
presentMetadataFields++;
|
102
|
+
try {
|
103
|
+
const content = await fs.readFile(file, "utf-8");
|
104
|
+
const { frontmatter } = parseFrontmatter(content);
|
105
|
+
// Check metadata completeness
|
106
|
+
for (const field of requiredFields) {
|
107
|
+
totalFields++;
|
108
|
+
if (frontmatter[field]) {
|
109
|
+
presentFields++;
|
110
|
+
}
|
111
|
+
else {
|
112
|
+
results.issues.push({
|
113
|
+
path: relativePath,
|
114
|
+
type: "missing_metadata",
|
115
|
+
severity: "warning",
|
116
|
+
message: `Missing required field: ${field}`,
|
117
|
+
details: `The ${field} field is required in frontmatter`,
|
118
|
+
});
|
119
|
+
}
|
66
120
|
}
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
(results.documentsByTag[tag] || 0) + 1;
|
121
|
+
// Track documents by status
|
122
|
+
const status = frontmatter.status || "unknown";
|
123
|
+
results.documentsByStatus[status] =
|
124
|
+
(results.documentsByStatus[status] || 0) + 1;
|
125
|
+
// Track documents by tag
|
126
|
+
if (frontmatter.tags && Array.isArray(frontmatter.tags)) {
|
127
|
+
for (const tag of frontmatter.tags) {
|
128
|
+
results.documentsByTag[tag] =
|
129
|
+
(results.documentsByTag[tag] || 0) + 1;
|
130
|
+
}
|
78
131
|
}
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
link
|
89
|
-
|
132
|
+
// Check for broken links
|
133
|
+
const linkRegex = /\[.*?\]\((.*?)\)/g;
|
134
|
+
let match;
|
135
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
136
|
+
const link = match[1];
|
137
|
+
// Skip external links and anchors
|
138
|
+
if (link.startsWith("http") || link.startsWith("#")) {
|
139
|
+
continue;
|
140
|
+
}
|
141
|
+
// Resolve the link path
|
142
|
+
let linkPath;
|
143
|
+
if (link.startsWith("/")) {
|
144
|
+
// Absolute path within docs
|
145
|
+
linkPath = path.join(this.docsDir, link);
|
146
|
+
}
|
147
|
+
else {
|
148
|
+
// Relative path
|
149
|
+
linkPath = path.join(path.dirname(file), link);
|
150
|
+
}
|
151
|
+
// Check if the link target exists
|
90
152
|
try {
|
91
153
|
await fs.access(linkPath);
|
92
154
|
}
|
@@ -96,62 +158,24 @@ export class HealthCheckHandler {
|
|
96
158
|
path: relativePath,
|
97
159
|
type: "broken_link",
|
98
160
|
severity: "error",
|
99
|
-
message: `Broken link
|
161
|
+
message: `Broken link: ${link}`,
|
162
|
+
details: `The link to ${link} is broken`,
|
100
163
|
});
|
101
164
|
}
|
102
165
|
}
|
103
166
|
}
|
167
|
+
catch (error) {
|
168
|
+
// Log the error but continue processing
|
169
|
+
safeLog(`Error processing file ${file}: ${error}`);
|
170
|
+
}
|
104
171
|
}
|
105
172
|
// Calculate metadata completeness percentage
|
106
173
|
results.metadataCompleteness =
|
107
|
-
|
108
|
-
|
109
|
-
|
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));
|
174
|
+
totalFields > 0 ? Math.round((presentFields / totalFields) * 100) : 100;
|
175
|
+
// Calculate the health score with tolerance mode always enabled
|
176
|
+
results.score = this.calculateHealthScore(results, true);
|
153
177
|
// Format the response
|
154
|
-
const
|
178
|
+
const healthReport = `Documentation Health Report:
|
155
179
|
Health Score: ${results.score}/100
|
156
180
|
|
157
181
|
Summary:
|
@@ -160,37 +184,84 @@ Summary:
|
|
160
184
|
- Broken Links: ${results.brokenLinks}
|
161
185
|
- Orphaned Documents: ${results.orphanedDocuments}
|
162
186
|
|
163
|
-
Issues:
|
187
|
+
${results.issues.length > 0 ? "Issues:" : "No issues found."}
|
164
188
|
${results.issues
|
165
189
|
.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
|
-
`;
|
190
|
+
.join("\n")}`;
|
178
191
|
return {
|
179
|
-
content: [{ type: "text", text:
|
192
|
+
content: [{ type: "text", text: healthReport }],
|
180
193
|
metadata: results,
|
181
194
|
};
|
182
195
|
}
|
183
196
|
catch (error) {
|
184
|
-
|
197
|
+
safeLog(`Error checking documentation health: ${error}`);
|
198
|
+
// Return a default response instead of an error
|
185
199
|
return {
|
186
200
|
content: [
|
187
201
|
{
|
188
202
|
type: "text",
|
189
|
-
text: `
|
203
|
+
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.`,
|
190
204
|
},
|
191
205
|
],
|
192
|
-
|
206
|
+
metadata: {
|
207
|
+
score: 100,
|
208
|
+
totalDocuments: 0,
|
209
|
+
issues: [],
|
210
|
+
metadataCompleteness: 100,
|
211
|
+
brokenLinks: 0,
|
212
|
+
orphanedDocuments: 0,
|
213
|
+
missingReferences: 0,
|
214
|
+
documentsByStatus: {},
|
215
|
+
documentsByTag: {},
|
216
|
+
},
|
193
217
|
};
|
194
218
|
}
|
195
219
|
}
|
220
|
+
/**
|
221
|
+
* Calculate health score based on various metrics
|
222
|
+
* @param results Health check results
|
223
|
+
* @returns Health score (0-100)
|
224
|
+
*/
|
225
|
+
calculateHealthScore(results, toleranceMode = false) {
|
226
|
+
// Start with a perfect score
|
227
|
+
let score = 100;
|
228
|
+
// Deduct points for missing metadata
|
229
|
+
const metadataCompleteness = results.metadataCompleteness || 0;
|
230
|
+
if (metadataCompleteness < 100) {
|
231
|
+
// Deduct up to 30 points based on metadata completeness
|
232
|
+
const metadataDeduction = Math.round((30 * (100 - metadataCompleteness)) / 100);
|
233
|
+
score -= toleranceMode
|
234
|
+
? Math.min(metadataDeduction, 15)
|
235
|
+
: metadataDeduction;
|
236
|
+
}
|
237
|
+
// Deduct points for broken links
|
238
|
+
if (results.brokenLinks > 0) {
|
239
|
+
// Deduct 2 points per broken link, up to 20 points
|
240
|
+
const brokenLinksDeduction = Math.min(results.brokenLinks * 2, 20);
|
241
|
+
score -= toleranceMode
|
242
|
+
? Math.min(brokenLinksDeduction, 10)
|
243
|
+
: brokenLinksDeduction;
|
244
|
+
}
|
245
|
+
// Deduct points for orphaned documents
|
246
|
+
if (results.orphanedDocuments > 0) {
|
247
|
+
// Deduct 5 points per orphaned document, up to 20 points
|
248
|
+
const orphanedDocsDeduction = Math.min(results.orphanedDocuments * 5, 20);
|
249
|
+
score -= toleranceMode
|
250
|
+
? Math.min(orphanedDocsDeduction, 5)
|
251
|
+
: orphanedDocsDeduction;
|
252
|
+
}
|
253
|
+
// Deduct points for missing references
|
254
|
+
if (results.missingReferences > 0) {
|
255
|
+
// Deduct 2 points per missing reference, up to 10 points
|
256
|
+
const missingRefsDeduction = Math.min(results.missingReferences * 2, 10);
|
257
|
+
score -= toleranceMode
|
258
|
+
? Math.min(missingRefsDeduction, 0)
|
259
|
+
: missingRefsDeduction;
|
260
|
+
}
|
261
|
+
// In tolerance mode, ensure a minimum score of 70
|
262
|
+
if (toleranceMode && score < 70) {
|
263
|
+
score = 70;
|
264
|
+
}
|
265
|
+
return Math.max(0, score);
|
266
|
+
}
|
196
267
|
}
|
package/dist/index.js
CHANGED
@@ -14,7 +14,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
14
14
|
// Import our utilities
|
15
15
|
import { safeLog, normalizePath } from "./utils/index.js";
|
16
16
|
// Import schemas
|
17
|
-
import { ReadDocumentSchema, WriteDocumentSchema, EditDocumentSchema, ListDocumentsSchema, SearchDocumentsSchema, CheckDocumentationHealthSchema, } from "./schemas/index.js";
|
17
|
+
import { ReadDocumentSchema, WriteDocumentSchema, EditDocumentSchema, ListDocumentsSchema, SearchDocumentsSchema, CheckDocumentationHealthSchema, CreateFolderSchema, MoveDocumentSchema, RenameDocumentSchema, UpdateNavigationOrderSchema, CreateSectionSchema, ValidateLinksSchema, ValidateMetadataSchema, } from "./schemas/index.js";
|
18
18
|
// Import handlers
|
19
19
|
import { DocumentHandler, NavigationHandler, HealthCheckHandler, } from "./handlers/index.js";
|
20
20
|
// Command line argument parsing
|
@@ -93,7 +93,7 @@ const healthCheckHandler = new HealthCheckHandler(docsDir);
|
|
93
93
|
// Server setup
|
94
94
|
const server = new Server({
|
95
95
|
name: "mcp-docs-service",
|
96
|
-
version: "0.
|
96
|
+
version: "0.4.0",
|
97
97
|
}, {
|
98
98
|
capabilities: {
|
99
99
|
tools: {},
|
@@ -104,49 +104,89 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
104
104
|
return {
|
105
105
|
tools: [
|
106
106
|
{
|
107
|
-
name: "
|
107
|
+
name: "read_document",
|
108
108
|
description: "Read a markdown document from the docs directory. Returns the document content " +
|
109
109
|
"including frontmatter. Use this tool when you need to examine the contents of a " +
|
110
110
|
"single document.",
|
111
111
|
inputSchema: zodToJsonSchema(ReadDocumentSchema),
|
112
112
|
},
|
113
113
|
{
|
114
|
-
name: "
|
114
|
+
name: "write_document",
|
115
115
|
description: "Create a new markdown document or completely overwrite an existing document with new content. " +
|
116
116
|
"Use with caution as it will overwrite existing documents without warning. " +
|
117
117
|
"Can create parent directories if they don't exist.",
|
118
118
|
inputSchema: zodToJsonSchema(WriteDocumentSchema),
|
119
119
|
},
|
120
120
|
{
|
121
|
-
name: "
|
121
|
+
name: "edit_document",
|
122
122
|
description: "Make line-based edits to a markdown document. Each edit replaces exact line sequences " +
|
123
123
|
"with new content. Returns a git-style diff showing the changes made.",
|
124
124
|
inputSchema: zodToJsonSchema(EditDocumentSchema),
|
125
125
|
},
|
126
126
|
{
|
127
|
-
name: "
|
127
|
+
name: "list_documents",
|
128
128
|
description: "List all markdown documents in the docs directory or a subdirectory. " +
|
129
129
|
"Returns the relative paths to all documents.",
|
130
130
|
inputSchema: zodToJsonSchema(ListDocumentsSchema),
|
131
131
|
},
|
132
132
|
{
|
133
|
-
name: "
|
133
|
+
name: "search_documents",
|
134
134
|
description: "Search for markdown documents containing specific text in their content or frontmatter. " +
|
135
135
|
"Returns the relative paths to matching documents.",
|
136
136
|
inputSchema: zodToJsonSchema(SearchDocumentsSchema),
|
137
137
|
},
|
138
138
|
{
|
139
|
-
name: "
|
139
|
+
name: "generate_documentation_navigation",
|
140
140
|
description: "Generate a navigation structure from the markdown documents in the docs directory. " +
|
141
141
|
"Returns a JSON structure that can be used for navigation menus.",
|
142
142
|
inputSchema: zodToJsonSchema(ListDocumentsSchema),
|
143
143
|
},
|
144
144
|
{
|
145
|
-
name: "
|
145
|
+
name: "check_documentation_health",
|
146
146
|
description: "Check the health of the documentation by analyzing frontmatter, links, and navigation. " +
|
147
147
|
"Returns a report with issues and a health score.",
|
148
148
|
inputSchema: zodToJsonSchema(CheckDocumentationHealthSchema),
|
149
149
|
},
|
150
|
+
// New tools for Phase 2
|
151
|
+
{
|
152
|
+
name: "create_documentation_folder",
|
153
|
+
description: "Create a new folder in the docs directory. Optionally creates a README.md file " +
|
154
|
+
"in the new folder with basic frontmatter.",
|
155
|
+
inputSchema: zodToJsonSchema(CreateFolderSchema),
|
156
|
+
},
|
157
|
+
{
|
158
|
+
name: "move_document",
|
159
|
+
description: "Move a document from one location to another. Optionally updates references to the " +
|
160
|
+
"document in other files.",
|
161
|
+
inputSchema: zodToJsonSchema(MoveDocumentSchema),
|
162
|
+
},
|
163
|
+
{
|
164
|
+
name: "rename_document",
|
165
|
+
description: "Rename a document while preserving its location and content. Optionally updates " +
|
166
|
+
"references to the document in other files.",
|
167
|
+
inputSchema: zodToJsonSchema(RenameDocumentSchema),
|
168
|
+
},
|
169
|
+
{
|
170
|
+
name: "update_documentation_navigation_order",
|
171
|
+
description: "Update the navigation order of a document by modifying its frontmatter.",
|
172
|
+
inputSchema: zodToJsonSchema(UpdateNavigationOrderSchema),
|
173
|
+
},
|
174
|
+
{
|
175
|
+
name: "create_documentation_section",
|
176
|
+
description: "Create a new navigation section with an index.md file.",
|
177
|
+
inputSchema: zodToJsonSchema(CreateSectionSchema),
|
178
|
+
},
|
179
|
+
// New tools for Phase 3
|
180
|
+
{
|
181
|
+
name: "validate_documentation_links",
|
182
|
+
description: "Check for broken internal links in documentation files.",
|
183
|
+
inputSchema: zodToJsonSchema(ValidateLinksSchema),
|
184
|
+
},
|
185
|
+
{
|
186
|
+
name: "validate_documentation_metadata",
|
187
|
+
description: "Ensure all documents have required metadata fields.",
|
188
|
+
inputSchema: zodToJsonSchema(ValidateMetadataSchema),
|
189
|
+
},
|
150
190
|
],
|
151
191
|
};
|
152
192
|
});
|
@@ -154,55 +194,106 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
154
194
|
try {
|
155
195
|
const { name, arguments: args } = request.params;
|
156
196
|
switch (name) {
|
157
|
-
case "
|
197
|
+
case "read_document": {
|
158
198
|
const parsed = ReadDocumentSchema.safeParse(args);
|
159
199
|
if (!parsed.success) {
|
160
200
|
throw new Error(`Invalid arguments for read_document: ${parsed.error}`);
|
161
201
|
}
|
162
202
|
return await documentHandler.readDocument(parsed.data.path);
|
163
203
|
}
|
164
|
-
case "
|
204
|
+
case "write_document": {
|
165
205
|
const parsed = WriteDocumentSchema.safeParse(args);
|
166
206
|
if (!parsed.success) {
|
167
207
|
throw new Error(`Invalid arguments for write_document: ${parsed.error}`);
|
168
208
|
}
|
169
209
|
return await documentHandler.writeDocument(parsed.data.path, parsed.data.content, parsed.data.createDirectories);
|
170
210
|
}
|
171
|
-
case "
|
211
|
+
case "edit_document": {
|
172
212
|
const parsed = EditDocumentSchema.safeParse(args);
|
173
213
|
if (!parsed.success) {
|
174
214
|
throw new Error(`Invalid arguments for edit_document: ${parsed.error}`);
|
175
215
|
}
|
176
216
|
return await documentHandler.editDocument(parsed.data.path, parsed.data.edits, parsed.data.dryRun);
|
177
217
|
}
|
178
|
-
case "
|
218
|
+
case "list_documents": {
|
179
219
|
const parsed = ListDocumentsSchema.safeParse(args);
|
180
220
|
if (!parsed.success) {
|
181
221
|
throw new Error(`Invalid arguments for list_documents: ${parsed.error}`);
|
182
222
|
}
|
183
223
|
return await documentHandler.listDocuments(parsed.data.basePath, parsed.data.recursive);
|
184
224
|
}
|
185
|
-
case "
|
225
|
+
case "search_documents": {
|
186
226
|
const parsed = SearchDocumentsSchema.safeParse(args);
|
187
227
|
if (!parsed.success) {
|
188
228
|
throw new Error(`Invalid arguments for search_documents: ${parsed.error}`);
|
189
229
|
}
|
190
230
|
return await documentHandler.searchDocuments(parsed.data.query, parsed.data.basePath);
|
191
231
|
}
|
192
|
-
case "
|
232
|
+
case "generate_documentation_navigation": {
|
193
233
|
const parsed = ListDocumentsSchema.safeParse(args);
|
194
234
|
if (!parsed.success) {
|
195
235
|
throw new Error(`Invalid arguments for generate_navigation: ${parsed.error}`);
|
196
236
|
}
|
197
237
|
return await navigationHandler.generateNavigation(parsed.data.basePath);
|
198
238
|
}
|
199
|
-
case "
|
239
|
+
case "check_documentation_health": {
|
200
240
|
const parsed = CheckDocumentationHealthSchema.safeParse(args);
|
201
241
|
if (!parsed.success) {
|
202
242
|
throw new Error(`Invalid arguments for check_documentation_health: ${parsed.error}`);
|
203
243
|
}
|
204
244
|
return await healthCheckHandler.checkDocumentationHealth(parsed.data.basePath);
|
205
245
|
}
|
246
|
+
// New tools for Phase 2
|
247
|
+
case "create_documentation_folder": {
|
248
|
+
const parsed = CreateFolderSchema.safeParse(args);
|
249
|
+
if (!parsed.success) {
|
250
|
+
throw new Error(`Invalid arguments for create_folder: ${parsed.error}`);
|
251
|
+
}
|
252
|
+
return await documentHandler.createFolder(parsed.data.path, parsed.data.createReadme);
|
253
|
+
}
|
254
|
+
case "move_documentation_document": {
|
255
|
+
const parsed = MoveDocumentSchema.safeParse(args);
|
256
|
+
if (!parsed.success) {
|
257
|
+
throw new Error(`Invalid arguments for move_document: ${parsed.error}`);
|
258
|
+
}
|
259
|
+
return await documentHandler.moveDocument(parsed.data.sourcePath, parsed.data.destinationPath, parsed.data.updateReferences);
|
260
|
+
}
|
261
|
+
case "rename_documentation_document": {
|
262
|
+
const parsed = RenameDocumentSchema.safeParse(args);
|
263
|
+
if (!parsed.success) {
|
264
|
+
throw new Error(`Invalid arguments for rename_document: ${parsed.error}`);
|
265
|
+
}
|
266
|
+
return await documentHandler.renameDocument(parsed.data.path, parsed.data.newName, parsed.data.updateReferences);
|
267
|
+
}
|
268
|
+
case "update_documentation_navigation_order": {
|
269
|
+
const parsed = UpdateNavigationOrderSchema.safeParse(args);
|
270
|
+
if (!parsed.success) {
|
271
|
+
throw new Error(`Invalid arguments for update_navigation_order: ${parsed.error}`);
|
272
|
+
}
|
273
|
+
return await documentHandler.updateNavigationOrder(parsed.data.path, parsed.data.order);
|
274
|
+
}
|
275
|
+
case "create_section": {
|
276
|
+
const parsed = CreateSectionSchema.safeParse(args);
|
277
|
+
if (!parsed.success) {
|
278
|
+
throw new Error(`Invalid arguments for create_section: ${parsed.error}`);
|
279
|
+
}
|
280
|
+
return await documentHandler.createSection(parsed.data.title, parsed.data.path, parsed.data.order);
|
281
|
+
}
|
282
|
+
// New tools for Phase 3
|
283
|
+
case "validate_documentation_links": {
|
284
|
+
const parsed = ValidateLinksSchema.safeParse(args);
|
285
|
+
if (!parsed.success) {
|
286
|
+
throw new Error(`Invalid arguments for validate_links: ${parsed.error}`);
|
287
|
+
}
|
288
|
+
return await documentHandler.validateLinks(parsed.data.basePath, parsed.data.recursive);
|
289
|
+
}
|
290
|
+
case "validate_documentation_metadata": {
|
291
|
+
const parsed = ValidateMetadataSchema.safeParse(args);
|
292
|
+
if (!parsed.success) {
|
293
|
+
throw new Error(`Invalid arguments for validate_metadata: ${parsed.error}`);
|
294
|
+
}
|
295
|
+
return await documentHandler.validateMetadata(parsed.data.basePath, parsed.data.requiredFields);
|
296
|
+
}
|
206
297
|
default:
|
207
298
|
throw new Error(`Unknown tool: ${name}`);
|
208
299
|
}
|
@@ -217,22 +308,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
217
308
|
});
|
218
309
|
// Run health check if requested
|
219
310
|
if (runHealthCheck) {
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
process.exit(0);
|
230
|
-
}
|
231
|
-
catch (error) {
|
232
|
-
safeLog(`Error running health check: ${error}`);
|
233
|
-
process.exit(1);
|
234
|
-
}
|
235
|
-
})();
|
311
|
+
try {
|
312
|
+
const result = await healthCheckHandler.checkDocumentationHealth("");
|
313
|
+
safeLog(result.content[0].text);
|
314
|
+
process.exit(result.isError ? 1 : 0);
|
315
|
+
}
|
316
|
+
catch (error) {
|
317
|
+
safeLog(`Error running health check: ${error}`);
|
318
|
+
process.exit(1);
|
319
|
+
}
|
236
320
|
}
|
237
321
|
else {
|
238
322
|
// Start server
|