docs-agent 1.1.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/src/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import 'dotenv/config';
2
+ import api from './api.js';
3
+ import mcp from './mcp.js';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ if(process.env.API_DISABLED !== 'true'){
8
+ api.start(process.env.PORT || 3001);
9
+ }
10
+
11
+ if(process.env.MCP_DISABLED !== 'true'){
12
+ // Disable console logs because they write to std output stream which mcp client listens to
13
+ configureLogsForMcp();
14
+ // setupPersistentLogs('mcp.log');
15
+ mcp.start();
16
+ }
17
+
18
+ function configureLogsForMcp(){
19
+ console.log = console.error;
20
+ // console.error = () => {};
21
+ console.debug = console.error;
22
+ console.info = console.error;
23
+ console.warn = console.error;
24
+ }
25
+
26
+ function setupPersistentLogs(filename){
27
+ // Override console methods
28
+ console.log = logToFile;
29
+ console.error = logToFile;
30
+ console.warn = logToFile;
31
+ console.info = logToFile;
32
+ console.debug = logToFile;
33
+ }
34
+
35
+ function logToFile(...args) {
36
+ const logFile = path.join(process.cwd(), filename);
37
+ const logStream = fs.createWriteStream(logFile, { flags: 'a' });
38
+ const message = args?.map(arg => {
39
+ if (typeof arg === 'object') {
40
+ try {
41
+ return JSON.stringify(arg);
42
+ } catch {
43
+ return '[Circular]';
44
+ }
45
+ }
46
+ return String(arg);
47
+ })?.join(' ') + '\n';
48
+ logStream.write(message);
49
+ }
package/src/lib.js ADDED
@@ -0,0 +1,4 @@
1
+ import DocsAgent from "./DocsAgent.js";
2
+
3
+ export { DocsAgent };
4
+ export default DocsAgent;
package/src/mcp.js ADDED
@@ -0,0 +1,268 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import DocsAgent from "./DocsAgent.js";
5
+ import PROMPT_FOR_RELATED_FILES from "./config/prompt.relatedfiles.js";
6
+
7
+ const mcpServer = new McpServer({
8
+ name: "docs-agent",
9
+ version: "1.4.0",
10
+ });
11
+
12
+ /**
13
+ * Tool to review docs quality following Diataxis framework
14
+ * @param {string} content - The complete docs page content to review
15
+ * @param {object} context - The context of the docs page
16
+ * @param {string} context.filename - The filename of the docs page
17
+ * @param {string} context.projectStructure - The project structure as folders/files hierarchy
18
+ * @param {string} context.relatedFilesContent - The contents of the related docs pages to the current page
19
+ * @returns {object} mcpResponse - The review results
20
+ * @property {object[]} mcpResponse.content - The review results
21
+ * @property {string} mcpResponse.content[].type - The type of the content e.g. text, code, image, etc.
22
+ * @property {string} mcpResponse.content[].text - The review results
23
+ * @example
24
+ * ```json
25
+ * {
26
+ * "content": [{ "type": "text", "text": "The review results" }]
27
+ * }
28
+ * ```
29
+ */
30
+ const reviewDocs = mcpServer.tool("reviewDocs",
31
+ "Review the docs page and return the review results.\n"+
32
+ "This tool reviews a specific docs page, and returns the review results.\n"+
33
+ "The projectStructure parameter is optional, but it is recommended to provide it to help the agent understand the project structure and improve the results.\n"+
34
+ "The relatedFiles parameter is optional, but it is recommended to improve the results.\n"+
35
+ PROMPT_FOR_RELATED_FILES + "\n",
36
+ {
37
+ filepath: z.string().describe("Absolute filepath of the docs page to improve"),
38
+ filename: z.string().describe("The relative path of the docs page from the root of the project").optional(),
39
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy").optional(),
40
+ customInstructions: z.string().describe("Custom instructions for the docs improvement process. Use this parameter cautiously as it might lead to limited scope of the changes.").optional(),
41
+ glossaryFile: z.string().describe("Filepath of the glossary file, containing a list of project-specific terms and their definitions, used for consistent and technically accurate documentation").optional(),
42
+ relatedFiles: z.array(z.string()).describe("Absolute filepaths of the docs pages that are related to the current page being improved. Usually the related files are the ones mentioned in the docs page content or the ones that are located under the same parent folder as the current page and related to the same topic. Order is important, the most relevant files should be the initial items in the array. Optional, but always recommened to at least include few related files.").optional(),
43
+ allowDisruptiveChanges: z.boolean().describe("Whether to allow disruptive changes such as restructuring the project files/folder structure").optional().default(false)
44
+ },
45
+ async (params) => {
46
+ log(`reviewDocs triggered for ${params?.filepath}`);
47
+ log(`Additional context ${params?.filename} ${params?.projectStructure}`);
48
+ const docsAgent = new DocsAgent('mcp');
49
+ const response = await docsAgent.review(null, {
50
+ filepath: params?.filepath,
51
+ filename: params?.filename,
52
+ projectStructure: params?.projectStructure,
53
+ customInstructions: params?.customInstructions,
54
+ glossaryFile: params?.glossaryFile,
55
+ relatedFiles: params?.relatedFiles,
56
+ allowDisruptiveChanges: params?.allowDisruptiveChanges
57
+ });
58
+ log(`Review results: ${response}`);
59
+ return {
60
+ content: [{ type: "text", text: String(response) }]
61
+ };
62
+ }
63
+ );
64
+
65
+ /**
66
+ * Tool to improve docs quality following Diataxis framework
67
+ * @param {string} content - The complete docs page content to improve
68
+ * @param {object} context - The context of the docs page
69
+ * @param {string} context.filename - The filename of the docs page
70
+ * @param {string} context.projectStructure - The project structure as folders/files hierarchy
71
+ * @param {string} context.relatedFilesContent - The contents of the related docs pages to the current page
72
+ * @returns {object} mcpResponse - The edited docs content
73
+ * @property {object[]} mcpResponse.content - The edited docs content
74
+ * @property {string} mcpResponse.content[].type - The type of the content e.g. text, code, image, etc.
75
+ * @property {string} mcpResponse.content[].text - The edited docs content
76
+ * @example
77
+ * ```json
78
+ * {
79
+ * "content": [{ "type": "text", "text": "The edited docs content" }]
80
+ * }
81
+ * ```
82
+ */
83
+ const improveDocsTool = mcpServer.tool("improveDocs",
84
+ "Improve docs quality following Diataxis framework, returns the edited docs content.\n"+
85
+ "This tool reviews a specific docs page, prioritizes the changes to be made, and returns the edited docs content.\n"+
86
+ "The projectStructure parameter is optional, but it is recommended to provide it to help the agent understand the project structure and improve the results.\n"+
87
+ "The relatedFiles parameter is optional, but it is recommended to improve the results.\n"+
88
+ "This tool returns the complete edited docs content, do not run editDocs tool again after this tool.\n"+
89
+ PROMPT_FOR_RELATED_FILES + "\n",
90
+ {
91
+ filepath: z.string().describe("Absolute filepath of the docs page to improve"),
92
+ filename: z.string().describe("The relative path of the docs page from the root of the project").optional(),
93
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy").optional(),
94
+ customInstructions: z.string().describe("Custom instructions for the docs improvement process. Use this parameter cautiously as it might lead to limited scope of the changes.").optional(),
95
+ glossaryFile: z.string().describe("Filepath of the glossary file, containing a list of project-specific terms and their definitions, used for consistent and technically accurate documentation").optional(),
96
+ relatedFiles: z.array(z.string()).describe("Absolute filepaths of the docs pages that are related to the current page being improved. Usually the related files are the ones mentioned in the docs page content or the ones that are located under the same parent folder as the current page and related to the same topic. Order is important, the most relevant files should be the initial items in the array. Optional, but always recommened to at least include few related files.").optional(),
97
+ allowDisruptiveChanges: z.boolean().describe("Whether to allow disruptive changes such as restructuring the project files/folder structure").optional().default(false)
98
+ },
99
+ async (params) => {
100
+ log(`improveDocs triggered for ${params?.filepath}`);
101
+ log(`Additional context ${params?.filename} ${params?.projectStructure}`);
102
+ const docsAgent = new DocsAgent('mcp');
103
+ const response = await docsAgent.reviewPrioritizeAndEdit(null, {
104
+ filepath: params?.filepath,
105
+ filename: params?.filename,
106
+ projectStructure: params?.projectStructure,
107
+ customInstructions: params?.customInstructions,
108
+ glossaryFile: params?.glossaryFile,
109
+ relatedFiles: params?.relatedFiles,
110
+ allowDisruptiveChanges: params?.allowDisruptiveChanges
111
+ });
112
+ log(`Edited docs content: ${response}`);
113
+ return {
114
+ content: [{ type: "text", text: String(response) }]
115
+ };
116
+ }
117
+ );
118
+
119
+ const editDocsTool = mcpServer.tool("editDocs",
120
+ "Edit the docs content according to the given specific editing instructions\n"+
121
+ "The edit plan parameter must be a detailed plan of the changes to be made to the documentation.\n"+
122
+ "This is not the right tool to choose when the goal also includes finding the gaps in the documentation. For the entire task to review and edit the docs, use the improveDocs tool instead.\n"+
123
+ PROMPT_FOR_RELATED_FILES + "\n",
124
+ {
125
+ filepath: z.string().describe("Absolute filepath of the docs page to edit"),
126
+ editPlan: z.string().describe("Specific changes to be made to the documentation. It must be highly detailed, specific, and with necessary citations and reasoning to support the changes."),
127
+ filename: z.string().describe("The relative path of the docs page from the root of the project").optional(),
128
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy").optional(),
129
+ glossaryFile: z.string().describe("Filepath of the glossary file, containing a list of project-specific terms and their definitions, used for consistent and technically accurate documentation").optional(),
130
+ relatedFiles: z.array(z.string()).describe("Absolute filepaths of the docs pages that are related to the current page being reviewed/edited. Usually the related files are the ones mentioned in the docs page content or the ones that are located within the same parent folder as the current page and related to the same topic.").optional(),
131
+ customInstructions: z.string().describe("Custom instructions for the docs editing process").optional(),
132
+ },
133
+ async (params) => {
134
+ log(`editDocs triggered for ${params?.filepath}`);
135
+ const docsAgent = new DocsAgent('mcp');
136
+ const response = await docsAgent.edit(null, params?.editPlan, {
137
+ filepath: params?.filepath,
138
+ filename: params?.filename,
139
+ projectStructure: params?.projectStructure,
140
+ glossaryFile: params?.glossaryFile,
141
+ relatedFiles: params?.relatedFiles
142
+ },
143
+ params?.customInstructions
144
+ );
145
+ log(`Edited docs content: ${response}`);
146
+ return {
147
+ content: [{ type: "text", text: String(response) }]
148
+ };
149
+ }
150
+ );
151
+
152
+ const linkifyDocsTool = mcpServer.tool("linkifyDocs",
153
+ "Improve internal linking of the docs page to the related files and glossary concepts.\n"+
154
+ "Finds broken links, missing links, and adds internal links to the related files and glossary concepts.\n"+
155
+ "Including projectStructure and glossaryFile in the context will help improve the results dramatically.\n"+
156
+ "This tool returns the complete docs content with improved linking, do not run editDocs tool again after this tool.\n"+
157
+ PROMPT_FOR_RELATED_FILES + "\n",
158
+ {
159
+ filepath: z.string().describe("Absolute filepath of the docs page in which we need to add internal links"),
160
+ filename: z.string().describe("The relative path of the docs page from the root of the project").optional(),
161
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy").optional(),
162
+ customInstructions: z.string().describe("Custom instructions for the docs internal linking process").optional(),
163
+ glossaryFile: z.string().describe("Filepath of the glossary file, containing a list of project-specific terms and their definitions, used for consistent and technically accurate documentation").optional(),
164
+ relatedFiles: z.array(z.string()).describe("Absolute filepaths of the docs pages that are related to the current page being reviewed/edited. Usually the related files are the ones mentioned in the docs page content or the ones that are located within the same parent folder as the current page and related to the same topic.").optional()
165
+ },
166
+ async (params) => {
167
+ log(`linkifyDocs triggered for ${params?.filepath}`);
168
+ const docsAgent = new DocsAgent('mcp');
169
+ const response = await docsAgent.linkify(null, { filepath: params?.filepath,
170
+ filename: params?.filename,
171
+ projectStructure: params?.projectStructure,
172
+ customInstructions: params?.customInstructions,
173
+ glossaryFile: params?.glossaryFile,
174
+ relatedFiles: params?.relatedFiles
175
+ });
176
+ log(`Linked docs content: ${response}`);
177
+ return {
178
+ content: [{ type: "text", text: String(response) }]
179
+ };
180
+ }
181
+ );
182
+
183
+ const auditDocsAgainstCodeTool = mcpServer.tool("auditDocsAgainstCode",
184
+ "Audit the docs against the source code to find the incorrect or outdated information. This tool checks the reference source code and verifies if the docs match the implementation in the codebase. It can also find missing critical technical information in the docs necessary for the goals of that particular docs page. This will take a really long time, sometimes even more than 10 minutes.",
185
+ {
186
+ docsContentFilePath: z.string().describe("Absolute filepath of the docs page to audit"),
187
+ repoUrl: z.string().describe("GitHub repository URL for the reference source code for the code audit"),
188
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy of the repository").optional(),
189
+ customInstructions: z.string().describe("Custom instructions for the audit process").optional(),
190
+ },
191
+ async (params) => {
192
+ log(`auditDocsAgainstCode triggered for ${params?.docsContentFilePath}`);
193
+ const docsAgent = new DocsAgent('mcp');
194
+ const response = await docsAgent.auditDocsAgainstCode(
195
+ params?.docsContentFilePath,
196
+ {
197
+ repoUrl: params?.repoUrl,
198
+ projectStructure: params?.projectStructure
199
+ },
200
+ params?.customInstructions
201
+ );
202
+ log(`Audited docs against code: ${response}`);
203
+ return {
204
+ content: [{ type: "text", text: String(response) }]
205
+ };
206
+ }
207
+ );
208
+
209
+ /**
210
+ * @experimental This tool is experimental and might be dropped in the future.
211
+ */
212
+ const generateReferenceDocsTool = mcpServer.tool("generateReferenceDocs",
213
+ "Generate reference docs for the given public interface file or GitHub repository\n"+
214
+ PROMPT_FOR_RELATED_FILES + "\n",
215
+ {
216
+ referenceSourceCodeFiles: z.array(z.string()).describe("Remote URLs of the necessary source code files to generate reference docs e.g. [\"https://raw.githubusercontent.com/rudderlabs/analytics-go/refs/heads/master/analytics.go\", \"https://raw.githubusercontent.com/rudderlabs/analytics-go/refs/heads/master/config.go\"]"),
217
+ repoUrl: z.string().describe("GitHub repository URL").optional(),
218
+ interfaceType: z.enum(["library", "http", "mcp", "cli", "rpc", "other"]).describe("Type of reference docs, how the code exposes its interfaces to the outside world").optional(),
219
+ relatedFileUrls: z.array(z.string()).describe("Remote URLs of the code files that are related to the given mainPublicInterfaceFileUrl").optional(),
220
+ projectStructure: z.string().describe("A nested markdown list of the project structure showing the folders/files hierarchy of the repository").optional(),
221
+ customInstructions: z.string().describe("Custom instructions for the reference docs generation process").optional(),
222
+ knowledgeBase: z.string().describe("Knowledge base such as higher level architecture of the parent project, this is only to develop understanding of the context of the current codebase. Do not include this in the reference docs, this is already published on docs website").optional(),
223
+ },
224
+ async (params) => {
225
+ log(`generateReferenceDocs triggered for ${params?.referenceSourceCodeFiles}`);
226
+ const docsAgent = new DocsAgent('mcp');
227
+ const response = await docsAgent.generateReferenceDocs(params?.referenceSourceCodeFiles, {
228
+ interfaceType: params?.interfaceType,
229
+ relatedFileUrls: params?.relatedFileUrls,
230
+ repoUrl: params?.repoUrl,
231
+ projectStructure: params?.projectStructure,
232
+ knowledgeBase: params?.knowledgeBase
233
+ }, params?.customInstructions);
234
+ log(`Generated reference docs: ${response}`);
235
+ return {
236
+ content: [{ type: "text", text: String(response) }]
237
+ };
238
+ }
239
+ ).disable();
240
+
241
+ async function start() {
242
+ // Start receiving messages on stdin and sending messages on stdout
243
+ const transport = new StdioServerTransport();
244
+ await mcpServer.connect(transport);
245
+ log("MCP Server started successfully");
246
+ log("reviewDocs tool " + (reviewDocs?.enabled ? "enabled" : "disabled") + "\nMetadata: "+reviewDocs?.metadata);
247
+ log("improveDocs tool " + (improveDocsTool?.enabled ? "enabled" : "disabled") + "\nMetadata: "+improveDocsTool?.metadata);
248
+ log("editDocs tool " + (editDocsTool?.enabled ? "enabled" : "disabled") + "\nMetadata: "+editDocsTool?.metadata);
249
+ log("linkifyDocs tool " + (linkifyDocsTool?.enabled ? "enabled" : "disabled") + "\nMetadata: "+linkifyDocsTool?.metadata);
250
+ log("auditDocsAgainstCode tool " + (auditDocsAgainstCodeTool?.enabled ? "enabled" : "disabled") + "\nMetadata: "+auditDocsAgainstCodeTool?.metadata);
251
+ log("generateReferenceDocs tool " + (generateReferenceDocsTool?.enabled ? "enabled" : "disabled") + "\nMetadata: "+generateReferenceDocsTool?.metadata);
252
+ }
253
+
254
+ function log(message, level = "info"){
255
+ if(level === "error"){
256
+ console.error(message);
257
+ } else {
258
+ console.log(message);
259
+ }
260
+ if(process.env.NODE_ENV === "production"){
261
+ // Do not send logs to client in production
262
+ return;
263
+ }
264
+ //TODO: Send logs to client.
265
+ // `mcpServer.server.sendLoggingMessage` is throwing error as of now - "server does not support logging"
266
+ }
267
+
268
+ export default { start };