@graphmemory/server 1.2.0 → 1.3.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/LICENSE +84 -12
- package/README.md +7 -1
- package/dist/api/index.js +151 -54
- package/dist/api/rest/code.js +2 -1
- package/dist/api/rest/docs.js +2 -1
- package/dist/api/rest/embed.js +8 -1
- package/dist/api/rest/index.js +39 -18
- package/dist/api/rest/knowledge.js +4 -2
- package/dist/api/rest/skills.js +2 -1
- package/dist/api/rest/tasks.js +2 -1
- package/dist/api/rest/tools.js +8 -1
- package/dist/api/rest/validation.js +41 -40
- package/dist/api/rest/websocket.js +24 -7
- package/dist/api/tools/code/search-code.js +12 -9
- package/dist/api/tools/code/search-files.js +1 -1
- package/dist/api/tools/docs/cross-references.js +3 -2
- package/dist/api/tools/docs/explain-symbol.js +2 -1
- package/dist/api/tools/docs/find-examples.js +2 -1
- package/dist/api/tools/docs/search-files.js +1 -1
- package/dist/api/tools/docs/search-snippets.js +1 -1
- package/dist/api/tools/docs/search.js +5 -4
- package/dist/api/tools/file-index/search-all-files.js +1 -1
- package/dist/api/tools/knowledge/add-attachment.js +15 -3
- package/dist/api/tools/knowledge/remove-attachment.js +5 -1
- package/dist/api/tools/knowledge/search-notes.js +5 -4
- package/dist/api/tools/skills/add-attachment.js +15 -3
- package/dist/api/tools/skills/recall-skills.js +1 -1
- package/dist/api/tools/skills/remove-attachment.js +5 -1
- package/dist/api/tools/skills/search-skills.js +6 -5
- package/dist/api/tools/tasks/add-attachment.js +15 -3
- package/dist/api/tools/tasks/remove-attachment.js +5 -1
- package/dist/api/tools/tasks/search-tasks.js +5 -4
- package/dist/cli/index.js +63 -52
- package/dist/cli/indexer.js +62 -29
- package/dist/graphs/attachment-types.js +5 -0
- package/dist/graphs/code.js +99 -10
- package/dist/graphs/docs.js +20 -5
- package/dist/graphs/file-index.js +22 -6
- package/dist/graphs/file-lang.js +1 -1
- package/dist/graphs/knowledge.js +31 -7
- package/dist/graphs/skill.js +35 -9
- package/dist/graphs/task.js +35 -9
- package/dist/lib/defaults.js +78 -0
- package/dist/lib/embedder.js +11 -12
- package/dist/lib/embedding-codec.js +63 -0
- package/dist/lib/graph-persistence.js +68 -0
- package/dist/lib/jwt.js +4 -4
- package/dist/lib/mirror-watcher.js +4 -3
- package/dist/lib/multi-config.js +6 -1
- package/dist/lib/parsers/code.js +158 -31
- package/dist/lib/parsers/codeblock.js +11 -6
- package/dist/lib/parsers/docs.js +60 -31
- package/dist/lib/parsers/languages/registry.js +2 -2
- package/dist/lib/parsers/languages/typescript.js +214 -46
- package/dist/lib/project-manager.js +21 -11
- package/dist/lib/search/bm25.js +23 -5
- package/dist/lib/search/code.js +13 -3
- package/dist/lib/search/docs.js +2 -1
- package/dist/lib/search/file-index.js +2 -1
- package/dist/lib/search/files.js +3 -2
- package/dist/lib/search/knowledge.js +2 -1
- package/dist/lib/search/skills.js +2 -1
- package/dist/lib/search/tasks.js +2 -1
- package/dist/ui/assets/NoteForm-aZX9f6-3.js +1 -0
- package/dist/ui/assets/SkillForm-KYa3o92l.js +1 -0
- package/dist/ui/assets/TaskForm-Bl5nkybO.js +1 -0
- package/dist/ui/assets/_articleId_-DjbCByxM.js +1 -0
- package/dist/ui/assets/_docId_-hdCDjclV.js +1 -0
- package/dist/ui/assets/_filePath_-CpG836v4.js +1 -0
- package/dist/ui/assets/_noteId_-C1enaQd1.js +1 -0
- package/dist/ui/assets/_skillId_-hPoCet7J.js +1 -0
- package/dist/ui/assets/_taskId_-DSB3dLVz.js +1 -0
- package/dist/ui/assets/_toolName_-3SmCfxZy.js +2 -0
- package/dist/ui/assets/api-BMnBjMMf.js +1 -0
- package/dist/ui/assets/api-BlFF6gX-.js +1 -0
- package/dist/ui/assets/api-CrGJOcaN.js +1 -0
- package/dist/ui/assets/api-DuX-0a_X.js +1 -0
- package/dist/ui/assets/attachments-CEQ-2nMo.js +1 -0
- package/dist/ui/assets/client-Bq88u7gN.js +1 -0
- package/dist/ui/assets/docs-CrXsRcOG.js +1 -0
- package/dist/ui/assets/edit-BYiy1FZy.js +1 -0
- package/dist/ui/assets/edit-TUIIpUMF.js +1 -0
- package/dist/ui/assets/edit-hc-ZWz3y.js +1 -0
- package/dist/ui/assets/esm-BWiKNcBW.js +1 -0
- package/dist/ui/assets/files-0bPg6NH9.js +1 -0
- package/dist/ui/assets/graph-DXGud_wF.js +1 -0
- package/dist/ui/assets/help-CEMQqZUR.js +891 -0
- package/dist/ui/assets/help-DJ52_fxN.js +1 -0
- package/dist/ui/assets/index-BCZDAYZi.js +2 -0
- package/dist/ui/assets/index-D6zSNtzo.css +1 -0
- package/dist/ui/assets/knowledge-DeygeGGH.js +1 -0
- package/dist/ui/assets/new-CpD7hOBA.js +1 -0
- package/dist/ui/assets/new-DHTg3Dqq.js +1 -0
- package/dist/ui/assets/new-s8c0M75X.js +1 -0
- package/dist/ui/assets/prompts-BgOmdxgM.js +295 -0
- package/dist/ui/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/dist/ui/assets/search-EpJhdP2a.js +1 -0
- package/dist/ui/assets/skill-y9pizyqE.js +1 -0
- package/dist/ui/assets/skills-Cga9iUZN.js +1 -0
- package/dist/ui/assets/tasks-CobouTKV.js +1 -0
- package/dist/ui/assets/tools-JxKH5BDF.js +1 -0
- package/dist/ui/assets/vendor-graph-BWpSgpMe.js +321 -0
- package/dist/ui/assets/vendor-markdown-CT8ZVEPu.js +50 -0
- package/dist/ui/assets/vendor-md-editor-DmWafJvr.js +44 -0
- package/dist/ui/assets/{index-kKd4mVrh.css → vendor-md-editor-HrwGbQou.css} +1 -1
- package/dist/ui/assets/vendor-mui-BPj7d3Sw.js +139 -0
- package/dist/ui/assets/vendor-mui-icons-B196sG3f.js +1 -0
- package/dist/ui/assets/vendor-react-CHUjhoxh.js +11 -0
- package/dist/ui/index.html +11 -3
- package/package.json +6 -3
- package/dist/ui/assets/index-0hRezICt.js +0 -1702
package/LICENSE
CHANGED
|
@@ -1,15 +1,87 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright (c) 2025-2026 Graph Memory Team
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Elastic License 2.0 (ELv2)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
-
copyright notice and this permission notice appear in all copies.
|
|
5
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
14
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
15
|
+
available, and prepare derivative works of the software, in each case subject
|
|
16
|
+
to the limitations and conditions below.
|
|
17
|
+
|
|
18
|
+
## Limitations
|
|
19
|
+
|
|
20
|
+
You may not provide the software to third parties as a hosted or managed
|
|
21
|
+
service, where the service provides users with access to any substantial set
|
|
22
|
+
of the features or functionality of the software.
|
|
23
|
+
|
|
24
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
25
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
26
|
+
software that is protected by the license key.
|
|
27
|
+
|
|
28
|
+
You may not alter, remove, or obscure any licensing, copyright, or other
|
|
29
|
+
notices of the licensor in the software. Any use of the licensor's trademarks
|
|
30
|
+
is subject to applicable law.
|
|
31
|
+
|
|
32
|
+
## Patents
|
|
33
|
+
|
|
34
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
35
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
36
|
+
sale, import and have imported the software, in each case subject to the
|
|
37
|
+
limitations and conditions in this license. This license does not cover any
|
|
38
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
39
|
+
the software. If you or your company make any written claim that the software
|
|
40
|
+
infringes or contributes to infringement of any patent, your patent license
|
|
41
|
+
for the software granted under these terms ends immediately. If your company
|
|
42
|
+
makes such a claim, your patent license ends immediately for work on behalf
|
|
43
|
+
of your company.
|
|
44
|
+
|
|
45
|
+
## Notices
|
|
46
|
+
|
|
47
|
+
You must ensure that anyone who gets a copy of any part of the software from
|
|
48
|
+
you also gets a copy of these terms.
|
|
49
|
+
|
|
50
|
+
If you modify the software, you must include in any modified copies of the
|
|
51
|
+
software prominent notices stating that you have modified the software.
|
|
52
|
+
|
|
53
|
+
## No Other Rights
|
|
54
|
+
|
|
55
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
56
|
+
these terms.
|
|
57
|
+
|
|
58
|
+
## Termination
|
|
59
|
+
|
|
60
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
61
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
62
|
+
with a notice of your violation, and you cease all violation of this license
|
|
63
|
+
no later than 30 days after you receive that notice, your licenses will be
|
|
64
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
65
|
+
reinstatement, any additional violation of these terms will cause your
|
|
66
|
+
licenses to terminate automatically and permanently.
|
|
67
|
+
|
|
68
|
+
## No Liability
|
|
69
|
+
|
|
70
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
71
|
+
condition, and the licensor will not be liable to you for any damages arising
|
|
72
|
+
out of these terms or the use or nature of the software, under any kind of
|
|
73
|
+
legal claim.*
|
|
74
|
+
|
|
75
|
+
## Definitions
|
|
76
|
+
|
|
77
|
+
The **licensor** is the entity offering these terms, and the **software** is
|
|
78
|
+
the software the licensor makes available under these terms, including any
|
|
79
|
+
portion of it.
|
|
80
|
+
|
|
81
|
+
**you** refers to the individual or entity agreeing to these terms.
|
|
82
|
+
|
|
83
|
+
**your company** is any legal entity, sole proprietorship, or other kind of
|
|
84
|
+
organization that you work for, plus all organizations that have control over,
|
|
85
|
+
are under the control of, or are under common control with that organization.
|
|
86
|
+
**control** means ownership of substantially all the assets of an entity, or
|
|
87
|
+
the power to direct the management and policies of an entity.
|
package/README.md
CHANGED
|
@@ -170,6 +170,12 @@ Full documentation is in [docs/](docs/README.md):
|
|
|
170
170
|
- **UI**: [architecture](docs/ui-architecture.md), [features](docs/ui-features.md), [patterns](docs/ui-patterns.md)
|
|
171
171
|
- **Development**: [testing](docs/testing.md), [API patterns](docs/api-patterns.md)
|
|
172
172
|
|
|
173
|
+
## Contributing
|
|
174
|
+
|
|
175
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
176
|
+
|
|
177
|
+
For security vulnerabilities, see [SECURITY.md](SECURITY.md).
|
|
178
|
+
|
|
173
179
|
## License
|
|
174
180
|
|
|
175
|
-
|
|
181
|
+
[Elastic License 2.0 (ELv2)](LICENSE) — free to use, modify, and self-host. Not permitted to offer as a managed/hosted service.
|
package/dist/api/index.js
CHANGED
|
@@ -46,6 +46,9 @@ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamable
|
|
|
46
46
|
const embedder_1 = require("../lib/embedder");
|
|
47
47
|
const index_1 = require("../api/rest/index");
|
|
48
48
|
const websocket_1 = require("../api/rest/websocket");
|
|
49
|
+
const access_1 = require("../lib/access");
|
|
50
|
+
const defaults_1 = require("../lib/defaults");
|
|
51
|
+
const multi_config_1 = require("../lib/multi-config");
|
|
49
52
|
const docs_1 = require("../graphs/docs");
|
|
50
53
|
const code_1 = require("../graphs/code");
|
|
51
54
|
const knowledge_1 = require("../graphs/knowledge");
|
|
@@ -146,7 +149,7 @@ function buildInstructions(ctx) {
|
|
|
146
149
|
}
|
|
147
150
|
/**
|
|
148
151
|
* Creates the McpServer with all tools wired to the given graphs.
|
|
149
|
-
* Pass docGraph to enable the
|
|
152
|
+
* Pass docGraph to enable the 9 doc tools (5 base + 4 code-block) + 1 cross_references (needs codeGraph too);
|
|
150
153
|
* pass codeGraph to enable the 5 code tools;
|
|
151
154
|
* pass fileIndexGraph to enable the 3 file index tools.
|
|
152
155
|
* cross_references requires both docGraph and codeGraph.
|
|
@@ -156,7 +159,7 @@ function buildInstructions(ctx) {
|
|
|
156
159
|
* Tests typically pass a single function; CLI passes a map for per-graph models.
|
|
157
160
|
* @param mutationQueue Optional PromiseQueue to serialize mutation tool handlers.
|
|
158
161
|
*/
|
|
159
|
-
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext) {
|
|
162
|
+
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext, readonlyGraphs, userAccess) {
|
|
160
163
|
// Backward-compat: single EmbedFn → use for both document and query
|
|
161
164
|
const defaultPair = { document: (q) => (0, embedder_1.embed)(q, ''), query: (q) => (0, embedder_1.embed)(q, '') };
|
|
162
165
|
const fns = typeof embedFn === 'function'
|
|
@@ -181,11 +184,34 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
181
184
|
const server = new mcp_js_1.McpServer({ name: 'graphmemory', version: '1.2.0' }, instructions ? { instructions } : undefined);
|
|
182
185
|
// Mutation tools are registered through mutServer to serialize concurrent writes
|
|
183
186
|
const mutServer = mutationQueue ? createMutationServer(server, mutationQueue) : server;
|
|
187
|
+
// Check if mutation tools should be registered for a graph:
|
|
188
|
+
// - graph must not be readonly (global setting — tools hidden for all)
|
|
189
|
+
// - user must have write access (per-user — tools hidden for this user)
|
|
190
|
+
// - if no userAccess map, all mutations are allowed (no auth configured)
|
|
191
|
+
const canMutate = (graphName) => {
|
|
192
|
+
if (readonlyGraphs?.has(graphName))
|
|
193
|
+
return false;
|
|
194
|
+
if (userAccess) {
|
|
195
|
+
const level = userAccess.get(graphName);
|
|
196
|
+
if (level && !(0, access_1.canWrite)(level))
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
};
|
|
201
|
+
// Check if a graph's tools should be registered at all (deny = no tools)
|
|
202
|
+
const canAccess = (graphName) => {
|
|
203
|
+
if (!userAccess)
|
|
204
|
+
return true;
|
|
205
|
+
const level = userAccess.get(graphName);
|
|
206
|
+
if (level && !(0, access_1.canRead)(level))
|
|
207
|
+
return false;
|
|
208
|
+
return true;
|
|
209
|
+
};
|
|
184
210
|
// Context tool (always registered)
|
|
185
211
|
getContext.register(server, sessionContext);
|
|
186
212
|
const ext = { docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, skillGraph };
|
|
187
|
-
// Docs tools (only when docGraph is provided)
|
|
188
|
-
if (docGraph) {
|
|
213
|
+
// Docs tools (only when docGraph is provided and user has access)
|
|
214
|
+
if (docGraph && canAccess('docs')) {
|
|
189
215
|
const docMgr = new docs_1.DocGraphManager(docGraph, fns.docs, ext);
|
|
190
216
|
listTopics.register(server, docMgr);
|
|
191
217
|
getToc.register(server, docMgr);
|
|
@@ -197,13 +223,13 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
197
223
|
listSnippets.register(server, docMgr);
|
|
198
224
|
explainSymbol.register(server, docMgr);
|
|
199
225
|
// Cross-graph tools (require both docGraph and codeGraph)
|
|
200
|
-
if (codeGraph) {
|
|
226
|
+
if (codeGraph && canAccess('code')) {
|
|
201
227
|
const codeMgrForCross = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
202
228
|
crossReferences.register(server, docMgr, codeMgrForCross);
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
|
-
// Code tools (only when codeGraph is provided)
|
|
206
|
-
if (codeGraph) {
|
|
231
|
+
// Code tools (only when codeGraph is provided and user has access)
|
|
232
|
+
if (codeGraph && canAccess('code')) {
|
|
207
233
|
const codeMgr = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
208
234
|
listFiles.register(server, codeMgr);
|
|
209
235
|
getFileSymbols.register(server, codeMgr);
|
|
@@ -211,93 +237,96 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
211
237
|
getSymbol.register(server, codeMgr);
|
|
212
238
|
searchCodeFiles.register(server, codeMgr);
|
|
213
239
|
}
|
|
214
|
-
// File index tools (
|
|
215
|
-
if (fileIndexGraph) {
|
|
240
|
+
// File index tools (when fileIndexGraph is provided and user has access)
|
|
241
|
+
if (fileIndexGraph && canAccess('files')) {
|
|
216
242
|
const fileIndexMgr = new file_index_1.FileIndexGraphManager(fileIndexGraph, fns.files, ext);
|
|
217
243
|
listAllFiles.register(server, fileIndexMgr);
|
|
218
244
|
searchAllFiles.register(server, fileIndexMgr);
|
|
219
245
|
getFileInfo.register(server, fileIndexMgr);
|
|
220
246
|
}
|
|
221
|
-
// Knowledge tools
|
|
222
|
-
|
|
223
|
-
if (knowledgeGraph) {
|
|
247
|
+
// Knowledge tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
248
|
+
if (knowledgeGraph && canAccess('knowledge')) {
|
|
224
249
|
const ctx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
225
250
|
const knowledgeMgr = new knowledge_1.KnowledgeGraphManager(knowledgeGraph, fns.knowledge, ctx, {
|
|
226
251
|
docGraph, codeGraph, fileIndexGraph, taskGraph, skillGraph,
|
|
227
252
|
});
|
|
228
|
-
createNote.register(mutServer, knowledgeMgr);
|
|
229
|
-
updateNote.register(mutServer, knowledgeMgr);
|
|
230
|
-
deleteNote.register(mutServer, knowledgeMgr);
|
|
231
253
|
getNote.register(server, knowledgeMgr);
|
|
232
254
|
listNotes.register(server, knowledgeMgr);
|
|
233
255
|
searchNotes.register(server, knowledgeMgr);
|
|
234
|
-
createRelation.register(mutServer, knowledgeMgr);
|
|
235
|
-
deleteRelation.register(mutServer, knowledgeMgr);
|
|
236
256
|
listRelations.register(server, knowledgeMgr);
|
|
237
257
|
findLinkedNotes.register(server, knowledgeMgr);
|
|
238
|
-
|
|
239
|
-
|
|
258
|
+
if (canMutate('knowledge')) {
|
|
259
|
+
createNote.register(mutServer, knowledgeMgr);
|
|
260
|
+
updateNote.register(mutServer, knowledgeMgr);
|
|
261
|
+
deleteNote.register(mutServer, knowledgeMgr);
|
|
262
|
+
createRelation.register(mutServer, knowledgeMgr);
|
|
263
|
+
deleteRelation.register(mutServer, knowledgeMgr);
|
|
264
|
+
addNoteAttachment.register(mutServer, knowledgeMgr);
|
|
265
|
+
removeNoteAttachment.register(mutServer, knowledgeMgr);
|
|
266
|
+
}
|
|
240
267
|
}
|
|
241
|
-
// Task tools
|
|
242
|
-
|
|
243
|
-
if (taskGraph) {
|
|
268
|
+
// Task tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
269
|
+
if (taskGraph && canAccess('tasks')) {
|
|
244
270
|
const taskCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
245
271
|
const taskMgr = new task_1.TaskGraphManager(taskGraph, fns.tasks, taskCtx, {
|
|
246
272
|
docGraph, codeGraph, knowledgeGraph, fileIndexGraph, skillGraph,
|
|
247
273
|
});
|
|
248
|
-
createTask.register(mutServer, taskMgr);
|
|
249
|
-
updateTask.register(mutServer, taskMgr);
|
|
250
|
-
deleteTask.register(mutServer, taskMgr);
|
|
251
274
|
getTask.register(server, taskMgr);
|
|
252
275
|
listTasksTool.register(server, taskMgr);
|
|
253
276
|
searchTasksTool.register(server, taskMgr);
|
|
254
|
-
moveTask.register(mutServer, taskMgr);
|
|
255
|
-
linkTask.register(mutServer, taskMgr);
|
|
256
|
-
createTaskLink.register(mutServer, taskMgr);
|
|
257
|
-
deleteTaskLink.register(mutServer, taskMgr);
|
|
258
277
|
findLinkedTasks.register(server, taskMgr);
|
|
259
|
-
|
|
260
|
-
|
|
278
|
+
if (canMutate('tasks')) {
|
|
279
|
+
createTask.register(mutServer, taskMgr);
|
|
280
|
+
updateTask.register(mutServer, taskMgr);
|
|
281
|
+
deleteTask.register(mutServer, taskMgr);
|
|
282
|
+
moveTask.register(mutServer, taskMgr);
|
|
283
|
+
linkTask.register(mutServer, taskMgr);
|
|
284
|
+
createTaskLink.register(mutServer, taskMgr);
|
|
285
|
+
deleteTaskLink.register(mutServer, taskMgr);
|
|
286
|
+
addTaskAttachment.register(mutServer, taskMgr);
|
|
287
|
+
removeTaskAttachment.register(mutServer, taskMgr);
|
|
288
|
+
}
|
|
261
289
|
}
|
|
262
|
-
// Skill tools
|
|
263
|
-
if (skillGraph) {
|
|
290
|
+
// Skill tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
291
|
+
if (skillGraph && canAccess('skills')) {
|
|
264
292
|
const skillCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
265
293
|
const skillMgr = new skill_1.SkillGraphManager(skillGraph, fns.skills, skillCtx, {
|
|
266
294
|
docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph,
|
|
267
295
|
});
|
|
268
|
-
createSkillTool.register(mutServer, skillMgr);
|
|
269
|
-
updateSkillTool.register(mutServer, skillMgr);
|
|
270
|
-
deleteSkillTool.register(mutServer, skillMgr);
|
|
271
296
|
getSkillTool.register(server, skillMgr);
|
|
272
297
|
listSkillsTool.register(server, skillMgr);
|
|
273
298
|
searchSkillsTool.register(server, skillMgr);
|
|
274
|
-
linkSkill.register(mutServer, skillMgr);
|
|
275
|
-
createSkillLink.register(mutServer, skillMgr);
|
|
276
|
-
deleteSkillLink.register(mutServer, skillMgr);
|
|
277
299
|
findLinkedSkills.register(server, skillMgr);
|
|
278
|
-
addSkillAttachment.register(mutServer, skillMgr);
|
|
279
|
-
removeSkillAttachment.register(mutServer, skillMgr);
|
|
280
300
|
recallSkills.register(server, skillMgr);
|
|
281
|
-
|
|
301
|
+
if (canMutate('skills')) {
|
|
302
|
+
createSkillTool.register(mutServer, skillMgr);
|
|
303
|
+
updateSkillTool.register(mutServer, skillMgr);
|
|
304
|
+
deleteSkillTool.register(mutServer, skillMgr);
|
|
305
|
+
linkSkill.register(mutServer, skillMgr);
|
|
306
|
+
createSkillLink.register(mutServer, skillMgr);
|
|
307
|
+
deleteSkillLink.register(mutServer, skillMgr);
|
|
308
|
+
addSkillAttachment.register(mutServer, skillMgr);
|
|
309
|
+
removeSkillAttachment.register(mutServer, skillMgr);
|
|
310
|
+
bumpSkillUsage.register(mutServer, skillMgr);
|
|
311
|
+
}
|
|
282
312
|
}
|
|
283
313
|
return server;
|
|
284
314
|
}
|
|
285
315
|
// ---------------------------------------------------------------------------
|
|
286
316
|
// HTTP transport (Streamable HTTP)
|
|
287
317
|
// ---------------------------------------------------------------------------
|
|
288
|
-
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
289
318
|
async function collectBody(req) {
|
|
290
319
|
const chunks = [];
|
|
291
320
|
let size = 0;
|
|
292
321
|
for await (const chunk of req) {
|
|
293
322
|
size += chunk.length;
|
|
294
|
-
if (size > MAX_BODY_SIZE)
|
|
323
|
+
if (size > defaults_1.MAX_BODY_SIZE)
|
|
295
324
|
throw new Error('Request body too large');
|
|
296
325
|
chunks.push(chunk);
|
|
297
326
|
}
|
|
298
327
|
return JSON.parse(Buffer.concat(chunks).toString());
|
|
299
328
|
}
|
|
300
|
-
async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, projectDir, skillGraph, sessionContext) {
|
|
329
|
+
async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, projectDir, skillGraph, sessionContext, readonlyGraphs) {
|
|
301
330
|
const sessions = new Map();
|
|
302
331
|
// Sweep stale sessions every 60s
|
|
303
332
|
const sweepInterval = setInterval(() => {
|
|
@@ -309,7 +338,7 @@ async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph
|
|
|
309
338
|
process.stderr.write(`[http] Session ${sid} timed out\n`);
|
|
310
339
|
}
|
|
311
340
|
}
|
|
312
|
-
},
|
|
341
|
+
}, defaults_1.SESSION_SWEEP_INTERVAL_MS);
|
|
313
342
|
sweepInterval.unref();
|
|
314
343
|
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
315
344
|
try {
|
|
@@ -344,7 +373,7 @@ async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph
|
|
|
344
373
|
if (sid)
|
|
345
374
|
sessions.delete(sid);
|
|
346
375
|
};
|
|
347
|
-
const mcpServer = createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, undefined, projectDir, skillGraph, sessionContext);
|
|
376
|
+
const mcpServer = createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, undefined, projectDir, skillGraph, sessionContext, readonlyGraphs);
|
|
348
377
|
await mcpServer.connect(transport);
|
|
349
378
|
await transport.handleRequest(req, res, body);
|
|
350
379
|
}
|
|
@@ -372,7 +401,7 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
372
401
|
process.stderr.write(`[http] Session ${sid} (project: ${s.projectId}) timed out\n`);
|
|
373
402
|
}
|
|
374
403
|
}
|
|
375
|
-
},
|
|
404
|
+
}, defaults_1.SESSION_SWEEP_INTERVAL_MS);
|
|
376
405
|
sweepInterval.unref();
|
|
377
406
|
// Express app handles /api/* routes
|
|
378
407
|
const restApp = (0, index_1.createRestApp)(projectManager, restOptions);
|
|
@@ -439,6 +468,23 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
439
468
|
res.writeHead(404).end(JSON.stringify({ error: `Project "${projectId}" not found` }));
|
|
440
469
|
return;
|
|
441
470
|
}
|
|
471
|
+
// Auth: if users configured, require valid API key
|
|
472
|
+
const users = restOptions?.users ?? {};
|
|
473
|
+
const hasUsers = Object.keys(users).length > 0;
|
|
474
|
+
let userId;
|
|
475
|
+
if (hasUsers) {
|
|
476
|
+
const auth = req.headers.authorization;
|
|
477
|
+
if (!auth?.startsWith('Bearer ') || auth.length <= 7) {
|
|
478
|
+
res.writeHead(401).end(JSON.stringify({ error: 'API key required' }));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const result = (0, access_1.resolveUserFromApiKey)(auth.slice(7), users);
|
|
482
|
+
if (!result) {
|
|
483
|
+
res.writeHead(401).end(JSON.stringify({ error: 'Invalid API key' }));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
userId = result.userId;
|
|
487
|
+
}
|
|
442
488
|
// Build session context (auto-detect workspace if not in URL)
|
|
443
489
|
const ws = workspaceId
|
|
444
490
|
? projectManager.getWorkspace(workspaceId)
|
|
@@ -447,13 +493,38 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
447
493
|
projectId,
|
|
448
494
|
workspaceId: ws?.id,
|
|
449
495
|
workspaceProjects: ws?.config.projects,
|
|
496
|
+
userId,
|
|
450
497
|
};
|
|
498
|
+
// Build readonly set from config + workspace overrides
|
|
499
|
+
const mcpReadonlyGraphs = new Set();
|
|
500
|
+
for (const gn of multi_config_1.GRAPH_NAMES) {
|
|
501
|
+
if (project.config.graphConfigs[gn].readonly)
|
|
502
|
+
mcpReadonlyGraphs.add(gn);
|
|
503
|
+
}
|
|
504
|
+
if (project.workspaceId) {
|
|
505
|
+
const wsInst = projectManager.getWorkspace(project.workspaceId);
|
|
506
|
+
if (wsInst) {
|
|
507
|
+
for (const gn of ['knowledge', 'tasks', 'skills']) {
|
|
508
|
+
if (wsInst.config.graphConfigs[gn].readonly)
|
|
509
|
+
mcpReadonlyGraphs.add(gn);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// Build per-user access map
|
|
514
|
+
let mcpUserAccess;
|
|
515
|
+
if (userId && restOptions?.serverConfig) {
|
|
516
|
+
mcpUserAccess = new Map();
|
|
517
|
+
for (const gn of multi_config_1.GRAPH_NAMES) {
|
|
518
|
+
const level = (0, access_1.resolveAccess)(userId, gn, project.config, restOptions.serverConfig, ws?.config);
|
|
519
|
+
mcpUserAccess.set(gn, level);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
451
522
|
const body = await collectBody(req);
|
|
452
523
|
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
453
524
|
sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
|
|
454
525
|
onsessioninitialized: (sid) => {
|
|
455
|
-
sessions.set(sid, { projectId, workspaceId: ws?.id, server: mcpServer, transport, lastActivity: Date.now() });
|
|
456
|
-
process.stderr.write(`[http] Session ${sid} started (project: ${projectId}${ws ? `, workspace: ${ws.id}` : ''})\n`);
|
|
526
|
+
sessions.set(sid, { projectId, workspaceId: ws?.id, userId, server: mcpServer, transport, lastActivity: Date.now() });
|
|
527
|
+
process.stderr.write(`[http] Session ${sid} started (project: ${projectId}${ws ? `, workspace: ${ws.id}` : ''}${userId ? `, user: ${userId}` : ''})\n`);
|
|
457
528
|
},
|
|
458
529
|
});
|
|
459
530
|
transport.onclose = () => {
|
|
@@ -461,7 +532,7 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
461
532
|
if (sid)
|
|
462
533
|
sessions.delete(sid);
|
|
463
534
|
};
|
|
464
|
-
const mcpServer = createMcpServer(project.docGraph, project.codeGraph, project.knowledgeGraph, project.fileIndexGraph, project.taskGraph, project.embedFns, project.mutationQueue, project.config.projectDir, project.skillGraph, sessionCtx);
|
|
535
|
+
const mcpServer = createMcpServer(project.docGraph, project.codeGraph, project.knowledgeGraph, project.fileIndexGraph, project.taskGraph, project.embedFns, project.mutationQueue, project.config.projectDir, project.skillGraph, sessionCtx, mcpReadonlyGraphs, mcpUserAccess);
|
|
465
536
|
await mcpServer.connect(transport);
|
|
466
537
|
await transport.handleRequest(req, res, body);
|
|
467
538
|
}
|
|
@@ -477,9 +548,35 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
477
548
|
});
|
|
478
549
|
return new Promise((resolve) => {
|
|
479
550
|
httpServer.listen(port, host, () => {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
551
|
+
const base = `http://${host}:${port}`;
|
|
552
|
+
const projects = projectManager.listProjects();
|
|
553
|
+
const workspaces = projectManager.listWorkspaces();
|
|
554
|
+
const lines = [
|
|
555
|
+
'',
|
|
556
|
+
' ╔══════════════════════════════════════════════╗',
|
|
557
|
+
' ║ Graph Memory Server Ready ║',
|
|
558
|
+
' ╚══════════════════════════════════════════════╝',
|
|
559
|
+
'',
|
|
560
|
+
` UI ${base}/ui/`,
|
|
561
|
+
` REST API ${base}/api/`,
|
|
562
|
+
` WebSocket ws://${host}:${port}/api/ws`,
|
|
563
|
+
'',
|
|
564
|
+
' MCP endpoints:',
|
|
565
|
+
];
|
|
566
|
+
for (const id of projects) {
|
|
567
|
+
const ws = projectManager.getProjectWorkspace(id);
|
|
568
|
+
const wsLabel = ws ? ` (${ws.id})` : '';
|
|
569
|
+
lines.push(` ${id}${wsLabel} ${base}/mcp/${id}`);
|
|
570
|
+
}
|
|
571
|
+
if (projects.length === 0) {
|
|
572
|
+
lines.push(' (no projects configured)');
|
|
573
|
+
}
|
|
574
|
+
if (workspaces.length > 0) {
|
|
575
|
+
lines.push('');
|
|
576
|
+
lines.push(` Workspaces: ${workspaces.join(', ')}`);
|
|
577
|
+
}
|
|
578
|
+
lines.push('');
|
|
579
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
483
580
|
resolve(httpServer);
|
|
484
581
|
});
|
|
485
582
|
});
|
package/dist/api/rest/code.js
CHANGED
|
@@ -50,7 +50,8 @@ function createCodeRouter() {
|
|
|
50
50
|
const symbol = p.codeManager.getSymbol(symbolId);
|
|
51
51
|
if (!symbol)
|
|
52
52
|
return res.status(404).json({ error: 'Symbol not found' });
|
|
53
|
-
|
|
53
|
+
const { embedding: _, fileEmbedding: _fe, ...rest } = symbol;
|
|
54
|
+
res.json(rest);
|
|
54
55
|
}
|
|
55
56
|
catch (err) {
|
|
56
57
|
next(err);
|
package/dist/api/rest/docs.js
CHANGED
|
@@ -52,7 +52,8 @@ function createDocsRouter() {
|
|
|
52
52
|
const node = p.docManager.getNode(nodeId);
|
|
53
53
|
if (!node)
|
|
54
54
|
return res.status(404).json({ error: 'Node not found' });
|
|
55
|
-
|
|
55
|
+
const { embedding: _, ...rest } = node;
|
|
56
|
+
res.json(rest);
|
|
56
57
|
}
|
|
57
58
|
catch (err) {
|
|
58
59
|
next(err);
|
package/dist/api/rest/embed.js
CHANGED
|
@@ -8,6 +8,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
8
8
|
const express_1 = require("express");
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const embedder_1 = require("../../lib/embedder");
|
|
11
|
+
const embedding_codec_1 = require("../../lib/embedding-codec");
|
|
11
12
|
/**
|
|
12
13
|
* Create an Express router for the embedding API.
|
|
13
14
|
* POST /api/embed — embed texts using the server's embedding model.
|
|
@@ -16,6 +17,7 @@ function createEmbedRouter(apiConfig, modelName) {
|
|
|
16
17
|
const router = (0, express_1.Router)();
|
|
17
18
|
const embedRequestSchema = zod_1.z.object({
|
|
18
19
|
texts: zod_1.z.array(zod_1.z.string().max(apiConfig.maxTextChars)).min(1).max(apiConfig.maxTexts),
|
|
20
|
+
format: zod_1.z.enum(['json', 'base64']).optional().default('json'),
|
|
19
21
|
});
|
|
20
22
|
router.post('/', async (req, res, next) => {
|
|
21
23
|
try {
|
|
@@ -34,7 +36,12 @@ function createEmbedRouter(apiConfig, modelName) {
|
|
|
34
36
|
const parsed = embedRequestSchema.parse(req.body);
|
|
35
37
|
const inputs = parsed.texts.map(text => ({ title: text, content: '' }));
|
|
36
38
|
const embeddings = await (0, embedder_1.embedBatch)(inputs, modelName);
|
|
37
|
-
|
|
39
|
+
if (parsed.format === 'base64') {
|
|
40
|
+
res.json({ embeddings: embeddings.map(e => (0, embedding_codec_1.float32ToBase64)(e)), format: 'base64' });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
res.json({ embeddings });
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
catch (err) {
|
|
40
47
|
if (err?.name === 'ZodError') {
|