@graphmemory/server 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +84 -12
- package/README.md +7 -1
- package/dist/api/index.js +147 -50
- package/dist/api/rest/index.js +35 -15
- package/dist/api/rest/tools.js +8 -1
- 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 +14 -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 +14 -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 +14 -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 +61 -51
- package/dist/cli/indexer.js +60 -28
- package/dist/graphs/code.js +70 -7
- package/dist/graphs/docs.js +15 -2
- package/dist/graphs/file-index.js +17 -3
- package/dist/graphs/file-lang.js +1 -1
- package/dist/graphs/knowledge.js +20 -3
- package/dist/graphs/skill.js +23 -4
- package/dist/graphs/task.js +23 -4
- package/dist/lib/embedding-codec.js +65 -0
- package/dist/lib/jwt.js +4 -4
- 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 +59 -31
- package/dist/lib/parsers/languages/registry.js +2 -2
- package/dist/lib/parsers/languages/typescript.js +195 -44
- package/dist/lib/project-manager.js +14 -10
- package/dist/lib/search/bm25.js +18 -1
- package/dist/lib/search/code.js +12 -3
- 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 +2 -2
- 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,8 @@ 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 multi_config_1 = require("../lib/multi-config");
|
|
49
51
|
const docs_1 = require("../graphs/docs");
|
|
50
52
|
const code_1 = require("../graphs/code");
|
|
51
53
|
const knowledge_1 = require("../graphs/knowledge");
|
|
@@ -146,7 +148,7 @@ function buildInstructions(ctx) {
|
|
|
146
148
|
}
|
|
147
149
|
/**
|
|
148
150
|
* Creates the McpServer with all tools wired to the given graphs.
|
|
149
|
-
* Pass docGraph to enable the
|
|
151
|
+
* Pass docGraph to enable the 9 doc tools (5 base + 4 code-block) + 1 cross_references (needs codeGraph too);
|
|
150
152
|
* pass codeGraph to enable the 5 code tools;
|
|
151
153
|
* pass fileIndexGraph to enable the 3 file index tools.
|
|
152
154
|
* cross_references requires both docGraph and codeGraph.
|
|
@@ -156,7 +158,7 @@ function buildInstructions(ctx) {
|
|
|
156
158
|
* Tests typically pass a single function; CLI passes a map for per-graph models.
|
|
157
159
|
* @param mutationQueue Optional PromiseQueue to serialize mutation tool handlers.
|
|
158
160
|
*/
|
|
159
|
-
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext) {
|
|
161
|
+
function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, embedFn, mutationQueue, projectDir, skillGraph, sessionContext, readonlyGraphs, userAccess) {
|
|
160
162
|
// Backward-compat: single EmbedFn → use for both document and query
|
|
161
163
|
const defaultPair = { document: (q) => (0, embedder_1.embed)(q, ''), query: (q) => (0, embedder_1.embed)(q, '') };
|
|
162
164
|
const fns = typeof embedFn === 'function'
|
|
@@ -181,11 +183,34 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
181
183
|
const server = new mcp_js_1.McpServer({ name: 'graphmemory', version: '1.2.0' }, instructions ? { instructions } : undefined);
|
|
182
184
|
// Mutation tools are registered through mutServer to serialize concurrent writes
|
|
183
185
|
const mutServer = mutationQueue ? createMutationServer(server, mutationQueue) : server;
|
|
186
|
+
// Check if mutation tools should be registered for a graph:
|
|
187
|
+
// - graph must not be readonly (global setting — tools hidden for all)
|
|
188
|
+
// - user must have write access (per-user — tools hidden for this user)
|
|
189
|
+
// - if no userAccess map, all mutations are allowed (no auth configured)
|
|
190
|
+
const canMutate = (graphName) => {
|
|
191
|
+
if (readonlyGraphs?.has(graphName))
|
|
192
|
+
return false;
|
|
193
|
+
if (userAccess) {
|
|
194
|
+
const level = userAccess.get(graphName);
|
|
195
|
+
if (level && !(0, access_1.canWrite)(level))
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
// Check if a graph's tools should be registered at all (deny = no tools)
|
|
201
|
+
const canAccess = (graphName) => {
|
|
202
|
+
if (!userAccess)
|
|
203
|
+
return true;
|
|
204
|
+
const level = userAccess.get(graphName);
|
|
205
|
+
if (level && !(0, access_1.canRead)(level))
|
|
206
|
+
return false;
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
184
209
|
// Context tool (always registered)
|
|
185
210
|
getContext.register(server, sessionContext);
|
|
186
211
|
const ext = { docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph, skillGraph };
|
|
187
|
-
// Docs tools (only when docGraph is provided)
|
|
188
|
-
if (docGraph) {
|
|
212
|
+
// Docs tools (only when docGraph is provided and user has access)
|
|
213
|
+
if (docGraph && canAccess('docs')) {
|
|
189
214
|
const docMgr = new docs_1.DocGraphManager(docGraph, fns.docs, ext);
|
|
190
215
|
listTopics.register(server, docMgr);
|
|
191
216
|
getToc.register(server, docMgr);
|
|
@@ -197,13 +222,13 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
197
222
|
listSnippets.register(server, docMgr);
|
|
198
223
|
explainSymbol.register(server, docMgr);
|
|
199
224
|
// Cross-graph tools (require both docGraph and codeGraph)
|
|
200
|
-
if (codeGraph) {
|
|
225
|
+
if (codeGraph && canAccess('code')) {
|
|
201
226
|
const codeMgrForCross = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
202
227
|
crossReferences.register(server, docMgr, codeMgrForCross);
|
|
203
228
|
}
|
|
204
229
|
}
|
|
205
|
-
// Code tools (only when codeGraph is provided)
|
|
206
|
-
if (codeGraph) {
|
|
230
|
+
// Code tools (only when codeGraph is provided and user has access)
|
|
231
|
+
if (codeGraph && canAccess('code')) {
|
|
207
232
|
const codeMgr = new code_1.CodeGraphManager(codeGraph, fns.code, ext);
|
|
208
233
|
listFiles.register(server, codeMgr);
|
|
209
234
|
getFileSymbols.register(server, codeMgr);
|
|
@@ -211,74 +236,78 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
211
236
|
getSymbol.register(server, codeMgr);
|
|
212
237
|
searchCodeFiles.register(server, codeMgr);
|
|
213
238
|
}
|
|
214
|
-
// File index tools (
|
|
215
|
-
if (fileIndexGraph) {
|
|
239
|
+
// File index tools (when fileIndexGraph is provided and user has access)
|
|
240
|
+
if (fileIndexGraph && canAccess('files')) {
|
|
216
241
|
const fileIndexMgr = new file_index_1.FileIndexGraphManager(fileIndexGraph, fns.files, ext);
|
|
217
242
|
listAllFiles.register(server, fileIndexMgr);
|
|
218
243
|
searchAllFiles.register(server, fileIndexMgr);
|
|
219
244
|
getFileInfo.register(server, fileIndexMgr);
|
|
220
245
|
}
|
|
221
|
-
// Knowledge tools
|
|
222
|
-
|
|
223
|
-
if (knowledgeGraph) {
|
|
246
|
+
// Knowledge tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
247
|
+
if (knowledgeGraph && canAccess('knowledge')) {
|
|
224
248
|
const ctx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
225
249
|
const knowledgeMgr = new knowledge_1.KnowledgeGraphManager(knowledgeGraph, fns.knowledge, ctx, {
|
|
226
250
|
docGraph, codeGraph, fileIndexGraph, taskGraph, skillGraph,
|
|
227
251
|
});
|
|
228
|
-
createNote.register(mutServer, knowledgeMgr);
|
|
229
|
-
updateNote.register(mutServer, knowledgeMgr);
|
|
230
|
-
deleteNote.register(mutServer, knowledgeMgr);
|
|
231
252
|
getNote.register(server, knowledgeMgr);
|
|
232
253
|
listNotes.register(server, knowledgeMgr);
|
|
233
254
|
searchNotes.register(server, knowledgeMgr);
|
|
234
|
-
createRelation.register(mutServer, knowledgeMgr);
|
|
235
|
-
deleteRelation.register(mutServer, knowledgeMgr);
|
|
236
255
|
listRelations.register(server, knowledgeMgr);
|
|
237
256
|
findLinkedNotes.register(server, knowledgeMgr);
|
|
238
|
-
|
|
239
|
-
|
|
257
|
+
if (canMutate('knowledge')) {
|
|
258
|
+
createNote.register(mutServer, knowledgeMgr);
|
|
259
|
+
updateNote.register(mutServer, knowledgeMgr);
|
|
260
|
+
deleteNote.register(mutServer, knowledgeMgr);
|
|
261
|
+
createRelation.register(mutServer, knowledgeMgr);
|
|
262
|
+
deleteRelation.register(mutServer, knowledgeMgr);
|
|
263
|
+
addNoteAttachment.register(mutServer, knowledgeMgr);
|
|
264
|
+
removeNoteAttachment.register(mutServer, knowledgeMgr);
|
|
265
|
+
}
|
|
240
266
|
}
|
|
241
|
-
// Task tools
|
|
242
|
-
|
|
243
|
-
if (taskGraph) {
|
|
267
|
+
// Task tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
268
|
+
if (taskGraph && canAccess('tasks')) {
|
|
244
269
|
const taskCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
245
270
|
const taskMgr = new task_1.TaskGraphManager(taskGraph, fns.tasks, taskCtx, {
|
|
246
271
|
docGraph, codeGraph, knowledgeGraph, fileIndexGraph, skillGraph,
|
|
247
272
|
});
|
|
248
|
-
createTask.register(mutServer, taskMgr);
|
|
249
|
-
updateTask.register(mutServer, taskMgr);
|
|
250
|
-
deleteTask.register(mutServer, taskMgr);
|
|
251
273
|
getTask.register(server, taskMgr);
|
|
252
274
|
listTasksTool.register(server, taskMgr);
|
|
253
275
|
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
276
|
findLinkedTasks.register(server, taskMgr);
|
|
259
|
-
|
|
260
|
-
|
|
277
|
+
if (canMutate('tasks')) {
|
|
278
|
+
createTask.register(mutServer, taskMgr);
|
|
279
|
+
updateTask.register(mutServer, taskMgr);
|
|
280
|
+
deleteTask.register(mutServer, taskMgr);
|
|
281
|
+
moveTask.register(mutServer, taskMgr);
|
|
282
|
+
linkTask.register(mutServer, taskMgr);
|
|
283
|
+
createTaskLink.register(mutServer, taskMgr);
|
|
284
|
+
deleteTaskLink.register(mutServer, taskMgr);
|
|
285
|
+
addTaskAttachment.register(mutServer, taskMgr);
|
|
286
|
+
removeTaskAttachment.register(mutServer, taskMgr);
|
|
287
|
+
}
|
|
261
288
|
}
|
|
262
|
-
// Skill tools
|
|
263
|
-
if (skillGraph) {
|
|
289
|
+
// Skill tools — read tools gated by canAccess, mutation tools gated by canMutate
|
|
290
|
+
if (skillGraph && canAccess('skills')) {
|
|
264
291
|
const skillCtx = projectDir ? { ...(0, manager_types_1.noopContext)(), projectDir } : (0, manager_types_1.noopContext)();
|
|
265
292
|
const skillMgr = new skill_1.SkillGraphManager(skillGraph, fns.skills, skillCtx, {
|
|
266
293
|
docGraph, codeGraph, knowledgeGraph, fileIndexGraph, taskGraph,
|
|
267
294
|
});
|
|
268
|
-
createSkillTool.register(mutServer, skillMgr);
|
|
269
|
-
updateSkillTool.register(mutServer, skillMgr);
|
|
270
|
-
deleteSkillTool.register(mutServer, skillMgr);
|
|
271
295
|
getSkillTool.register(server, skillMgr);
|
|
272
296
|
listSkillsTool.register(server, skillMgr);
|
|
273
297
|
searchSkillsTool.register(server, skillMgr);
|
|
274
|
-
linkSkill.register(mutServer, skillMgr);
|
|
275
|
-
createSkillLink.register(mutServer, skillMgr);
|
|
276
|
-
deleteSkillLink.register(mutServer, skillMgr);
|
|
277
298
|
findLinkedSkills.register(server, skillMgr);
|
|
278
|
-
addSkillAttachment.register(mutServer, skillMgr);
|
|
279
|
-
removeSkillAttachment.register(mutServer, skillMgr);
|
|
280
299
|
recallSkills.register(server, skillMgr);
|
|
281
|
-
|
|
300
|
+
if (canMutate('skills')) {
|
|
301
|
+
createSkillTool.register(mutServer, skillMgr);
|
|
302
|
+
updateSkillTool.register(mutServer, skillMgr);
|
|
303
|
+
deleteSkillTool.register(mutServer, skillMgr);
|
|
304
|
+
linkSkill.register(mutServer, skillMgr);
|
|
305
|
+
createSkillLink.register(mutServer, skillMgr);
|
|
306
|
+
deleteSkillLink.register(mutServer, skillMgr);
|
|
307
|
+
addSkillAttachment.register(mutServer, skillMgr);
|
|
308
|
+
removeSkillAttachment.register(mutServer, skillMgr);
|
|
309
|
+
bumpSkillUsage.register(mutServer, skillMgr);
|
|
310
|
+
}
|
|
282
311
|
}
|
|
283
312
|
return server;
|
|
284
313
|
}
|
|
@@ -297,7 +326,7 @@ async function collectBody(req) {
|
|
|
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(() => {
|
|
@@ -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
|
}
|
|
@@ -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/index.js
CHANGED
|
@@ -46,7 +46,7 @@ function createRestApp(projectManager, options) {
|
|
|
46
46
|
const users = options?.users ?? {};
|
|
47
47
|
const hasUsers = Object.keys(users).length > 0;
|
|
48
48
|
const corsOrigins = serverConfig?.corsOrigins;
|
|
49
|
-
app.use((0, cors_1.default)(corsOrigins?.length ? { origin: corsOrigins, credentials: true } : {
|
|
49
|
+
app.use((0, cors_1.default)(corsOrigins?.length ? { origin: corsOrigins, credentials: true } : {}));
|
|
50
50
|
app.use(express_1.default.json({ limit: '10mb' }));
|
|
51
51
|
app.use((0, cookie_parser_1.default)());
|
|
52
52
|
// Security headers
|
|
@@ -89,7 +89,7 @@ function createRestApp(projectManager, options) {
|
|
|
89
89
|
const payload = (0, jwt_1.verifyToken)(accessToken, jwtSecret);
|
|
90
90
|
if (payload?.type === 'access' && users[payload.userId]) {
|
|
91
91
|
const user = users[payload.userId];
|
|
92
|
-
return res.json({ required: true, authenticated: true, userId: payload.userId, name: user.name });
|
|
92
|
+
return res.json({ required: true, authenticated: true, userId: payload.userId, name: user.name, apiKey: user.apiKey });
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -170,7 +170,7 @@ function createRestApp(projectManager, options) {
|
|
|
170
170
|
}
|
|
171
171
|
// 2. Bearer apiKey (from MCP/API clients)
|
|
172
172
|
const auth = req.headers.authorization;
|
|
173
|
-
if (auth?.startsWith('Bearer ')) {
|
|
173
|
+
if (auth?.startsWith('Bearer ') && auth.length > 7) {
|
|
174
174
|
const apiKey = auth.slice(7);
|
|
175
175
|
const result = (0, access_1.resolveUserFromApiKey)(apiKey, users);
|
|
176
176
|
if (result) {
|
|
@@ -178,6 +178,7 @@ function createRestApp(projectManager, options) {
|
|
|
178
178
|
req.user = result.user;
|
|
179
179
|
return next();
|
|
180
180
|
}
|
|
181
|
+
// Invalid Bearer token — reject (explicit auth attempt should not fall through)
|
|
181
182
|
return _res.status(401).json({ error: 'Invalid API key' });
|
|
182
183
|
}
|
|
183
184
|
// 3. No auth = anonymous (uses defaultAccess)
|
|
@@ -200,13 +201,16 @@ function createRestApp(projectManager, options) {
|
|
|
200
201
|
const p = projectManager.getProject(id);
|
|
201
202
|
const gc = p.config.graphConfigs;
|
|
202
203
|
const ws = p.workspaceId ? projectManager.getWorkspace(p.workspaceId) : undefined;
|
|
203
|
-
// Per-graph info: enabled
|
|
204
|
+
// Per-graph info: enabled, readonly, access level for current user
|
|
204
205
|
const graphs = {};
|
|
205
206
|
for (const gn of multi_config_1.GRAPH_NAMES) {
|
|
206
|
-
|
|
207
|
+
let access = serverConfig
|
|
207
208
|
? (0, access_1.resolveAccess)(userId, gn, p.config, serverConfig, ws?.config)
|
|
208
209
|
: 'rw';
|
|
209
|
-
|
|
210
|
+
// Cap access for readonly graphs
|
|
211
|
+
if (access === 'rw' && gc[gn].readonly)
|
|
212
|
+
access = 'r';
|
|
213
|
+
graphs[gn] = { enabled: gc[gn].enabled, readonly: gc[gn].readonly, access: gc[gn].enabled ? access : null };
|
|
210
214
|
}
|
|
211
215
|
return {
|
|
212
216
|
id,
|
|
@@ -280,14 +284,23 @@ function createRestApp(projectManager, options) {
|
|
|
280
284
|
// Middleware: check access level for a graph (read or read-write)
|
|
281
285
|
function requireGraphAccess(graphName, level) {
|
|
282
286
|
return (req, _res, next) => {
|
|
287
|
+
const p = req.project;
|
|
288
|
+
// Graph-level readonly: enforce even without auth config
|
|
289
|
+
const isReadonly = p?.config.graphConfigs[graphName]?.readonly;
|
|
290
|
+
if (isReadonly) {
|
|
291
|
+
req.accessLevel = 'r';
|
|
292
|
+
}
|
|
283
293
|
if (!serverConfig)
|
|
284
294
|
return next(); // no config = no auth enforcement
|
|
285
|
-
const p = req.project;
|
|
286
295
|
if (!p)
|
|
287
296
|
return next();
|
|
288
297
|
const userId = req.userId;
|
|
289
298
|
const ws = p.workspaceId ? projectManager.getWorkspace(p.workspaceId) : undefined;
|
|
290
|
-
|
|
299
|
+
let access = (0, access_1.resolveAccess)(userId, graphName, p.config, serverConfig, ws?.config);
|
|
300
|
+
// Graph-level readonly: cap to 'r' regardless of user permissions
|
|
301
|
+
if (access === 'rw' && p.config.graphConfigs[graphName]?.readonly) {
|
|
302
|
+
access = 'r';
|
|
303
|
+
}
|
|
291
304
|
if (!(0, access_1.canRead)(access)) {
|
|
292
305
|
return _res.status(403).json({ error: 'Access denied' });
|
|
293
306
|
}
|
|
@@ -329,7 +342,9 @@ function createRestApp(projectManager, options) {
|
|
|
329
342
|
return true;
|
|
330
343
|
const userId = req.userId;
|
|
331
344
|
const ws = p.workspaceId ? projectManager.getWorkspace(p.workspaceId) : undefined;
|
|
332
|
-
|
|
345
|
+
let access = (0, access_1.resolveAccess)(userId, graphName, p.config, serverConfig, ws?.config);
|
|
346
|
+
if (access === 'rw' && p.config.graphConfigs[graphName]?.readonly)
|
|
347
|
+
access = 'r';
|
|
333
348
|
if (level === 'rw')
|
|
334
349
|
return (0, access_1.canWrite)(access);
|
|
335
350
|
return (0, access_1.canRead)(access);
|
|
@@ -338,16 +353,21 @@ function createRestApp(projectManager, options) {
|
|
|
338
353
|
if (serverConfig?.embeddingApi?.enabled && options?.embeddingApiModelName) {
|
|
339
354
|
app.use('/api/embed', (0, embed_1.createEmbedRouter)(serverConfig.embeddingApi, options.embeddingApiModelName));
|
|
340
355
|
}
|
|
341
|
-
// Serve UI
|
|
356
|
+
// Serve UI at /ui/ path — check dist/ui/ (npm package) then ui/dist/ (dev)
|
|
342
357
|
const uiDistPkg = path_1.default.resolve(__dirname, '../../ui');
|
|
343
358
|
const uiDistDev = path_1.default.resolve(__dirname, '../../../ui/dist');
|
|
344
359
|
const uiDist = fs_1.default.existsSync(uiDistPkg) ? uiDistPkg : uiDistDev;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
360
|
+
// Redirect root to /ui/
|
|
361
|
+
app.get('/', (_req, res) => { res.redirect('/ui/'); });
|
|
362
|
+
// Static files under /ui/
|
|
363
|
+
app.use('/ui', express_1.default.static(uiDist, { redirect: false, index: false }));
|
|
364
|
+
// SPA fallback: serve index.html for all /ui/* routes
|
|
365
|
+
const indexHtml = path_1.default.join(uiDist, 'index.html');
|
|
366
|
+
app.use('/ui', (_req, res, next) => {
|
|
367
|
+
// Skip requests for actual files (assets with extensions like .js, .css, .png)
|
|
368
|
+
if (_req.path.includes('.') && !_req.path.endsWith('.html'))
|
|
349
369
|
return next();
|
|
350
|
-
res.sendFile(
|
|
370
|
+
res.sendFile(indexHtml, (err) => {
|
|
351
371
|
if (err)
|
|
352
372
|
next();
|
|
353
373
|
});
|
package/dist/api/rest/tools.js
CHANGED
|
@@ -5,6 +5,7 @@ const express_1 = require("express");
|
|
|
5
5
|
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
6
6
|
const inMemory_js_1 = require("@modelcontextprotocol/sdk/inMemory.js");
|
|
7
7
|
const index_1 = require("../../api/index");
|
|
8
|
+
const multi_config_1 = require("../../lib/multi-config");
|
|
8
9
|
// Tool category detection based on tool name
|
|
9
10
|
const TOOL_CATEGORIES = {
|
|
10
11
|
get_context: 'context',
|
|
@@ -49,8 +50,14 @@ async function getClient(p, pm) {
|
|
|
49
50
|
workspaceId: ws?.id,
|
|
50
51
|
workspaceProjects: ws?.config.projects,
|
|
51
52
|
};
|
|
53
|
+
// Build readonly set from config for defense-in-depth
|
|
54
|
+
const readonlyGraphs = new Set();
|
|
55
|
+
for (const gn of multi_config_1.GRAPH_NAMES) {
|
|
56
|
+
if (p.config.graphConfigs[gn].readonly)
|
|
57
|
+
readonlyGraphs.add(gn);
|
|
58
|
+
}
|
|
52
59
|
const [serverTransport, clientTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
|
|
53
|
-
const server = (0, index_1.createMcpServer)(p.docGraph, p.codeGraph, p.knowledgeGraph, p.fileIndexGraph, p.taskGraph, p.embedFns, p.mutationQueue, p.config.projectDir, p.skillGraph, sessionCtx);
|
|
60
|
+
const server = (0, index_1.createMcpServer)(p.docGraph, p.codeGraph, p.knowledgeGraph, p.fileIndexGraph, p.taskGraph, p.embedFns, p.mutationQueue, p.config.projectDir, p.skillGraph, sessionCtx, readonlyGraphs.size > 0 ? readonlyGraphs : undefined);
|
|
54
61
|
await server.connect(serverTransport);
|
|
55
62
|
const client = new index_js_1.Client({ name: 'tools-explorer', version: '1.0.0' });
|
|
56
63
|
await client.connect(clientTransport);
|
|
@@ -5,23 +5,26 @@ const zod_1 = require("zod");
|
|
|
5
5
|
function register(server, mgr) {
|
|
6
6
|
server.registerTool('search_code', {
|
|
7
7
|
description: 'Semantic search over the indexed source code. ' +
|
|
8
|
-
'
|
|
9
|
-
'
|
|
8
|
+
'Supports three modes: hybrid (default, combines BM25 keyword + vector similarity), ' +
|
|
9
|
+
'vector (embedding only), keyword (BM25 text matching only). ' +
|
|
10
|
+
'Finds the most relevant symbols (functions, classes, constructors, types) by matching ' +
|
|
11
|
+
'against name, signature, doc comments, and body text, ' +
|
|
10
12
|
'then expands results by following graph edges (imports, contains, extends). ' +
|
|
11
13
|
'Returns an array sorted by relevance score (0–1), each with: ' +
|
|
12
14
|
'id, fileId, kind, name, signature, docComment, startLine, endLine, score. ' +
|
|
13
|
-
'
|
|
15
|
+
'Set includeBody=true to include full source code in results (avoids extra get_symbol calls).',
|
|
14
16
|
inputSchema: {
|
|
15
17
|
query: zod_1.z.string().describe('Natural language or code search query, e.g. "function that loads the graph from disk"'),
|
|
16
|
-
topK: zod_1.z.number().optional().describe('How many top similar symbols to use as seeds (default 5)'),
|
|
17
|
-
bfsDepth: zod_1.z.number().optional().describe('How many hops to follow graph edges from each seed (default 1; 0 = no expansion)'),
|
|
18
|
-
maxResults: zod_1.z.number().optional().describe('Maximum number of results to return (default 20)'),
|
|
19
|
-
minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1; lower values return more results (default 0.
|
|
18
|
+
topK: zod_1.z.number().min(1).max(500).optional().describe('How many top similar symbols to use as seeds (default 5)'),
|
|
19
|
+
bfsDepth: zod_1.z.number().min(0).max(10).optional().describe('How many hops to follow graph edges from each seed (default 1; 0 = no expansion)'),
|
|
20
|
+
maxResults: zod_1.z.number().min(1).max(500).optional().describe('Maximum number of results to return (default 20)'),
|
|
21
|
+
minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1; lower values return more results (default 0.3)'),
|
|
20
22
|
bfsDecay: zod_1.z.number().min(0).max(1).optional().describe('Score multiplier per graph hop (default 0.8)'),
|
|
21
23
|
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional().describe('Search mode: hybrid (default, BM25 + vector), vector (embedding only), keyword (BM25 only)'),
|
|
24
|
+
includeBody: zod_1.z.boolean().optional().describe('Include full source code body in results (default false)'),
|
|
22
25
|
},
|
|
23
|
-
}, async ({ query, topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode }) => {
|
|
24
|
-
const results = await mgr.search(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode });
|
|
26
|
+
}, async ({ query, topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode, includeBody }) => {
|
|
27
|
+
const results = await mgr.search(query, { topK, bfsDepth, maxResults, minScore, bfsDecay, searchMode, includeBody });
|
|
25
28
|
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
26
29
|
});
|
|
27
30
|
}
|
|
@@ -12,7 +12,7 @@ function register(server, mgr) {
|
|
|
12
12
|
'Use this to discover which source files are relevant before diving into symbols with get_file_symbols or search_code.',
|
|
13
13
|
inputSchema: {
|
|
14
14
|
query: zod_1.z.string().describe('Natural language or path search query, e.g. "graph persistence" or "search module"'),
|
|
15
|
-
topK: zod_1.z.number().optional().describe('Maximum number of results to return (default 10)'),
|
|
15
|
+
topK: zod_1.z.number().min(1).max(500).optional().describe('Maximum number of results to return (default 10)'),
|
|
16
16
|
minScore: zod_1.z.number().min(0).max(1).optional().describe('Minimum relevance score 0–1 (default 0.3)'),
|
|
17
17
|
},
|
|
18
18
|
}, async ({ query, topK, minScore }) => {
|