@prih/mcp-graph-memory 1.0.3
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/LICENSE +15 -0
- package/README.md +512 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +56 -0
- package/dist/api/rest/index.js +117 -0
- package/dist/api/rest/knowledge.js +238 -0
- package/dist/api/rest/skills.js +284 -0
- package/dist/api/rest/tasks.js +272 -0
- package/dist/api/rest/tools.js +126 -0
- package/dist/api/rest/validation.js +191 -0
- package/dist/api/rest/websocket.js +65 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +25 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +24 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +55 -0
- package/dist/cli/index.js +451 -0
- package/dist/cli/indexer.js +277 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +172 -0
- package/dist/graphs/docs.js +198 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +764 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1013 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +960 -0
- package/dist/lib/embedder.js +101 -0
- package/dist/lib/events-log.js +400 -0
- package/dist/lib/file-import.js +327 -0
- package/dist/lib/file-mirror.js +446 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +254 -0
- package/dist/lib/parsers/code.js +246 -0
- package/dist/lib/parsers/codeblock.js +66 -0
- package/dist/lib/parsers/docs.js +196 -0
- package/dist/lib/project-manager.js +418 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +108 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/watcher.js +67 -0
- package/package.json +83 -0
- package/ui/README.md +54 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createMcpServer = createMcpServer;
|
|
40
|
+
exports.startStdioServer = startStdioServer;
|
|
41
|
+
exports.startHttpServer = startHttpServer;
|
|
42
|
+
exports.startMultiProjectHttpServer = startMultiProjectHttpServer;
|
|
43
|
+
const http_1 = __importDefault(require("http"));
|
|
44
|
+
const crypto_1 = require("crypto");
|
|
45
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
46
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
47
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
48
|
+
const embedder_1 = require("../lib/embedder");
|
|
49
|
+
const index_1 = require("../api/rest/index");
|
|
50
|
+
const websocket_1 = require("../api/rest/websocket");
|
|
51
|
+
const docs_1 = require("../graphs/docs");
|
|
52
|
+
const code_1 = require("../graphs/code");
|
|
53
|
+
const knowledge_1 = require("../graphs/knowledge");
|
|
54
|
+
const file_index_1 = require("../graphs/file-index");
|
|
55
|
+
const task_1 = require("../graphs/task");
|
|
56
|
+
const skill_1 = require("../graphs/skill");
|
|
57
|
+
const manager_types_1 = require("../graphs/manager-types");
|
|
58
|
+
const listTopics = __importStar(require("../api/tools/docs/list-topics"));
|
|
59
|
+
const getToc = __importStar(require("../api/tools/docs/get-toc"));
|
|
60
|
+
const search = __importStar(require("../api/tools/docs/search"));
|
|
61
|
+
const getNode = __importStar(require("../api/tools/docs/get-node"));
|
|
62
|
+
const searchDocFiles = __importStar(require("../api/tools/docs/search-files"));
|
|
63
|
+
const findExamples = __importStar(require("../api/tools/docs/find-examples"));
|
|
64
|
+
const searchSnippets = __importStar(require("../api/tools/docs/search-snippets"));
|
|
65
|
+
const listSnippets = __importStar(require("../api/tools/docs/list-snippets"));
|
|
66
|
+
const explainSymbol = __importStar(require("../api/tools/docs/explain-symbol"));
|
|
67
|
+
const crossReferences = __importStar(require("../api/tools/docs/cross-references"));
|
|
68
|
+
const listFiles = __importStar(require("../api/tools/code/list-files"));
|
|
69
|
+
const getFileSymbols = __importStar(require("../api/tools/code/get-file-symbols"));
|
|
70
|
+
const searchCode = __importStar(require("../api/tools/code/search-code"));
|
|
71
|
+
const getSymbol = __importStar(require("../api/tools/code/get-symbol"));
|
|
72
|
+
const searchCodeFiles = __importStar(require("../api/tools/code/search-files"));
|
|
73
|
+
const createNote = __importStar(require("../api/tools/knowledge/create-note"));
|
|
74
|
+
const updateNote = __importStar(require("../api/tools/knowledge/update-note"));
|
|
75
|
+
const deleteNote = __importStar(require("../api/tools/knowledge/delete-note"));
|
|
76
|
+
const getNote = __importStar(require("../api/tools/knowledge/get-note"));
|
|
77
|
+
const listNotes = __importStar(require("../api/tools/knowledge/list-notes"));
|
|
78
|
+
const searchNotes = __importStar(require("../api/tools/knowledge/search-notes"));
|
|
79
|
+
const createRelation = __importStar(require("../api/tools/knowledge/create-relation"));
|
|
80
|
+
const deleteRelation = __importStar(require("../api/tools/knowledge/delete-relation"));
|
|
81
|
+
const listRelations = __importStar(require("../api/tools/knowledge/list-relations"));
|
|
82
|
+
const findLinkedNotes = __importStar(require("../api/tools/knowledge/find-linked-notes"));
|
|
83
|
+
const addNoteAttachment = __importStar(require("../api/tools/knowledge/add-attachment"));
|
|
84
|
+
const removeNoteAttachment = __importStar(require("../api/tools/knowledge/remove-attachment"));
|
|
85
|
+
const listAllFiles = __importStar(require("../api/tools/file-index/list-all-files"));
|
|
86
|
+
const searchAllFiles = __importStar(require("../api/tools/file-index/search-all-files"));
|
|
87
|
+
const getFileInfo = __importStar(require("../api/tools/file-index/get-file-info"));
|
|
88
|
+
const createTask = __importStar(require("../api/tools/tasks/create-task"));
|
|
89
|
+
const updateTask = __importStar(require("../api/tools/tasks/update-task"));
|
|
90
|
+
const deleteTask = __importStar(require("../api/tools/tasks/delete-task"));
|
|
91
|
+
const getTask = __importStar(require("../api/tools/tasks/get-task"));
|
|
92
|
+
const listTasksTool = __importStar(require("../api/tools/tasks/list-tasks"));
|
|
93
|
+
const searchTasksTool = __importStar(require("../api/tools/tasks/search-tasks"));
|
|
94
|
+
const moveTask = __importStar(require("../api/tools/tasks/move-task"));
|
|
95
|
+
const linkTask = __importStar(require("../api/tools/tasks/link-task"));
|
|
96
|
+
const createTaskLink = __importStar(require("../api/tools/tasks/create-task-link"));
|
|
97
|
+
const deleteTaskLink = __importStar(require("../api/tools/tasks/delete-task-link"));
|
|
98
|
+
const findLinkedTasks = __importStar(require("../api/tools/tasks/find-linked-tasks"));
|
|
99
|
+
const addTaskAttachment = __importStar(require("../api/tools/tasks/add-attachment"));
|
|
100
|
+
const removeTaskAttachment = __importStar(require("../api/tools/tasks/remove-attachment"));
|
|
101
|
+
const createSkillTool = __importStar(require("../api/tools/skills/create-skill"));
|
|
102
|
+
const updateSkillTool = __importStar(require("../api/tools/skills/update-skill"));
|
|
103
|
+
const deleteSkillTool = __importStar(require("../api/tools/skills/delete-skill"));
|
|
104
|
+
const getSkillTool = __importStar(require("../api/tools/skills/get-skill"));
|
|
105
|
+
const listSkillsTool = __importStar(require("../api/tools/skills/list-skills"));
|
|
106
|
+
const searchSkillsTool = __importStar(require("../api/tools/skills/search-skills"));
|
|
107
|
+
const linkSkill = __importStar(require("../api/tools/skills/link-skill"));
|
|
108
|
+
const createSkillLink = __importStar(require("../api/tools/skills/create-skill-link"));
|
|
109
|
+
const deleteSkillLink = __importStar(require("../api/tools/skills/delete-skill-link"));
|
|
110
|
+
const findLinkedSkills = __importStar(require("../api/tools/skills/find-linked-skills"));
|
|
111
|
+
const addSkillAttachment = __importStar(require("../api/tools/skills/add-attachment"));
|
|
112
|
+
const removeSkillAttachment = __importStar(require("../api/tools/skills/remove-attachment"));
|
|
113
|
+
const recallSkills = __importStar(require("../api/tools/skills/recall-skills"));
|
|
114
|
+
const bumpSkillUsage = __importStar(require("../api/tools/skills/bump-usage"));
|
|
115
|
+
const getContext = __importStar(require("../api/tools/context/get-context"));
|
|
116
|
+
/**
|
|
117
|
+
* Create an McpServer proxy that wraps registerTool handlers in a PromiseQueue.
|
|
118
|
+
* Used for mutation tools to prevent concurrent graph modifications.
|
|
119
|
+
*/
|
|
120
|
+
function createMutationServer(server, queue) {
|
|
121
|
+
const proxy = Object.create(server);
|
|
122
|
+
const origRegister = server.registerTool.bind(server);
|
|
123
|
+
proxy.registerTool = function (name, config, handler) {
|
|
124
|
+
if (typeof handler === 'function') {
|
|
125
|
+
const wrapped = (...handlerArgs) => queue.enqueue(() => handler(...handlerArgs));
|
|
126
|
+
return origRegister(name, config, wrapped);
|
|
127
|
+
}
|
|
128
|
+
return origRegister(name, config, handler);
|
|
129
|
+
};
|
|
130
|
+
return proxy;
|
|
131
|
+
}
|
|
132
|
+
function buildInstructions(ctx) {
|
|
133
|
+
const lines = [];
|
|
134
|
+
if (ctx.workspaceId) {
|
|
135
|
+
lines.push(`Connected to project "${ctx.projectId}" in workspace "${ctx.workspaceId}".`);
|
|
136
|
+
if (ctx.workspaceProjects?.length) {
|
|
137
|
+
lines.push(`Workspace projects: ${ctx.workspaceProjects.join(', ')}.`);
|
|
138
|
+
}
|
|
139
|
+
lines.push('Knowledge, tasks, and skills are shared across all workspace projects.');
|
|
140
|
+
lines.push('Docs, code, and files are specific to the current project.');
|
|
141
|
+
lines.push('Use projectId parameter in cross-graph links to reference nodes from other projects.');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
lines.push(`Connected to project "${ctx.projectId}".`);
|
|
145
|
+
}
|
|
146
|
+
lines.push('Use get_context tool for structured project/workspace information.');
|
|
147
|
+
return lines.join(' ');
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates the McpServer with all tools wired to the given graphs.
|
|
151
|
+
* Pass docGraph to enable the 10 doc tools (5 base + 5 code-block tools);
|
|
152
|
+
* pass codeGraph to enable the 5 code tools;
|
|
153
|
+
* pass fileIndexGraph to enable the 3 file index tools.
|
|
154
|
+
* cross_references requires both docGraph and codeGraph.
|
|
155
|
+
* Knowledge tools (12) are always registered.
|
|
156
|
+
* Task tools (13) are always registered when taskGraph is provided.
|
|
157
|
+
* @param embedFn Single EmbedFn (all graphs share it) or per-graph EmbedFnMap.
|
|
158
|
+
* Tests typically pass a single function; CLI passes a map for per-graph models.
|
|
159
|
+
* @param mutationQueue Optional PromiseQueue to serialize mutation tool handlers.
|
|
160
|
+
*/
|
|
161
|
+
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext) {
|
|
162
|
+
// Backward-compat: single EmbedFn → use for both document and query
|
|
163
|
+
const defaultPair = { document: (q) => (0, embedder_1.embed)(q, ''), query: (q) => (0, embedder_1.embed)(q, '') };
|
|
164
|
+
const fns = typeof embedFn === 'function'
|
|
165
|
+
? {
|
|
166
|
+
docs: { document: embedFn, query: embedFn },
|
|
167
|
+
code: { document: embedFn, query: embedFn },
|
|
168
|
+
knowledge: { document: embedFn, query: embedFn },
|
|
169
|
+
tasks: { document: embedFn, query: embedFn },
|
|
170
|
+
files: { document: embedFn, query: embedFn },
|
|
171
|
+
skills: { document: embedFn, query: embedFn },
|
|
172
|
+
}
|
|
173
|
+
: {
|
|
174
|
+
docs: embedFn?.docs ?? defaultPair,
|
|
175
|
+
code: embedFn?.code ?? defaultPair,
|
|
176
|
+
knowledge: embedFn?.knowledge ?? defaultPair,
|
|
177
|
+
tasks: embedFn?.tasks ?? defaultPair,
|
|
178
|
+
files: embedFn?.files ?? defaultPair,
|
|
179
|
+
skills: embedFn?.skills ?? defaultPair,
|
|
180
|
+
};
|
|
181
|
+
// Build instructions for MCP clients (workspace/project context)
|
|
182
|
+
const instructions = sessionContext ? buildInstructions(sessionContext) : undefined;
|
|
183
|
+
const server = new mcp_js_1.McpServer({ name: 'mcp-graph-memory', version: '1.0.0' }, instructions ? { instructions } : undefined);
|
|
184
|
+
// Mutation tools are registered through mutServer to serialize concurrent writes
|
|
185
|
+
const mutServer = mutationQueue ? createMutationServer(server, mutationQueue) : server;
|
|
186
|
+
// Context tool (always registered)
|
|
187
|
+
getContext.register(server, sessionContext);
|
|
188
|
+
const ext = { docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, skillGraph };
|
|
189
|
+
// Docs tools (only when docGraph is provided)
|
|
190
|
+
if (docGraph) {
|
|
191
|
+
const docMgr = new docs_1.DocGraphManager(docGraph, fns.docs, ext);
|
|
192
|
+
listTopics.register(server, docMgr);
|
|
193
|
+
getToc.register(server, docMgr);
|
|
194
|
+
search.register(server, docMgr);
|
|
195
|
+
getNode.register(server, docMgr);
|
|
196
|
+
searchDocFiles.register(server, docMgr);
|
|
197
|
+
findExamples.register(server, docMgr);
|
|
198
|
+
searchSnippets.register(server, docMgr);
|
|
199
|
+
listSnippets.register(server, docMgr);
|
|
200
|
+
explainSymbol.register(server, docMgr);
|
|
201
|
+
// Cross-graph tools (require both docGraph and codeGraph)
|
|
202
|
+
if (codeGraph) {
|
|
203
|
+
const codeMgrForCross = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
204
|
+
crossReferences.register(server, docMgr, codeMgrForCross);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Code tools (only when codeGraph is provided)
|
|
208
|
+
if (codeGraph) {
|
|
209
|
+
const codeMgr = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
210
|
+
listFiles.register(server, codeMgr);
|
|
211
|
+
getFileSymbols.register(server, codeMgr);
|
|
212
|
+
searchCode.register(server, codeMgr);
|
|
213
|
+
getSymbol.register(server, codeMgr);
|
|
214
|
+
searchCodeFiles.register(server, codeMgr);
|
|
215
|
+
}
|
|
216
|
+
// File index tools (always registered when fileIndexGraph is provided)
|
|
217
|
+
if (fileIndexGraph) {
|
|
218
|
+
const fileIndexMgr = new file_index_1.FileIndexGraphManager(fileIndexGraph, fns.files, ext);
|
|
219
|
+
listAllFiles.register(server, fileIndexMgr);
|
|
220
|
+
searchAllFiles.register(server, fileIndexMgr);
|
|
221
|
+
getFileInfo.register(server, fileIndexMgr);
|
|
222
|
+
}
|
|
223
|
+
// Knowledge tools (always registered)
|
|
224
|
+
// Mutations (create/update/delete) go through mutServer for queue serialization
|
|
225
|
+
if (knowledgeGraph) {
|
|
226
|
+
const ctx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
227
|
+
const knowledgeMgr = new knowledge_1.KnowledgeGraphManager(knowledgeGraph, fns.knowledge, ctx, {
|
|
228
|
+
docGraph, codeGraph, fileIndexGraph, taskGraph,
|
|
229
|
+
});
|
|
230
|
+
createNote.register(mutServer, knowledgeMgr);
|
|
231
|
+
updateNote.register(mutServer, knowledgeMgr);
|
|
232
|
+
deleteNote.register(mutServer, knowledgeMgr);
|
|
233
|
+
getNote.register(server, knowledgeMgr);
|
|
234
|
+
listNotes.register(server, knowledgeMgr);
|
|
235
|
+
searchNotes.register(server, knowledgeMgr);
|
|
236
|
+
createRelation.register(mutServer, knowledgeMgr);
|
|
237
|
+
deleteRelation.register(mutServer, knowledgeMgr);
|
|
238
|
+
listRelations.register(server, knowledgeMgr);
|
|
239
|
+
findLinkedNotes.register(server, knowledgeMgr);
|
|
240
|
+
addNoteAttachment.register(mutServer, knowledgeMgr);
|
|
241
|
+
removeNoteAttachment.register(mutServer, knowledgeMgr);
|
|
242
|
+
}
|
|
243
|
+
// Task tools (always registered when taskGraph is provided)
|
|
244
|
+
// Mutations go through mutServer for queue serialization
|
|
245
|
+
if (taskGraph) {
|
|
246
|
+
const taskCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
247
|
+
const taskMgr = new task_1.TaskGraphManager(taskGraph, fns.tasks, taskCtx, {
|
|
248
|
+
docGraph, codeGraph, knowledgeGraph, fileIndexGraph,
|
|
249
|
+
});
|
|
250
|
+
createTask.register(mutServer, taskMgr);
|
|
251
|
+
updateTask.register(mutServer, taskMgr);
|
|
252
|
+
deleteTask.register(mutServer, taskMgr);
|
|
253
|
+
getTask.register(server, taskMgr);
|
|
254
|
+
listTasksTool.register(server, taskMgr);
|
|
255
|
+
searchTasksTool.register(server, taskMgr);
|
|
256
|
+
moveTask.register(mutServer, taskMgr);
|
|
257
|
+
linkTask.register(mutServer, taskMgr);
|
|
258
|
+
createTaskLink.register(mutServer, taskMgr);
|
|
259
|
+
deleteTaskLink.register(mutServer, taskMgr);
|
|
260
|
+
findLinkedTasks.register(server, taskMgr);
|
|
261
|
+
addTaskAttachment.register(mutServer, taskMgr);
|
|
262
|
+
removeTaskAttachment.register(mutServer, taskMgr);
|
|
263
|
+
}
|
|
264
|
+
// Skill tools (always registered when skillGraph is provided)
|
|
265
|
+
if (skillGraph) {
|
|
266
|
+
const skillCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
267
|
+
const skillMgr = new skill_1.SkillGraphManager(skillGraph, fns.skills, skillCtx, {
|
|
268
|
+
docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph,
|
|
269
|
+
});
|
|
270
|
+
createSkillTool.register(mutServer, skillMgr);
|
|
271
|
+
updateSkillTool.register(mutServer, skillMgr);
|
|
272
|
+
deleteSkillTool.register(mutServer, skillMgr);
|
|
273
|
+
getSkillTool.register(server, skillMgr);
|
|
274
|
+
listSkillsTool.register(server, skillMgr);
|
|
275
|
+
searchSkillsTool.register(server, skillMgr);
|
|
276
|
+
linkSkill.register(mutServer, skillMgr);
|
|
277
|
+
createSkillLink.register(mutServer, skillMgr);
|
|
278
|
+
deleteSkillLink.register(mutServer, skillMgr);
|
|
279
|
+
findLinkedSkills.register(server, skillMgr);
|
|
280
|
+
addSkillAttachment.register(mutServer, skillMgr);
|
|
281
|
+
removeSkillAttachment.register(mutServer, skillMgr);
|
|
282
|
+
recallSkills.register(server, skillMgr);
|
|
283
|
+
bumpSkillUsage.register(mutServer, skillMgr);
|
|
284
|
+
}
|
|
285
|
+
return server;
|
|
286
|
+
}
|
|
287
|
+
async function startStdioServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, projectDir, skillGraph, sessionContext) {
|
|
288
|
+
const server = createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, undefined, projectDir, skillGraph, sessionContext);
|
|
289
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
290
|
+
await server.connect(transport);
|
|
291
|
+
process.stderr.write('[server] MCP server running on stdio\n');
|
|
292
|
+
}
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// HTTP transport (Streamable HTTP)
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
async function collectBody(req) {
|
|
297
|
+
const chunks = [];
|
|
298
|
+
for await (const chunk of req)
|
|
299
|
+
chunks.push(chunk);
|
|
300
|
+
return JSON.parse(Buffer.concat(chunks).toString());
|
|
301
|
+
}
|
|
302
|
+
async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, projectDir, skillGraph, sessionContext) {
|
|
303
|
+
const sessions = new Map();
|
|
304
|
+
// Sweep stale sessions every 60s
|
|
305
|
+
const sweepInterval = setInterval(() => {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
for (const [sid, s] of sessions) {
|
|
308
|
+
if (now - s.lastActivity > sessionTimeoutMs) {
|
|
309
|
+
s.server.close().catch(() => { });
|
|
310
|
+
sessions.delete(sid);
|
|
311
|
+
process.stderr.write(`[http] Session ${sid} timed out\n`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}, 60_000);
|
|
315
|
+
sweepInterval.unref();
|
|
316
|
+
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
317
|
+
if (req.url !== '/mcp') {
|
|
318
|
+
res.writeHead(404).end();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
322
|
+
// Existing session — route to its transport
|
|
323
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
324
|
+
const session = sessions.get(sessionId);
|
|
325
|
+
session.lastActivity = Date.now();
|
|
326
|
+
const body = req.method === 'POST' ? await collectBody(req) : undefined;
|
|
327
|
+
await session.transport.handleRequest(req, res, body);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// New session — only POST (initialize) can create one
|
|
331
|
+
if (req.method !== 'POST') {
|
|
332
|
+
res.writeHead(400).end('No session');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const body = await collectBody(req);
|
|
336
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
337
|
+
sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
|
|
338
|
+
onsessioninitialized: (sid) => {
|
|
339
|
+
sessions.set(sid, { server: mcpServer, transport, lastActivity: Date.now() });
|
|
340
|
+
process.stderr.write(`[http] Session ${sid} started\n`);
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
transport.onclose = () => {
|
|
344
|
+
const sid = transport.sessionId;
|
|
345
|
+
if (sid)
|
|
346
|
+
sessions.delete(sid);
|
|
347
|
+
};
|
|
348
|
+
const mcpServer = createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, undefined, projectDir, skillGraph, sessionContext);
|
|
349
|
+
await mcpServer.connect(transport);
|
|
350
|
+
await transport.handleRequest(req, res, body);
|
|
351
|
+
});
|
|
352
|
+
return new Promise((resolve) => {
|
|
353
|
+
httpServer.listen(port, host, () => {
|
|
354
|
+
process.stderr.write(`[server] MCP HTTP server listening on http://${host}:${port}/mcp\n`);
|
|
355
|
+
resolve(httpServer);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, projectManager) {
|
|
360
|
+
const sessions = new Map();
|
|
361
|
+
// Sweep stale sessions every 60s
|
|
362
|
+
const sweepInterval = setInterval(() => {
|
|
363
|
+
const now = Date.now();
|
|
364
|
+
for (const [sid, s] of sessions) {
|
|
365
|
+
if (now - s.lastActivity > sessionTimeoutMs) {
|
|
366
|
+
s.server.close().catch(() => { });
|
|
367
|
+
sessions.delete(sid);
|
|
368
|
+
process.stderr.write(`[http] Session ${sid} (project: ${s.projectId}) timed out\n`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}, 60_000);
|
|
372
|
+
sweepInterval.unref();
|
|
373
|
+
// Express app handles /api/* routes
|
|
374
|
+
const restApp = (0, index_1.createRestApp)(projectManager);
|
|
375
|
+
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
376
|
+
// Route /api/* to Express
|
|
377
|
+
if (req.url?.startsWith('/api/')) {
|
|
378
|
+
restApp(req, res);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// Route: /mcp/{workspaceId}/{projectId} or /mcp/{projectId}
|
|
382
|
+
const wsMatch = req.url?.match(/^\/mcp\/([^/?]+)\/([^/?]+)/);
|
|
383
|
+
const projMatch = !wsMatch ? req.url?.match(/^\/mcp\/([^/?]+)/) : null;
|
|
384
|
+
if (!wsMatch && !projMatch) {
|
|
385
|
+
// Everything else (UI static files, SPA fallback) goes through Express
|
|
386
|
+
restApp(req, res);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// Resolve project and optional workspace from URL
|
|
390
|
+
let projectId;
|
|
391
|
+
let workspaceId;
|
|
392
|
+
if (wsMatch) {
|
|
393
|
+
const maybeWs = decodeURIComponent(wsMatch[1]);
|
|
394
|
+
const maybeProjId = decodeURIComponent(wsMatch[2]);
|
|
395
|
+
const ws = projectManager.getWorkspace(maybeWs);
|
|
396
|
+
if (ws) {
|
|
397
|
+
// Valid workspace route
|
|
398
|
+
if (!ws.config.projects.includes(maybeProjId)) {
|
|
399
|
+
res.writeHead(404).end(JSON.stringify({ error: `Project "${maybeProjId}" is not part of workspace "${maybeWs}"` }));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
workspaceId = maybeWs;
|
|
403
|
+
projectId = maybeProjId;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
// Not a workspace — treat first segment as projectId (fallback)
|
|
407
|
+
projectId = maybeWs;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
projectId = decodeURIComponent(projMatch[1]);
|
|
412
|
+
}
|
|
413
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
414
|
+
// Existing session — route to its transport
|
|
415
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
416
|
+
const session = sessions.get(sessionId);
|
|
417
|
+
if (session.projectId !== projectId) {
|
|
418
|
+
res.writeHead(400).end('Session belongs to a different project');
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
session.lastActivity = Date.now();
|
|
422
|
+
const body = req.method === 'POST' ? await collectBody(req) : undefined;
|
|
423
|
+
await session.transport.handleRequest(req, res, body);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// New session — only POST (initialize) can create one
|
|
427
|
+
if (req.method !== 'POST') {
|
|
428
|
+
res.writeHead(400).end('No session');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// Validate project exists
|
|
432
|
+
const project = projectManager.getProject(projectId);
|
|
433
|
+
if (!project) {
|
|
434
|
+
res.writeHead(404).end(JSON.stringify({ error: `Project "${projectId}" not found` }));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// Build session context (auto-detect workspace if not in URL)
|
|
438
|
+
const ws = workspaceId
|
|
439
|
+
? projectManager.getWorkspace(workspaceId)
|
|
440
|
+
: projectManager.getProjectWorkspace(projectId);
|
|
441
|
+
const sessionCtx = {
|
|
442
|
+
projectId,
|
|
443
|
+
workspaceId: ws?.id,
|
|
444
|
+
workspaceProjects: ws?.config.projects,
|
|
445
|
+
};
|
|
446
|
+
const body = await collectBody(req);
|
|
447
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
448
|
+
sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
|
|
449
|
+
onsessioninitialized: (sid) => {
|
|
450
|
+
sessions.set(sid, { projectId, workspaceId: ws?.id, server: mcpServer, transport, lastActivity: Date.now() });
|
|
451
|
+
process.stderr.write(`[http] Session ${sid} started (project: ${projectId}${ws ? `, workspace: ${ws.id}` : ''})\n`);
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
transport.onclose = () => {
|
|
455
|
+
const sid = transport.sessionId;
|
|
456
|
+
if (sid)
|
|
457
|
+
sessions.delete(sid);
|
|
458
|
+
};
|
|
459
|
+
const mcpServer = createMcpServer(project.docGraph, project.codeGraph, project.knowledgeGraph, project.fileIndexGraph, project.taskGraph, project.embedFns, project.mutationQueue, project.config.projectDir, project.skillGraph, sessionCtx);
|
|
460
|
+
await mcpServer.connect(transport);
|
|
461
|
+
await transport.handleRequest(req, res, body);
|
|
462
|
+
});
|
|
463
|
+
// Attach WebSocket server for real-time events
|
|
464
|
+
(0, websocket_1.attachWebSocket)(httpServer, projectManager);
|
|
465
|
+
return new Promise((resolve) => {
|
|
466
|
+
httpServer.listen(port, host, () => {
|
|
467
|
+
process.stderr.write(`[server] MCP endpoints: http://${host}:${port}/mcp/{projectId} and /mcp/{workspaceId}/{projectId}\n`);
|
|
468
|
+
process.stderr.write(`[server] REST API at http://${host}:${port}/api/\n`);
|
|
469
|
+
process.stderr.write(`[server] WebSocket at ws://${host}:${port}/api/ws\n`);
|
|
470
|
+
resolve(httpServer);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCodeRouter = createCodeRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const validation_1 = require("../../api/rest/validation");
|
|
6
|
+
/** Express 5 wildcard params are arrays — join them back into a path string. */
|
|
7
|
+
function joinParam(value) {
|
|
8
|
+
return Array.isArray(value) ? value.join('/') : String(value);
|
|
9
|
+
}
|
|
10
|
+
function createCodeRouter() {
|
|
11
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
12
|
+
function getProject(req) {
|
|
13
|
+
return req.project;
|
|
14
|
+
}
|
|
15
|
+
// List code files
|
|
16
|
+
router.get('/files', (0, validation_1.validateQuery)(validation_1.listQuerySchema), (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const p = getProject(req);
|
|
19
|
+
if (!p.codeManager)
|
|
20
|
+
return res.json({ results: [] });
|
|
21
|
+
const q = req.validatedQuery;
|
|
22
|
+
const files = p.codeManager.listFiles(q.filter, q.limit);
|
|
23
|
+
res.json({ results: files });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
next(err);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Get symbols for a file
|
|
30
|
+
router.get('/files/*fileId/symbols', (req, res, next) => {
|
|
31
|
+
try {
|
|
32
|
+
const p = getProject(req);
|
|
33
|
+
if (!p.codeManager)
|
|
34
|
+
return res.status(404).json({ error: 'No code graph' });
|
|
35
|
+
const fileId = joinParam(req.params.fileId);
|
|
36
|
+
const symbols = p.codeManager.getFileSymbols(fileId);
|
|
37
|
+
res.json({ results: symbols });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
next(err);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// Get symbol by ID
|
|
44
|
+
router.get('/symbols/*symbolId', (req, res, next) => {
|
|
45
|
+
try {
|
|
46
|
+
const p = getProject(req);
|
|
47
|
+
if (!p.codeManager)
|
|
48
|
+
return res.status(404).json({ error: 'No code graph' });
|
|
49
|
+
const symbolId = joinParam(req.params.symbolId);
|
|
50
|
+
const symbol = p.codeManager.getSymbol(symbolId);
|
|
51
|
+
if (!symbol)
|
|
52
|
+
return res.status(404).json({ error: 'Symbol not found' });
|
|
53
|
+
res.json(symbol);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
next(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Search code
|
|
60
|
+
router.get('/search', (0, validation_1.validateQuery)(validation_1.searchQuerySchema), async (req, res, next) => {
|
|
61
|
+
try {
|
|
62
|
+
const p = getProject(req);
|
|
63
|
+
if (!p.codeManager)
|
|
64
|
+
return res.json({ results: [] });
|
|
65
|
+
const q = req.validatedQuery;
|
|
66
|
+
const results = await p.codeManager.search(q.q, {
|
|
67
|
+
topK: q.topK,
|
|
68
|
+
minScore: q.minScore,
|
|
69
|
+
searchMode: q.searchMode,
|
|
70
|
+
});
|
|
71
|
+
res.json({ results });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
next(err);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDocsRouter = createDocsRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const validation_1 = require("../../api/rest/validation");
|
|
6
|
+
/** Express 5 wildcard params are arrays — join them back into a path string. */
|
|
7
|
+
function joinParam(value) {
|
|
8
|
+
return Array.isArray(value) ? value.join('/') : String(value);
|
|
9
|
+
}
|
|
10
|
+
function createDocsRouter() {
|
|
11
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
12
|
+
function getProject(req) {
|
|
13
|
+
return req.project;
|
|
14
|
+
}
|
|
15
|
+
// List topics (files)
|
|
16
|
+
router.get('/topics', (0, validation_1.validateQuery)(validation_1.listQuerySchema), (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const p = getProject(req);
|
|
19
|
+
if (!p.docManager)
|
|
20
|
+
return res.json({ results: [] });
|
|
21
|
+
const q = req.validatedQuery;
|
|
22
|
+
const topics = p.docManager.listFiles(q.filter, q.limit);
|
|
23
|
+
res.json({ results: topics });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
next(err);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Get TOC for a file
|
|
30
|
+
router.get('/toc/*fileId', (req, res, next) => {
|
|
31
|
+
try {
|
|
32
|
+
const p = getProject(req);
|
|
33
|
+
if (!p.docManager)
|
|
34
|
+
return res.status(404).json({ error: 'No doc graph' });
|
|
35
|
+
const fileId = joinParam(req.params.fileId);
|
|
36
|
+
const chunks = p.docManager.getFileChunks(fileId);
|
|
37
|
+
if (!chunks.length)
|
|
38
|
+
return res.status(404).json({ error: 'File not found' });
|
|
39
|
+
res.json({ results: chunks });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
next(err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Get node by ID
|
|
46
|
+
router.get('/nodes/*nodeId', (req, res, next) => {
|
|
47
|
+
try {
|
|
48
|
+
const p = getProject(req);
|
|
49
|
+
if (!p.docManager)
|
|
50
|
+
return res.status(404).json({ error: 'No doc graph' });
|
|
51
|
+
const nodeId = joinParam(req.params.nodeId);
|
|
52
|
+
const node = p.docManager.getNode(nodeId);
|
|
53
|
+
if (!node)
|
|
54
|
+
return res.status(404).json({ error: 'Node not found' });
|
|
55
|
+
res.json(node);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
next(err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// Search docs
|
|
62
|
+
router.get('/search', (0, validation_1.validateQuery)(validation_1.searchQuerySchema), async (req, res, next) => {
|
|
63
|
+
try {
|
|
64
|
+
const p = getProject(req);
|
|
65
|
+
if (!p.docManager)
|
|
66
|
+
return res.json({ results: [] });
|
|
67
|
+
const q = req.validatedQuery;
|
|
68
|
+
const results = await p.docManager.search(q.q, {
|
|
69
|
+
topK: q.topK,
|
|
70
|
+
minScore: q.minScore,
|
|
71
|
+
searchMode: q.searchMode,
|
|
72
|
+
});
|
|
73
|
+
res.json({ results });
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
next(err);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return router;
|
|
80
|
+
}
|