@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,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchTasks = searchTasks;
|
|
4
|
+
const embedder_1 = require("../../lib/embedder");
|
|
5
|
+
const bm25_1 = require("../../lib/search/bm25");
|
|
6
|
+
/**
|
|
7
|
+
* Semantic search over the task graph.
|
|
8
|
+
*
|
|
9
|
+
* 1. Score every node by cosine similarity to the query embedding.
|
|
10
|
+
* 2. Filter seeds below `minScore`, take top `topK`.
|
|
11
|
+
* 3. BFS expansion via relation edges up to `bfsDepth` hops with score decay.
|
|
12
|
+
* 4. De-duplicate, re-filter, sort, cap at `maxResults`.
|
|
13
|
+
*/
|
|
14
|
+
function searchTasks(graph, queryEmbedding, options = {}) {
|
|
15
|
+
const { topK = 5, bfsDepth = 1, maxResults = 20, minScore = 0.5, bfsDecay = 0.8, queryText, bm25Index, searchMode = 'hybrid', rrfK = 60 } = options;
|
|
16
|
+
const useVector = searchMode !== 'keyword';
|
|
17
|
+
const useBm25 = searchMode !== 'vector' && !!queryText && !!bm25Index;
|
|
18
|
+
// --- 1. Score all nodes (skip proxy nodes) ---
|
|
19
|
+
const scored = [];
|
|
20
|
+
if (useVector) {
|
|
21
|
+
graph.forEachNode((id, attrs) => {
|
|
22
|
+
if (attrs.proxyFor)
|
|
23
|
+
return;
|
|
24
|
+
if (attrs.embedding.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
scored.push({ id, score: (0, embedder_1.cosineSimilarity)(queryEmbedding, attrs.embedding) });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (useBm25) {
|
|
30
|
+
const bm25Scores = bm25Index.score(queryText);
|
|
31
|
+
const positiveScored = useVector ? scored.filter(s => s.score > 0) : [];
|
|
32
|
+
if (positiveScored.length > 0) {
|
|
33
|
+
const vectorMap = new Map(positiveScored.map(s => [s.id, s.score]));
|
|
34
|
+
const fused = (0, bm25_1.rrfFuse)(vectorMap, bm25Scores, rrfK);
|
|
35
|
+
scored.length = 0;
|
|
36
|
+
for (const [id, score] of fused)
|
|
37
|
+
scored.push({ id, score });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
scored.length = 0;
|
|
41
|
+
for (const [id, score] of bm25Scores)
|
|
42
|
+
scored.push({ id, score });
|
|
43
|
+
}
|
|
44
|
+
// Normalize scores to 0–1 so minScore threshold works uniformly
|
|
45
|
+
const maxScore = scored.reduce((m, s) => Math.max(m, s.score), 0);
|
|
46
|
+
if (maxScore > 0) {
|
|
47
|
+
for (const s of scored)
|
|
48
|
+
s.score /= maxScore;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (scored.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
scored.sort((a, b) => b.score - a.score);
|
|
54
|
+
// --- 2. Filter seeds ---
|
|
55
|
+
const minS = minScore;
|
|
56
|
+
const seeds = scored.filter(s => s.score >= minS).slice(0, topK);
|
|
57
|
+
if (seeds.length === 0)
|
|
58
|
+
return [];
|
|
59
|
+
// --- 3. BFS expansion ---
|
|
60
|
+
const scoreMap = new Map(seeds.map(s => [s.id, s.score]));
|
|
61
|
+
function bfs(startId, seedScore) {
|
|
62
|
+
const queue = [
|
|
63
|
+
{ id: startId, depth: 0, score: seedScore },
|
|
64
|
+
];
|
|
65
|
+
const visited = new Set();
|
|
66
|
+
while (queue.length > 0) {
|
|
67
|
+
const item = queue.shift();
|
|
68
|
+
if (visited.has(item.id))
|
|
69
|
+
continue;
|
|
70
|
+
visited.add(item.id);
|
|
71
|
+
const prev = scoreMap.get(item.id) ?? -Infinity;
|
|
72
|
+
if (item.score > prev)
|
|
73
|
+
scoreMap.set(item.id, item.score);
|
|
74
|
+
if (item.depth >= bfsDepth)
|
|
75
|
+
continue;
|
|
76
|
+
if (item.score * bfsDecay < minS)
|
|
77
|
+
continue;
|
|
78
|
+
const nextScore = item.score * bfsDecay;
|
|
79
|
+
graph.outNeighbors(item.id).forEach(n => queue.push({ id: n, depth: item.depth + 1, score: nextScore }));
|
|
80
|
+
graph.inNeighbors(item.id).forEach(n => queue.push({ id: n, depth: item.depth + 1, score: nextScore }));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const seed of seeds) {
|
|
84
|
+
bfs(seed.id, seed.score);
|
|
85
|
+
}
|
|
86
|
+
// --- 4. Build results (exclude proxy nodes) ---
|
|
87
|
+
return [...scoreMap.entries()]
|
|
88
|
+
.filter(([id, score]) => score >= minS && !graph.getNodeAttribute(id, 'proxyFor'))
|
|
89
|
+
.map(([id, score]) => {
|
|
90
|
+
const attrs = graph.getNodeAttributes(id);
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
title: attrs.title,
|
|
94
|
+
description: attrs.description,
|
|
95
|
+
status: attrs.status,
|
|
96
|
+
priority: attrs.priority,
|
|
97
|
+
tags: attrs.tags,
|
|
98
|
+
score,
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
.sort((a, b) => b.score - a.score)
|
|
102
|
+
.slice(0, maxResults);
|
|
103
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startWatcher = startWatcher;
|
|
7
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
8
|
+
const micromatch_1 = __importDefault(require("micromatch"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/** Directories that are always excluded from watching (heavy, never useful). */
|
|
11
|
+
const ALWAYS_IGNORED = ['.git', 'node_modules', '.next', '.nuxt', '.turbo', 'dist', 'build', '.graph-memory', '.notes', '.tasks', '.skills'];
|
|
12
|
+
// chokidar 5: watch the directory directly — glob patterns don't fire 'add' for existing files
|
|
13
|
+
function startWatcher(dir, handlers, pattern = '**/*.md', excludePatterns) {
|
|
14
|
+
const matches = (filePath) => {
|
|
15
|
+
const rel = path_1.default.relative(dir, filePath);
|
|
16
|
+
if (excludePatterns && excludePatterns.length > 0 && micromatch_1.default.isMatch(rel, excludePatterns))
|
|
17
|
+
return false;
|
|
18
|
+
return micromatch_1.default.isMatch(rel, pattern);
|
|
19
|
+
};
|
|
20
|
+
// chokidar 5 `ignored` accepts a function — use micromatch for glob exclude patterns
|
|
21
|
+
// plus always skip heavy directories (.git, node_modules, etc.)
|
|
22
|
+
const alwaysIgnoredSet = new Set(ALWAYS_IGNORED.map(d => path_1.default.join(dir, d)));
|
|
23
|
+
const ignored = (filePath) => {
|
|
24
|
+
// Always ignore heavy directories by exact basename match
|
|
25
|
+
if (alwaysIgnoredSet.has(filePath))
|
|
26
|
+
return true;
|
|
27
|
+
// User-defined exclude patterns (glob-based)
|
|
28
|
+
if (excludePatterns && excludePatterns.length > 0) {
|
|
29
|
+
const rel = path_1.default.relative(dir, filePath);
|
|
30
|
+
if (micromatch_1.default.isMatch(rel, excludePatterns))
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
let resolveReady;
|
|
36
|
+
const whenReady = new Promise(resolve => {
|
|
37
|
+
resolveReady = resolve;
|
|
38
|
+
});
|
|
39
|
+
const watcher = chokidar_1.default.watch(dir, {
|
|
40
|
+
ignoreInitial: false,
|
|
41
|
+
persistent: true,
|
|
42
|
+
ignored,
|
|
43
|
+
});
|
|
44
|
+
watcher.on('add', (filePath) => {
|
|
45
|
+
if (matches(filePath))
|
|
46
|
+
handlers.onAdd(filePath);
|
|
47
|
+
});
|
|
48
|
+
watcher.on('change', (filePath) => {
|
|
49
|
+
if (matches(filePath))
|
|
50
|
+
handlers.onChange(filePath);
|
|
51
|
+
});
|
|
52
|
+
watcher.on('unlink', (filePath) => {
|
|
53
|
+
if (matches(filePath))
|
|
54
|
+
handlers.onUnlink(filePath);
|
|
55
|
+
});
|
|
56
|
+
watcher.on('ready', () => {
|
|
57
|
+
process.stderr.write(`[watcher] Ready. Watching ${dir}\n`);
|
|
58
|
+
resolveReady();
|
|
59
|
+
});
|
|
60
|
+
watcher.on('error', (err) => {
|
|
61
|
+
process.stderr.write(`[watcher] Watch error: ${err}\n`);
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
whenReady,
|
|
65
|
+
close: () => watcher.close(),
|
|
66
|
+
};
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prih/mcp-graph-memory",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "MCP server for semantic graph memory from markdown files",
|
|
5
|
+
"main": "dist/cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-graph-memory": "dist/cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "npm run build:server && npm run build:ui",
|
|
11
|
+
"build:server": "rm -rf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
12
|
+
"build:ui": "cd ui && npm run build",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"cli": "node dist/cli/index.js",
|
|
16
|
+
"cli:dev": "tsx src/cli/index.ts",
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"test:watch": "jest --watch"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/prih/mcp-graph-memory.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/prih/mcp-graph-memory",
|
|
25
|
+
"bugs": "https://github.com/prih/mcp-graph-memory/issues",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"graph",
|
|
30
|
+
"memory",
|
|
31
|
+
"semantic-search",
|
|
32
|
+
"knowledge-graph",
|
|
33
|
+
"embeddings",
|
|
34
|
+
"documentation",
|
|
35
|
+
"code-indexing"
|
|
36
|
+
],
|
|
37
|
+
"author": "prih <prihmail@gmail.com>",
|
|
38
|
+
"license": "ISC",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/",
|
|
44
|
+
"ui/dist/",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@huggingface/transformers": "^3.8.1",
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
53
|
+
"chokidar": "^5.0.0",
|
|
54
|
+
"commander": "^14.0.3",
|
|
55
|
+
"cors": "^2.8.6",
|
|
56
|
+
"dotenv": "^17.3.1",
|
|
57
|
+
"express": "^5.2.1",
|
|
58
|
+
"graphology": "^0.26.0",
|
|
59
|
+
"micromatch": "^4.0.8",
|
|
60
|
+
"mime": "^4.1.0",
|
|
61
|
+
"multer": "^2.1.1",
|
|
62
|
+
"ts-morph": "^27.0.2",
|
|
63
|
+
"ws": "^8.19.0",
|
|
64
|
+
"yaml": "^2.8.2",
|
|
65
|
+
"zod": "^4.3.6"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/cors": "^2.8.19",
|
|
69
|
+
"@types/express": "^5.0.6",
|
|
70
|
+
"@types/jest": "^30.0.0",
|
|
71
|
+
"@types/micromatch": "^4.0.10",
|
|
72
|
+
"@types/multer": "^2.1.0",
|
|
73
|
+
"@types/node": "^25.4.0",
|
|
74
|
+
"@types/supertest": "^7.2.0",
|
|
75
|
+
"@types/ws": "^8.18.1",
|
|
76
|
+
"jest": "^30.3.0",
|
|
77
|
+
"supertest": "^7.2.2",
|
|
78
|
+
"ts-jest": "^29.4.6",
|
|
79
|
+
"tsc-alias": "^1.8.16",
|
|
80
|
+
"tsx": "^4.21.0",
|
|
81
|
+
"typescript": "^5.9.3"
|
|
82
|
+
}
|
|
83
|
+
}
|
package/ui/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Graph Memory — Web UI
|
|
2
|
+
|
|
3
|
+
React web interface for browsing and managing graph memory data.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
| Library | Version | Purpose |
|
|
8
|
+
|---------|---------|---------|
|
|
9
|
+
| React | 19 | UI framework |
|
|
10
|
+
| Material UI (MUI) | 7 | Component library |
|
|
11
|
+
| React Router DOM | 7 | Client-side routing |
|
|
12
|
+
| Cytoscape.js | 3.33 | Graph visualization |
|
|
13
|
+
| Vite | 8 | Build tool + dev server |
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
Feature-Sliced Design (FSD):
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
├── app/ # Routes, theme, global styles
|
|
22
|
+
├── pages/ # Dashboard, Knowledge, Tasks, Skills, Docs, Files, Prompts, Search, Graph, Tools, Help
|
|
23
|
+
├── widgets/ # Layout (sidebar + project selector + theme toggle)
|
|
24
|
+
├── features/ # note-crud, task-crud, skill-crud
|
|
25
|
+
├── entities/ # project, note, task, skill, file, doc, code, graph
|
|
26
|
+
├── shared/ # API client, WebSocket hook, theme context
|
|
27
|
+
└── content/ # Help articles + prompt templates (markdown, bundled via ?raw)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
npm run dev # Vite dev server on :5173, proxies /api → http://localhost:3000
|
|
35
|
+
npm run build # Production build → dist/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The backend (`serve` command) must be running on port 3000 for the API proxy to work.
|
|
39
|
+
|
|
40
|
+
## Pages
|
|
41
|
+
|
|
42
|
+
| Route | Description |
|
|
43
|
+
|-------|-------------|
|
|
44
|
+
| `/:projectId/dashboard` | Stats cards + recent notes/tasks |
|
|
45
|
+
| `/:projectId/knowledge` | Notes CRUD, search, relations, cross-graph links |
|
|
46
|
+
| `/:projectId/tasks` | Kanban board: configurable columns, drag-drop with highlights, inline creation, filters, due date/estimate badges, quick actions |
|
|
47
|
+
| `/:projectId/docs` | Browse indexed documentation, TOC |
|
|
48
|
+
| `/:projectId/files` | File browser, directory navigation, metadata |
|
|
49
|
+
| `/:projectId/skills` | Skill/recipe management with triggers, steps, usage tracking |
|
|
50
|
+
| `/:projectId/prompts` | AI prompt generator: scenarios, role/style/graph selection, live preview, export as skill |
|
|
51
|
+
| `/:projectId/search` | Unified search across all 6 graphs |
|
|
52
|
+
| `/:projectId/graph` | Interactive force-directed graph (Cytoscape.js) |
|
|
53
|
+
| `/:projectId/tools` | MCP tools explorer with live execution |
|
|
54
|
+
| `/:projectId/help` | Built-in searchable documentation |
|