@imenam/mcp-documentor 1.0.23
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 +74 -0
- package/dist/shared/schemas/doc.schema.d.ts +112 -0
- package/dist/shared/schemas/doc.schema.js +83 -0
- package/dist/src/core/embedding-engine.d.ts +23 -0
- package/dist/src/core/embedding-engine.js +119 -0
- package/dist/src/core/embedding-store.d.ts +15 -0
- package/dist/src/core/embedding-store.js +97 -0
- package/dist/src/core/migrate-embeddings.d.ts +3 -0
- package/dist/src/core/migrate-embeddings.js +30 -0
- package/dist/src/core/migrate-folders.d.ts +2 -0
- package/dist/src/core/migrate-folders.js +45 -0
- package/dist/src/core/storage-engine.d.ts +29 -0
- package/dist/src/core/storage-engine.js +191 -0
- package/dist/src/gui-worker.d.ts +1 -0
- package/dist/src/gui-worker.js +349 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +678 -0
- package/dist/src/tools/check-freshness.d.ts +18 -0
- package/dist/src/tools/check-freshness.js +167 -0
- package/dist/src/tools/check-links.d.ts +14 -0
- package/dist/src/tools/check-links.js +99 -0
- package/dist/src/tools/create-doc.d.ts +15 -0
- package/dist/src/tools/create-doc.js +78 -0
- package/dist/src/tools/delete-doc.d.ts +15 -0
- package/dist/src/tools/delete-doc.js +18 -0
- package/dist/src/tools/get-doc-metadata.d.ts +14 -0
- package/dist/src/tools/get-doc-metadata.js +40 -0
- package/dist/src/tools/get-doc-tree.d.ts +14 -0
- package/dist/src/tools/get-doc-tree.js +64 -0
- package/dist/src/tools/get-doc.d.ts +14 -0
- package/dist/src/tools/get-doc.js +55 -0
- package/dist/src/tools/git-diff-doc.d.ts +14 -0
- package/dist/src/tools/git-diff-doc.js +93 -0
- package/dist/src/tools/list-docs.d.ts +14 -0
- package/dist/src/tools/list-docs.js +78 -0
- package/dist/src/tools/list-projects.d.ts +14 -0
- package/dist/src/tools/list-projects.js +16 -0
- package/dist/src/tools/move-doc.d.ts +15 -0
- package/dist/src/tools/move-doc.js +34 -0
- package/dist/src/tools/patch-doc.d.ts +15 -0
- package/dist/src/tools/patch-doc.js +37 -0
- package/dist/src/tools/refresh-file-snapshot.d.ts +14 -0
- package/dist/src/tools/refresh-file-snapshot.js +45 -0
- package/dist/src/tools/search-docs.d.ts +15 -0
- package/dist/src/tools/search-docs.js +26 -0
- package/dist/src/tools/set-project.d.ts +14 -0
- package/dist/src/tools/set-project.js +18 -0
- package/dist/src/tools/update-doc.d.ts +15 -0
- package/dist/src/tools/update-doc.js +66 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +2 -0
- package/gui/README.md +75 -0
- package/gui/dist/assets/index-BwyK1pan.css +1 -0
- package/gui/dist/assets/index-CzuFn4Pp.js +48 -0
- package/gui/dist/index.html +14 -0
- package/gui/dist/vite.svg +1 -0
- package/package.json +61 -0
- package/scripts/generate-version.js +20 -0
- package/scripts/migrate-links.js +258 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { APP_VERSION } from "./version.js";
|
|
10
|
+
import { GuiLauncher, createLogger, setupLogging } from "@imenam/mcp-gui-interface";
|
|
11
|
+
import { StorageEngine } from "./core/storage-engine.js";
|
|
12
|
+
import { EmbeddingStore } from "./core/embedding-store.js";
|
|
13
|
+
import { EmbeddingEngine } from "./core/embedding-engine.js";
|
|
14
|
+
import { createDoc } from "./tools/create-doc.js";
|
|
15
|
+
import { getDoc } from "./tools/get-doc.js";
|
|
16
|
+
import { listDocs } from "./tools/list-docs.js";
|
|
17
|
+
import { updateDoc } from "./tools/update-doc.js";
|
|
18
|
+
import { searchDocs } from "./tools/search-docs.js";
|
|
19
|
+
import { checkFreshness, computeFreshness } from "./tools/check-freshness.js";
|
|
20
|
+
import { setProject } from "./tools/set-project.js";
|
|
21
|
+
import { listProjects } from "./tools/list-projects.js";
|
|
22
|
+
import { refreshFileSnapshot } from "./tools/refresh-file-snapshot.js";
|
|
23
|
+
import { getDocMetadata } from "./tools/get-doc-metadata.js";
|
|
24
|
+
import { patchDoc } from "./tools/patch-doc.js";
|
|
25
|
+
import { gitDiffDoc } from "./tools/git-diff-doc.js";
|
|
26
|
+
import { checkLinks } from "./tools/check-links.js";
|
|
27
|
+
import { deleteDoc } from "./tools/delete-doc.js";
|
|
28
|
+
import { moveDoc } from "./tools/move-doc.js";
|
|
29
|
+
import { getDocTree } from "./tools/get-doc-tree.js";
|
|
30
|
+
import { migrateFolders } from "./core/migrate-folders.js";
|
|
31
|
+
import { migrateEmbeddings } from "./core/migrate-embeddings.js";
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = path.dirname(__filename);
|
|
34
|
+
function findProjectRoot(startDir) {
|
|
35
|
+
let current = startDir;
|
|
36
|
+
while (current !== path.dirname(current)) {
|
|
37
|
+
if (fs.existsSync(path.join(current, "package.json"))) {
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
current = path.dirname(current);
|
|
41
|
+
}
|
|
42
|
+
return startDir;
|
|
43
|
+
}
|
|
44
|
+
const rootDir = findProjectRoot(__dirname);
|
|
45
|
+
const packageEnvPath = path.join(rootDir, ".env");
|
|
46
|
+
setupLogging({ processLabel: "MASTER", logDir: path.join(rootDir, ".docs-data", "logs") });
|
|
47
|
+
const logger = createLogger("MASTER");
|
|
48
|
+
function getConfig() {
|
|
49
|
+
if (fs.existsSync(packageEnvPath)) {
|
|
50
|
+
dotenv.config({ path: packageEnvPath });
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
proxyUrl: process.env.PROXY_URL,
|
|
54
|
+
version: APP_VERSION,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
getConfig();
|
|
58
|
+
const storageEngine = new StorageEngine(rootDir);
|
|
59
|
+
const embeddingStore = new EmbeddingStore();
|
|
60
|
+
const embeddingEngine = new EmbeddingEngine(embeddingStore);
|
|
61
|
+
storageEngine.ensureProject(storageEngine.getConfig().currentProject);
|
|
62
|
+
const openaiModel = process.env.MCP_DOCUMENTOR_OPENAI_MODEL || "text-embedding-3-small";
|
|
63
|
+
embeddingStore.init(storageEngine.getDataDir(), openaiModel);
|
|
64
|
+
const server = new Server({ name: "mcp-documentor", version: APP_VERSION }, { capabilities: { tools: {} } });
|
|
65
|
+
let guiLauncher;
|
|
66
|
+
const sseControllers = new Set();
|
|
67
|
+
function broadcastToGUI(message) {
|
|
68
|
+
const child = guiLauncher.getProcess();
|
|
69
|
+
if (child?.connected) {
|
|
70
|
+
try {
|
|
71
|
+
child.send(message);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger.error(`Failed to send IPC message: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function handleIPCMessage(msg) {
|
|
79
|
+
if (msg.type === "ALREADY_RUNNING") {
|
|
80
|
+
logger.info(`GUI is already running at: ${msg.data?.url || "unknown URL"}.`);
|
|
81
|
+
}
|
|
82
|
+
else if (msg.type === "READY") {
|
|
83
|
+
logger.info(`GUI is ready! Access it at: ${msg.data.url}`);
|
|
84
|
+
}
|
|
85
|
+
else if (msg.type === "GET_INITIAL_STATE") {
|
|
86
|
+
handleGetInitialState(msg);
|
|
87
|
+
}
|
|
88
|
+
else if (msg.type === "GET_CONFIG") {
|
|
89
|
+
handleGetConfig(msg);
|
|
90
|
+
}
|
|
91
|
+
else if (msg.type === "UPDATE_PROJECT") {
|
|
92
|
+
handleUpdateProject(msg);
|
|
93
|
+
}
|
|
94
|
+
else if (msg.type === "LIST_PROJECTS") {
|
|
95
|
+
handleListProjects(msg);
|
|
96
|
+
}
|
|
97
|
+
else if (msg.type === "LIST_DOCS") {
|
|
98
|
+
handleListDocs(msg);
|
|
99
|
+
}
|
|
100
|
+
else if (msg.type === "GET_DOC") {
|
|
101
|
+
handleGetDoc(msg);
|
|
102
|
+
}
|
|
103
|
+
else if (msg.type === "CHECK_FRESHNESS") {
|
|
104
|
+
handleCheckFreshness(msg);
|
|
105
|
+
}
|
|
106
|
+
else if (msg.type === "SEARCH_DOCS") {
|
|
107
|
+
handleSearchDocs(msg);
|
|
108
|
+
}
|
|
109
|
+
else if (msg.type === "UPDATE_CONFIG") {
|
|
110
|
+
handleUpdateConfig(msg);
|
|
111
|
+
}
|
|
112
|
+
else if (msg.type === "GIT_DIFF") {
|
|
113
|
+
handleGitDiff(msg);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function handleGetInitialState(msg) {
|
|
120
|
+
try {
|
|
121
|
+
const config = storageEngine.getConfig();
|
|
122
|
+
const docs = await storageEngine.listDocs();
|
|
123
|
+
for (const doc of docs) {
|
|
124
|
+
const full = await storageEngine.getDoc(doc.id); // doc.id is now the full path
|
|
125
|
+
if (full && full.tracked_files.length > 0) {
|
|
126
|
+
const report = computeFreshness(full.tracked_files);
|
|
127
|
+
doc.isStale = report.status === "stale";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
broadcastToGUI({
|
|
131
|
+
type: "INITIAL_STATE",
|
|
132
|
+
correlationId: msg.correlationId,
|
|
133
|
+
data: { config, docs, projects: config.projects },
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
logger.error(`Failed to get initial state: ${err.message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function handleGetConfig(msg) {
|
|
142
|
+
const config = storageEngine.getConfig();
|
|
143
|
+
broadcastToGUI({
|
|
144
|
+
type: "CONFIG_RESPONSE",
|
|
145
|
+
correlationId: msg.correlationId,
|
|
146
|
+
data: { ...config, version: getConfig().version },
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function handleUpdateProject(msg) {
|
|
151
|
+
try {
|
|
152
|
+
const project = path.basename(msg.data.project);
|
|
153
|
+
if (!project || project === "..")
|
|
154
|
+
throw new Error("Invalid project name");
|
|
155
|
+
storageEngine.ensureProject(project);
|
|
156
|
+
const config = storageEngine.getConfig();
|
|
157
|
+
config.currentProject = project;
|
|
158
|
+
storageEngine.saveConfig(config);
|
|
159
|
+
const docs = await storageEngine.listDocs();
|
|
160
|
+
for (const doc of docs) {
|
|
161
|
+
const full = await storageEngine.getDoc(doc.id);
|
|
162
|
+
if (full && full.tracked_files.length > 0) {
|
|
163
|
+
const report = computeFreshness(full.tracked_files);
|
|
164
|
+
doc.isStale = report.status === "stale";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
broadcastToGUI({
|
|
168
|
+
type: "UPDATE_PROJECT_RESPONSE",
|
|
169
|
+
correlationId: msg.correlationId,
|
|
170
|
+
data: { success: true, currentProject: project },
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
});
|
|
173
|
+
broadcastToGUI({
|
|
174
|
+
type: "INITIAL_STATE",
|
|
175
|
+
data: { config: storageEngine.getConfig(), docs, projects: storageEngine.getConfig().projects },
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
broadcastToGUI({
|
|
181
|
+
type: "UPDATE_PROJECT_RESPONSE",
|
|
182
|
+
correlationId: msg.correlationId,
|
|
183
|
+
error: err.message,
|
|
184
|
+
timestamp: new Date().toISOString(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function handleListProjects(msg) {
|
|
189
|
+
const config = storageEngine.getConfig();
|
|
190
|
+
broadcastToGUI({
|
|
191
|
+
type: "LIST_PROJECTS_RESPONSE",
|
|
192
|
+
correlationId: msg.correlationId,
|
|
193
|
+
data: { projects: config.projects },
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function handleListDocs(msg) {
|
|
198
|
+
try {
|
|
199
|
+
const docs = await storageEngine.listDocs();
|
|
200
|
+
for (const doc of docs) {
|
|
201
|
+
const full = await storageEngine.getDoc(doc.id);
|
|
202
|
+
if (full && full.tracked_files.length > 0) {
|
|
203
|
+
const report = computeFreshness(full.tracked_files);
|
|
204
|
+
doc.isStale = report.status === "stale";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
broadcastToGUI({
|
|
208
|
+
type: "LIST_DOCS_RESPONSE",
|
|
209
|
+
correlationId: msg.correlationId,
|
|
210
|
+
data: { docs },
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
broadcastToGUI({
|
|
216
|
+
type: "LIST_DOCS_RESPONSE",
|
|
217
|
+
correlationId: msg.correlationId,
|
|
218
|
+
error: err.message,
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function handleGetDoc(msg) {
|
|
224
|
+
try {
|
|
225
|
+
const resolved = await storageEngine.resolveDoc(msg.data.id ?? msg.data.path);
|
|
226
|
+
broadcastToGUI({
|
|
227
|
+
type: "GET_DOC_RESPONSE",
|
|
228
|
+
correlationId: msg.correlationId,
|
|
229
|
+
data: resolved ? { doc: resolved.doc } : null,
|
|
230
|
+
error: resolved ? undefined : "Doc not found",
|
|
231
|
+
timestamp: new Date().toISOString(),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
broadcastToGUI({
|
|
236
|
+
type: "GET_DOC_RESPONSE",
|
|
237
|
+
correlationId: msg.correlationId,
|
|
238
|
+
error: err.message,
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function handleCheckFreshness(msg) {
|
|
244
|
+
try {
|
|
245
|
+
const resolved = await storageEngine.resolveDoc(msg.data.id ?? msg.data.path);
|
|
246
|
+
const doc = resolved?.doc;
|
|
247
|
+
if (!doc) {
|
|
248
|
+
broadcastToGUI({
|
|
249
|
+
type: "CHECK_FRESHNESS_RESPONSE",
|
|
250
|
+
correlationId: msg.correlationId,
|
|
251
|
+
error: "Doc not found",
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const report = computeFreshness(doc.tracked_files);
|
|
257
|
+
broadcastToGUI({
|
|
258
|
+
type: "CHECK_FRESHNESS_RESPONSE",
|
|
259
|
+
correlationId: msg.correlationId,
|
|
260
|
+
data: { report },
|
|
261
|
+
timestamp: new Date().toISOString(),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
broadcastToGUI({
|
|
266
|
+
type: "CHECK_FRESHNESS_RESPONSE",
|
|
267
|
+
correlationId: msg.correlationId,
|
|
268
|
+
error: err.message,
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function handleSearchDocs(msg) {
|
|
274
|
+
try {
|
|
275
|
+
const { query, top_k, all_projects } = msg.data;
|
|
276
|
+
let results;
|
|
277
|
+
if (all_projects) {
|
|
278
|
+
const config = storageEngine.getConfig();
|
|
279
|
+
results = await embeddingEngine.searchAcrossProjects(query, config.projects, top_k || 10, async (project, docPath) => {
|
|
280
|
+
const doc = await storageEngine.getDoc(docPath, project);
|
|
281
|
+
if (!doc)
|
|
282
|
+
return null;
|
|
283
|
+
return { title: doc.title, content: doc.content };
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const currentProject = storageEngine.getConfig().currentProject;
|
|
288
|
+
results = await embeddingEngine.search(query, currentProject, top_k || 5, async (docPath) => {
|
|
289
|
+
const doc = await storageEngine.getDoc(docPath);
|
|
290
|
+
if (!doc)
|
|
291
|
+
return null;
|
|
292
|
+
return { title: doc.title, content: doc.content };
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
broadcastToGUI({
|
|
296
|
+
type: "SEARCH_DOCS_RESPONSE",
|
|
297
|
+
correlationId: msg.correlationId,
|
|
298
|
+
data: { results },
|
|
299
|
+
timestamp: new Date().toISOString(),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
broadcastToGUI({
|
|
304
|
+
type: "SEARCH_DOCS_RESPONSE",
|
|
305
|
+
correlationId: msg.correlationId,
|
|
306
|
+
error: err.message,
|
|
307
|
+
timestamp: new Date().toISOString(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async function handleGitDiff(msg) {
|
|
312
|
+
try {
|
|
313
|
+
const result = await gitDiffDoc(storageEngine, { paths: [msg.data.id ?? msg.data.path] });
|
|
314
|
+
const text = result.content[0]?.text ?? "";
|
|
315
|
+
broadcastToGUI({
|
|
316
|
+
type: "GIT_DIFF_RESPONSE",
|
|
317
|
+
correlationId: msg.correlationId,
|
|
318
|
+
data: { diff: text, isError: !!result.isError },
|
|
319
|
+
timestamp: new Date().toISOString(),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
broadcastToGUI({
|
|
324
|
+
type: "GIT_DIFF_RESPONSE",
|
|
325
|
+
correlationId: msg.correlationId,
|
|
326
|
+
error: err.message,
|
|
327
|
+
timestamp: new Date().toISOString(),
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function handleUpdateConfig(msg) {
|
|
332
|
+
try {
|
|
333
|
+
const { key, value } = msg.data;
|
|
334
|
+
const envPath = path.join(rootDir, ".env");
|
|
335
|
+
let envContent = "";
|
|
336
|
+
if (fs.existsSync(envPath)) {
|
|
337
|
+
envContent = fs.readFileSync(envPath, "utf-8");
|
|
338
|
+
}
|
|
339
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
340
|
+
if (regex.test(envContent)) {
|
|
341
|
+
envContent = envContent.replace(regex, `${key}=${value}`);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
envContent += `\n${key}=${value}`;
|
|
345
|
+
}
|
|
346
|
+
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
347
|
+
getConfig();
|
|
348
|
+
broadcastToGUI({
|
|
349
|
+
type: "CONFIG_UPDATED",
|
|
350
|
+
correlationId: msg.correlationId,
|
|
351
|
+
data: { success: true },
|
|
352
|
+
timestamp: new Date().toISOString(),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
broadcastToGUI({
|
|
357
|
+
type: "CONFIG_UPDATED",
|
|
358
|
+
correlationId: msg.correlationId,
|
|
359
|
+
error: err.message,
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
guiLauncher = new GuiLauncher({
|
|
365
|
+
guiPath: path.join(__dirname, "gui-worker.js"),
|
|
366
|
+
env: { ...process.env },
|
|
367
|
+
maxRestarts: 0,
|
|
368
|
+
onMessage: (msg) => {
|
|
369
|
+
handleIPCMessage(msg);
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
function launchGUI() {
|
|
373
|
+
if (!process.env.PROXY_URL) {
|
|
374
|
+
logger.info('[MASTER] PROXY_URL non défini — GUI désactivée.');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
logger.info("Launching GUI worker...");
|
|
378
|
+
guiLauncher.start();
|
|
379
|
+
}
|
|
380
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
381
|
+
return {
|
|
382
|
+
tools: [
|
|
383
|
+
{
|
|
384
|
+
name: "create_doc",
|
|
385
|
+
description: "Create a new documentation entry with markdown content, optional tracked files, and tags.",
|
|
386
|
+
inputSchema: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
title: { type: "string", description: "Title of the documentation entry" },
|
|
390
|
+
content: { type: "string", description: "Documentation content in markdown. To link to another document inline, use the syntax [text](doc://target-doc-id) — the viewer will navigate to that document on click. To link to a heading within the same document, use [text](#heading-slug). Use check_links to validate both types. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql." },
|
|
391
|
+
tracked_files: {
|
|
392
|
+
type: "array",
|
|
393
|
+
items: { type: "string" },
|
|
394
|
+
description: "File paths to track for staleness detection",
|
|
395
|
+
},
|
|
396
|
+
tags: {
|
|
397
|
+
type: "array",
|
|
398
|
+
items: { type: "string" },
|
|
399
|
+
description: "Tags for categorization",
|
|
400
|
+
},
|
|
401
|
+
folder: { type: "string", description: "Folder path for organization, e.g. 'frontend/components'" },
|
|
402
|
+
related_docs: {
|
|
403
|
+
type: "array",
|
|
404
|
+
items: { type: "string" },
|
|
405
|
+
description: "IDs of related documentation entries",
|
|
406
|
+
},
|
|
407
|
+
project: { type: "string", description: "Project to create the doc in (defaults to current project if omitted)" },
|
|
408
|
+
},
|
|
409
|
+
required: ["title", "content"],
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "get_doc",
|
|
414
|
+
description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata.",
|
|
415
|
+
inputSchema: {
|
|
416
|
+
type: "object",
|
|
417
|
+
properties: {
|
|
418
|
+
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
|
|
419
|
+
},
|
|
420
|
+
required: ["path"],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "list_docs",
|
|
425
|
+
description: "List all documentation entries in the current project with staleness indicators.",
|
|
426
|
+
inputSchema: {
|
|
427
|
+
type: "object",
|
|
428
|
+
properties: {
|
|
429
|
+
project: { type: "string", description: "Project to list (lists all projects if omitted)" },
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "update_doc",
|
|
435
|
+
description: "Fully replace the content and/or metadata of a documentation entry. WARNING: This overwrites the entire content. Prefer patch_doc for targeted edits — only use update_doc when the changes are so extensive that a patch is impractical. To move a doc to another folder, use move_doc instead.",
|
|
436
|
+
inputSchema: {
|
|
437
|
+
type: "object",
|
|
438
|
+
properties: {
|
|
439
|
+
path: { type: "string", description: "Path of the documentation entry to update, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
|
|
440
|
+
content: { type: "string", description: "New content (replaces existing). Supports [text](doc://target-doc-path) to link inline to other documents, and [text](#heading-slug) to link to headings within this document. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql." },
|
|
441
|
+
tracked_files: {
|
|
442
|
+
type: "array",
|
|
443
|
+
items: { type: "string" },
|
|
444
|
+
description: "New list of file paths (replaces existing)",
|
|
445
|
+
},
|
|
446
|
+
tags: {
|
|
447
|
+
type: "array",
|
|
448
|
+
items: { type: "string" },
|
|
449
|
+
description: "New tags (replaces existing)",
|
|
450
|
+
},
|
|
451
|
+
related_docs: {
|
|
452
|
+
type: "array",
|
|
453
|
+
items: { type: "string" },
|
|
454
|
+
description: "Paths of related documentation entries (replaces existing), e.g. ['backend/auth/authentication']",
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
required: ["path"],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "move_doc",
|
|
462
|
+
description: "Move a documentation entry to a new path (changes its folder and/or name). This is the only way to relocate a document — update_doc does not change the location. After moving, use check_links to detect any broken references.",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
|
|
467
|
+
new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
|
|
468
|
+
},
|
|
469
|
+
required: ["path", "new_path"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "search_docs",
|
|
474
|
+
description: "Semantic search across documentation entries using natural language. Requires OpenAI API key.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {
|
|
478
|
+
query: { type: "string", description: "Search query in natural language" },
|
|
479
|
+
top_k: { type: "number", description: "Number of results to return (default: 5)" },
|
|
480
|
+
},
|
|
481
|
+
required: ["query"],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "check_freshness",
|
|
486
|
+
description: "Check freshness of documentation. Pass a path to check a single entry, or omit it to scan all docs in a project and get a full staleness report.",
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
properties: {
|
|
490
|
+
path: { type: "string", description: "Path of a single documentation entry to check, e.g. 'backend/auth/authentication'. If omitted, scans all docs in the project." },
|
|
491
|
+
project: { type: "string", description: "Project to scan (scans all projects if omitted). Only used when path is omitted." },
|
|
492
|
+
},
|
|
493
|
+
required: [],
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "set_project",
|
|
498
|
+
description: "Switch the active project for the GUI display. This does NOT affect MCP tool operations — tools work across all projects automatically. Use only to control what the graphical interface shows.",
|
|
499
|
+
inputSchema: {
|
|
500
|
+
type: "object",
|
|
501
|
+
properties: {
|
|
502
|
+
project: { type: "string", description: "Project name to switch to" },
|
|
503
|
+
},
|
|
504
|
+
required: ["project"],
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: "list_projects",
|
|
509
|
+
description: "List all available documentation projects and show which one is currently active.",
|
|
510
|
+
inputSchema: {
|
|
511
|
+
type: "object",
|
|
512
|
+
properties: {},
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "refresh_file_snapshot",
|
|
517
|
+
description: "Mark tracked files as up-to-date by updating their snapshot timestamps. Use when file changes don't affect the documentation. Accepts one or more doc paths.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
type: "object",
|
|
520
|
+
properties: {
|
|
521
|
+
paths: {
|
|
522
|
+
type: "array",
|
|
523
|
+
items: { type: "string" },
|
|
524
|
+
description: "Paths of documentation entries to refresh, e.g. ['backend/auth/authentication', 'setup']",
|
|
525
|
+
},
|
|
526
|
+
file_paths: {
|
|
527
|
+
type: "array",
|
|
528
|
+
items: { type: "string" },
|
|
529
|
+
description: "Specific tracked file paths to refresh (all tracked files if omitted)",
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
required: ["paths"],
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "get_doc_metadata",
|
|
537
|
+
description: "Get metadata of a documentation entry (path, title, tags, dates, tracked files with freshness) without loading the full content.",
|
|
538
|
+
inputSchema: {
|
|
539
|
+
type: "object",
|
|
540
|
+
properties: {
|
|
541
|
+
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
542
|
+
},
|
|
543
|
+
required: ["path"],
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: "patch_doc",
|
|
548
|
+
description: "PREFERRED method for modifying documentation. Replaces a specific portion of a documentation entry's content using an exact search-and-replace, without touching the rest of the doc. Always use this instead of update_doc when making partial changes.",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties: {
|
|
552
|
+
path: { type: "string", description: "Path of the documentation entry to patch, e.g. 'backend/auth/authentication'" },
|
|
553
|
+
search: { type: "string", description: "Exact text to find in the current content" },
|
|
554
|
+
replacement: { type: "string", description: "Text to replace it with" },
|
|
555
|
+
},
|
|
556
|
+
required: ["path", "search", "replacement"],
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: "git_diff_doc",
|
|
561
|
+
description: "Show the git diff for each tracked file of one or more docs, between their current state and the last commit before the stored snapshot date.",
|
|
562
|
+
inputSchema: {
|
|
563
|
+
type: "object",
|
|
564
|
+
properties: {
|
|
565
|
+
paths: {
|
|
566
|
+
type: "array",
|
|
567
|
+
items: { type: "string" },
|
|
568
|
+
description: "Paths of documentation entries to diff, e.g. ['backend/auth/authentication']",
|
|
569
|
+
},
|
|
570
|
+
file_paths: {
|
|
571
|
+
type: "array",
|
|
572
|
+
items: { type: "string" },
|
|
573
|
+
description: "Specific tracked file paths to diff (all tracked files if omitted)",
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
required: ["paths"],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "check_links",
|
|
581
|
+
description: "Check all links across a project and report broken references. Validates: related_docs IDs, inline doc links ([text](doc://id)) in content, and anchor links ([text](#heading-slug)) in content.",
|
|
582
|
+
inputSchema: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
project: { type: "string", description: "Project to check (defaults to current project)" },
|
|
586
|
+
},
|
|
587
|
+
required: [],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "delete_doc",
|
|
592
|
+
description: "Permanently delete a documentation entry. Works across all projects — no need to set the active project first.",
|
|
593
|
+
inputSchema: {
|
|
594
|
+
type: "object",
|
|
595
|
+
properties: {
|
|
596
|
+
path: { type: "string", description: "Path of the documentation entry to delete, e.g. 'backend/auth/authentication'" },
|
|
597
|
+
},
|
|
598
|
+
required: ["path"],
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
name: "get_doc_tree",
|
|
603
|
+
description: "Get the full documentation tree showing folders, subfolders, and documentation names (not file names). Useful to quickly understand the documentation structure of a project.",
|
|
604
|
+
inputSchema: {
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: {
|
|
607
|
+
project: { type: "string", description: "Project to display (shows all projects if omitted)" },
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
};
|
|
613
|
+
});
|
|
614
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
615
|
+
const { name, arguments: args } = request.params;
|
|
616
|
+
try {
|
|
617
|
+
switch (name) {
|
|
618
|
+
case "create_doc":
|
|
619
|
+
return await createDoc(storageEngine, embeddingEngine, args);
|
|
620
|
+
case "get_doc":
|
|
621
|
+
return await getDoc(storageEngine, args);
|
|
622
|
+
case "list_docs":
|
|
623
|
+
return await listDocs(storageEngine, args);
|
|
624
|
+
case "update_doc":
|
|
625
|
+
return await updateDoc(storageEngine, embeddingEngine, args);
|
|
626
|
+
case "search_docs":
|
|
627
|
+
return await searchDocs(storageEngine, embeddingEngine, args);
|
|
628
|
+
case "check_freshness":
|
|
629
|
+
return await checkFreshness(storageEngine, args);
|
|
630
|
+
case "set_project":
|
|
631
|
+
return await setProject(storageEngine, args);
|
|
632
|
+
case "list_projects":
|
|
633
|
+
return await listProjects(storageEngine);
|
|
634
|
+
case "refresh_file_snapshot":
|
|
635
|
+
return await refreshFileSnapshot(storageEngine, args);
|
|
636
|
+
case "get_doc_metadata":
|
|
637
|
+
return await getDocMetadata(storageEngine, args);
|
|
638
|
+
case "patch_doc":
|
|
639
|
+
return await patchDoc(storageEngine, embeddingEngine, args);
|
|
640
|
+
case "git_diff_doc":
|
|
641
|
+
return await gitDiffDoc(storageEngine, args);
|
|
642
|
+
case "check_links":
|
|
643
|
+
return await checkLinks(storageEngine, args);
|
|
644
|
+
case "delete_doc":
|
|
645
|
+
return await deleteDoc(storageEngine, embeddingEngine, args);
|
|
646
|
+
case "move_doc":
|
|
647
|
+
return await moveDoc(storageEngine, embeddingEngine, args);
|
|
648
|
+
case "get_doc_tree":
|
|
649
|
+
return await getDocTree(storageEngine, args);
|
|
650
|
+
default:
|
|
651
|
+
throw new Error(`Tool not found: ${name}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
return {
|
|
656
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
657
|
+
isError: true,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
async function main() {
|
|
662
|
+
await migrateFolders(storageEngine);
|
|
663
|
+
await migrateEmbeddings(embeddingStore, storageEngine);
|
|
664
|
+
const transport = new StdioServerTransport();
|
|
665
|
+
await server.connect(transport);
|
|
666
|
+
logger.info("MCP Server connected and running on stdio");
|
|
667
|
+
launchGUI();
|
|
668
|
+
}
|
|
669
|
+
main().catch((err) => {
|
|
670
|
+
logger.error(`Fatal error: ${err.message}`);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
});
|
|
673
|
+
process.on("uncaughtException", (err) => {
|
|
674
|
+
logger.error(`Uncaught Exception in MASTER: ${err.message}`);
|
|
675
|
+
logger.error(err.stack || "No stack trace");
|
|
676
|
+
guiLauncher.getProcess()?.kill("SIGTERM");
|
|
677
|
+
process.exit(1);
|
|
678
|
+
});
|