rag-memory-epf-mcp 3.2.0 → 3.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/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A **project-local RAG memory** MCP server — knowledge graph + multilingual vec
|
|
|
12
12
|
- **Project-local isolation** — each project gets its own `.memory/rag-memory.db`. Multiple projects run simultaneously without interference.
|
|
13
13
|
- **3-signal hybrid search** — vector similarity (bge-m3, 1024-dim) + FTS5 BM25 keyword matching + knowledge graph re-ranking, combined via Reciprocal Rank Fusion
|
|
14
14
|
- **100+ languages** — Korean, Chinese, Japanese, Arabic, and more. Cross-lingual search works out of the box.
|
|
15
|
+
- **Graph-aware scoring** — per-entity geometric decay (0.5^i) with hard cap prevents any single document from dominating results
|
|
15
16
|
- **27 MCP tools** — entity/relation CRUD, document pipeline, multi-hop graph traversal, export/import, temporal queries
|
|
16
17
|
- **SQLite optimized** — WAL mode, 32MB cache, 256MB mmap, FTS5 triggers, 7 indexes
|
|
17
18
|
- **MCP SDK 1.27.1** — Tool Annotations (readOnly/destructive/idempotent), latest protocol 2025-11-25
|
|
@@ -55,7 +56,7 @@ Place this `.mcp.json` in each project folder with its own `DB_FILE_PATH`. Each
|
|
|
55
56
|
| `embedChunks` | Generate 1024-dim embeddings + auto-link entities | idempotent |
|
|
56
57
|
| `embedAllEntities` | Batch embed all entities (32 parallel) | idempotent |
|
|
57
58
|
| `extractTerms` | Extract potential entity terms | — |
|
|
58
|
-
| `linkEntitiesToDocument` |
|
|
59
|
+
| `linkEntitiesToDocument` | Link entities to chunks where they actually appear (text-matched) | idempotent |
|
|
59
60
|
| `deleteDocuments` | Remove documents and associated data | destructive |
|
|
60
61
|
| `listDocuments` | View all stored documents | readOnly |
|
|
61
62
|
|
|
@@ -127,6 +128,13 @@ storeDocument(id, content, metadata)
|
|
|
127
128
|
|
|
128
129
|
## Changelog
|
|
129
130
|
|
|
131
|
+
### v3.2.1
|
|
132
|
+
- **Fix: `autoLinkEntities` silent failure** — was JOINing a non-existent `observations` table (observations are stored as JSON array column in `entities`). Changed to direct column select + `JSON.parse()`.
|
|
133
|
+
|
|
134
|
+
### v3.2.0
|
|
135
|
+
- **Chunk-level entity linking in `linkEntitiesToDocument`** — entities are now linked only to chunks where they actually appear (using `buildEntityMatcher` word-boundary/CJK matching), instead of blanket-linking to all chunks. Fixes search result domination by heavily-linked documents.
|
|
136
|
+
- **Graph boost decay + hard cap** — per-entity scores are sorted descending and decayed geometrically (0.5^i): 1st entity 100%, 2nd 50%, 3rd 25%, etc. Hard cap at 0.4 prevents graph signal from overwhelming vector similarity.
|
|
137
|
+
|
|
130
138
|
### v3.0.0
|
|
131
139
|
- **Back to self-contained embeddings** — reverted from Ollama dependency (v2.x) to built-in `@huggingface/transformers` with bge-m3 (1024-dim). No external services required.
|
|
132
140
|
- **Cross-lingual search** — auto-detects non-English queries and performs dual-language search
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,14 @@ import fsSync from 'fs';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { pipeline, env } from '@huggingface/transformers';
|
|
12
|
+
// Import graphology for graph analytics
|
|
13
|
+
import Graph from 'graphology';
|
|
14
|
+
import louvain from 'graphology-communities-louvain';
|
|
15
|
+
import degree from 'graphology-metrics/centrality/degree.js';
|
|
16
|
+
import betweennessCentrality from 'graphology-metrics/centrality/betweenness.js';
|
|
17
|
+
import closenessCentrality from 'graphology-metrics/centrality/closeness.js';
|
|
18
|
+
import pagerank from 'graphology-metrics/centrality/pagerank.js';
|
|
19
|
+
import modularity from 'graphology-metrics/graph/modularity.js';
|
|
12
20
|
// Import our new structured tool system
|
|
13
21
|
import { getAllMCPTools, validateToolArgs, getSystemInfo } from './src/tools/tool-registry.js';
|
|
14
22
|
// Import migration system
|
|
@@ -1264,11 +1272,7 @@ class RAGKnowledgeGraphManager {
|
|
|
1264
1272
|
if (chunks.length === 0)
|
|
1265
1273
|
return 0;
|
|
1266
1274
|
// Get all entities with observations for richer matching
|
|
1267
|
-
const entities = this.db.prepare(`SELECT
|
|
1268
|
-
GROUP_CONCAT(o.content, ' ||| ') as observations
|
|
1269
|
-
FROM entities e
|
|
1270
|
-
LEFT JOIN observations o ON o.entityId = e.id
|
|
1271
|
-
GROUP BY e.id`).all();
|
|
1275
|
+
const entities = this.db.prepare(`SELECT id, name, entityType, observations FROM entities`).all();
|
|
1272
1276
|
// Minimum name length: 2 for CJK (e.g. "할랄"), 4 for Latin (avoid "API", "Bug")
|
|
1273
1277
|
const MIN_LEN_CJK = 2;
|
|
1274
1278
|
const MIN_LEN_LATIN = 4;
|
|
@@ -1284,7 +1288,13 @@ class RAGKnowledgeGraphManager {
|
|
|
1284
1288
|
// Also collect observation-derived aliases (short keywords from observations)
|
|
1285
1289
|
const aliases = [];
|
|
1286
1290
|
if (entity.observations) {
|
|
1287
|
-
|
|
1291
|
+
let obs;
|
|
1292
|
+
try {
|
|
1293
|
+
obs = JSON.parse(entity.observations);
|
|
1294
|
+
}
|
|
1295
|
+
catch {
|
|
1296
|
+
obs = [];
|
|
1297
|
+
}
|
|
1288
1298
|
for (const ob of obs) {
|
|
1289
1299
|
// Extract file paths or identifiers mentioned in observations (e.g. "gemini_converter.py")
|
|
1290
1300
|
const pathMatch = ob.match(/[\w\-]+\.\w{1,4}\b/g);
|
|
@@ -2077,6 +2087,246 @@ class RAGKnowledgeGraphManager {
|
|
|
2077
2087
|
chunks: chunkCount.count
|
|
2078
2088
|
};
|
|
2079
2089
|
}
|
|
2090
|
+
// === GRAPH ANALYTICS TOOLS (graphology) ===
|
|
2091
|
+
_buildGraphologyGraph() {
|
|
2092
|
+
if (!this.db)
|
|
2093
|
+
throw new Error('Database not initialized');
|
|
2094
|
+
const graph = new Graph({ type: 'undirected', allowSelfLoops: false });
|
|
2095
|
+
// Load entities as nodes
|
|
2096
|
+
const entities = this.db.prepare('SELECT id, name, entityType FROM entities').all();
|
|
2097
|
+
for (const entity of entities) {
|
|
2098
|
+
graph.addNode(entity.id, { name: entity.name, entityType: entity.entityType });
|
|
2099
|
+
}
|
|
2100
|
+
// Load relationships as edges
|
|
2101
|
+
const relations = this.db.prepare('SELECT id, source_entity, target_entity, relationType FROM relationships').all();
|
|
2102
|
+
for (const rel of relations) {
|
|
2103
|
+
if (graph.hasNode(rel.source_entity) && graph.hasNode(rel.target_entity)) {
|
|
2104
|
+
try {
|
|
2105
|
+
graph.addEdge(rel.source_entity, rel.target_entity, {
|
|
2106
|
+
relationType: rel.relationType,
|
|
2107
|
+
id: rel.id,
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
catch (e) {
|
|
2111
|
+
// Skip duplicate edges (graphology undirected merges A→B and B→A)
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return { graph, entities };
|
|
2116
|
+
}
|
|
2117
|
+
async getGraphMetrics(entityNames, metrics, limit = 10) {
|
|
2118
|
+
if (!this.db)
|
|
2119
|
+
throw new Error('Database not initialized');
|
|
2120
|
+
const { graph } = this._buildGraphologyGraph();
|
|
2121
|
+
if (graph.order === 0) {
|
|
2122
|
+
return { metrics: {}, message: 'Knowledge graph is empty' };
|
|
2123
|
+
}
|
|
2124
|
+
const allMetrics = metrics || ['degree', 'betweenness', 'closeness', 'pagerank'];
|
|
2125
|
+
const result = {};
|
|
2126
|
+
if (allMetrics.includes('degree')) {
|
|
2127
|
+
result.degree = degree.degreeCentrality(graph);
|
|
2128
|
+
}
|
|
2129
|
+
if (allMetrics.includes('betweenness')) {
|
|
2130
|
+
result.betweenness = betweennessCentrality(graph);
|
|
2131
|
+
}
|
|
2132
|
+
if (allMetrics.includes('closeness')) {
|
|
2133
|
+
result.closeness = closenessCentrality(graph);
|
|
2134
|
+
}
|
|
2135
|
+
if (allMetrics.includes('pagerank')) {
|
|
2136
|
+
result.pagerank = pagerank(graph);
|
|
2137
|
+
}
|
|
2138
|
+
// Build id→name map
|
|
2139
|
+
const idToName = new Map();
|
|
2140
|
+
graph.forEachNode((id, attrs) => {
|
|
2141
|
+
idToName.set(id, attrs.name);
|
|
2142
|
+
});
|
|
2143
|
+
// If specific entities requested, filter to those
|
|
2144
|
+
if (entityNames && entityNames.length > 0) {
|
|
2145
|
+
const targetIds = new Set();
|
|
2146
|
+
for (const name of entityNames) {
|
|
2147
|
+
const id = `entity_${name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
|
|
2148
|
+
if (graph.hasNode(id))
|
|
2149
|
+
targetIds.add(id);
|
|
2150
|
+
}
|
|
2151
|
+
const filtered = {};
|
|
2152
|
+
for (const [metricName, scores] of Object.entries(result)) {
|
|
2153
|
+
filtered[metricName] = {};
|
|
2154
|
+
for (const id of targetIds) {
|
|
2155
|
+
const name = idToName.get(id) || id;
|
|
2156
|
+
filtered[metricName][name] = scores[id] ?? null;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return { metrics: filtered, entityCount: graph.order, edgeCount: graph.size };
|
|
2160
|
+
}
|
|
2161
|
+
// Otherwise return top-N per metric
|
|
2162
|
+
const topResults = {};
|
|
2163
|
+
for (const [metricName, scores] of Object.entries(result)) {
|
|
2164
|
+
const sorted = Object.entries(scores)
|
|
2165
|
+
.sort((a, b) => b[1] - a[1])
|
|
2166
|
+
.slice(0, limit)
|
|
2167
|
+
.map(([id, score]) => ({
|
|
2168
|
+
name: idToName.get(id) || id,
|
|
2169
|
+
score: Math.round(score * 10000) / 10000,
|
|
2170
|
+
}));
|
|
2171
|
+
topResults[metricName] = sorted;
|
|
2172
|
+
}
|
|
2173
|
+
return { metrics: topResults, entityCount: graph.order, edgeCount: graph.size };
|
|
2174
|
+
}
|
|
2175
|
+
async detectCommunities(resolution = 1.0) {
|
|
2176
|
+
if (!this.db)
|
|
2177
|
+
throw new Error('Database not initialized');
|
|
2178
|
+
const { graph } = this._buildGraphologyGraph();
|
|
2179
|
+
if (graph.order === 0) {
|
|
2180
|
+
return { communities: [], modularity: 0, message: 'Knowledge graph is empty' };
|
|
2181
|
+
}
|
|
2182
|
+
// Run Louvain community detection — assign to node attributes
|
|
2183
|
+
louvain.assign(graph, { resolution });
|
|
2184
|
+
// Assign isolated nodes (Louvain skips degree-0 nodes)
|
|
2185
|
+
let maxCommunityId = -1;
|
|
2186
|
+
graph.forEachNode((id, attrs) => {
|
|
2187
|
+
if (typeof attrs.community === 'number' && attrs.community > maxCommunityId) {
|
|
2188
|
+
maxCommunityId = attrs.community;
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
graph.forEachNode((id, attrs) => {
|
|
2192
|
+
if (attrs.community === undefined) {
|
|
2193
|
+
graph.setNodeAttribute(id, 'community', ++maxCommunityId);
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
// Build id→name map
|
|
2197
|
+
const idToName = new Map();
|
|
2198
|
+
graph.forEachNode((id, attrs) => {
|
|
2199
|
+
idToName.set(id, attrs.name);
|
|
2200
|
+
});
|
|
2201
|
+
// Group entities by community
|
|
2202
|
+
const communityMap = new Map();
|
|
2203
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
2204
|
+
const communityId = attrs.community;
|
|
2205
|
+
if (!communityMap.has(communityId)) {
|
|
2206
|
+
communityMap.set(communityId, []);
|
|
2207
|
+
}
|
|
2208
|
+
communityMap.get(communityId).push({
|
|
2209
|
+
name: idToName.get(nodeId) || nodeId,
|
|
2210
|
+
entityType: attrs.entityType,
|
|
2211
|
+
});
|
|
2212
|
+
});
|
|
2213
|
+
// Sort communities by size (largest first)
|
|
2214
|
+
const sortedCommunities = [...communityMap.entries()]
|
|
2215
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
2216
|
+
.map(([_id, members], index) => ({
|
|
2217
|
+
communityId: index,
|
|
2218
|
+
size: members.length,
|
|
2219
|
+
members: members.sort((a, b) => a.name.localeCompare(b.name)),
|
|
2220
|
+
}));
|
|
2221
|
+
// Calculate modularity (reads 'community' attribute from nodes)
|
|
2222
|
+
const modularityScore = modularity(graph);
|
|
2223
|
+
// Cross-community edges
|
|
2224
|
+
let crossEdges = 0;
|
|
2225
|
+
graph.forEachEdge((_edge, _attrs, source, target) => {
|
|
2226
|
+
const srcCommunity = graph.getNodeAttribute(source, 'community');
|
|
2227
|
+
const tgtCommunity = graph.getNodeAttribute(target, 'community');
|
|
2228
|
+
if (srcCommunity !== tgtCommunity)
|
|
2229
|
+
crossEdges++;
|
|
2230
|
+
});
|
|
2231
|
+
return {
|
|
2232
|
+
communities: sortedCommunities,
|
|
2233
|
+
totalCommunities: sortedCommunities.length,
|
|
2234
|
+
modularity: Math.round(modularityScore * 10000) / 10000,
|
|
2235
|
+
crossCommunityEdges: crossEdges,
|
|
2236
|
+
entityCount: graph.order,
|
|
2237
|
+
edgeCount: graph.size,
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
async analyzeGraphStructure() {
|
|
2241
|
+
if (!this.db)
|
|
2242
|
+
throw new Error('Database not initialized');
|
|
2243
|
+
const { graph } = this._buildGraphologyGraph();
|
|
2244
|
+
if (graph.order === 0) {
|
|
2245
|
+
return { message: 'Knowledge graph is empty', entityCount: 0, edgeCount: 0 };
|
|
2246
|
+
}
|
|
2247
|
+
// Density
|
|
2248
|
+
const density = graph.size > 0
|
|
2249
|
+
? (2 * graph.size) / (graph.order * (graph.order - 1))
|
|
2250
|
+
: 0;
|
|
2251
|
+
// Degree distribution
|
|
2252
|
+
const degrees = [];
|
|
2253
|
+
const isolatedNodes = [];
|
|
2254
|
+
graph.forEachNode((id, attrs) => {
|
|
2255
|
+
const deg = graph.degree(id);
|
|
2256
|
+
degrees.push(deg);
|
|
2257
|
+
if (deg === 0)
|
|
2258
|
+
isolatedNodes.push(attrs.name);
|
|
2259
|
+
});
|
|
2260
|
+
degrees.sort((a, b) => a - b);
|
|
2261
|
+
const avgDegree = degrees.reduce((s, d) => s + d, 0) / degrees.length;
|
|
2262
|
+
const medianDegree = degrees[Math.floor(degrees.length / 2)];
|
|
2263
|
+
// Connected components (BFS)
|
|
2264
|
+
const visited = new Set();
|
|
2265
|
+
const components = [];
|
|
2266
|
+
graph.forEachNode((startId) => {
|
|
2267
|
+
if (visited.has(startId))
|
|
2268
|
+
return;
|
|
2269
|
+
const component = [];
|
|
2270
|
+
const queue = [startId];
|
|
2271
|
+
visited.add(startId);
|
|
2272
|
+
while (queue.length > 0) {
|
|
2273
|
+
const nodeId = queue.shift();
|
|
2274
|
+
component.push(graph.getNodeAttribute(nodeId, 'name'));
|
|
2275
|
+
graph.forEachNeighbor(nodeId, (neighbor) => {
|
|
2276
|
+
if (!visited.has(neighbor)) {
|
|
2277
|
+
visited.add(neighbor);
|
|
2278
|
+
queue.push(neighbor);
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
components.push(component);
|
|
2283
|
+
});
|
|
2284
|
+
components.sort((a, b) => b.length - a.length);
|
|
2285
|
+
// Relationship type distribution
|
|
2286
|
+
const relTypeCount = {};
|
|
2287
|
+
graph.forEachEdge((_edge, attrs) => {
|
|
2288
|
+
const type = attrs.relationType || 'UNKNOWN';
|
|
2289
|
+
relTypeCount[type] = (relTypeCount[type] || 0) + 1;
|
|
2290
|
+
});
|
|
2291
|
+
// Average clustering coefficient
|
|
2292
|
+
let totalClustering = 0;
|
|
2293
|
+
let clusterableNodes = 0;
|
|
2294
|
+
graph.forEachNode((nodeId) => {
|
|
2295
|
+
const neighbors = graph.neighbors(nodeId);
|
|
2296
|
+
if (neighbors.length < 2)
|
|
2297
|
+
return;
|
|
2298
|
+
let triangles = 0;
|
|
2299
|
+
for (let i = 0; i < neighbors.length; i++) {
|
|
2300
|
+
for (let j = i + 1; j < neighbors.length; j++) {
|
|
2301
|
+
if (graph.hasEdge(neighbors[i], neighbors[j]))
|
|
2302
|
+
triangles++;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const possibleTriangles = (neighbors.length * (neighbors.length - 1)) / 2;
|
|
2306
|
+
totalClustering += triangles / possibleTriangles;
|
|
2307
|
+
clusterableNodes++;
|
|
2308
|
+
});
|
|
2309
|
+
const avgClustering = clusterableNodes > 0 ? totalClustering / clusterableNodes : 0;
|
|
2310
|
+
return {
|
|
2311
|
+
entityCount: graph.order,
|
|
2312
|
+
edgeCount: graph.size,
|
|
2313
|
+
density: Math.round(density * 10000) / 10000,
|
|
2314
|
+
connectedComponents: {
|
|
2315
|
+
count: components.length,
|
|
2316
|
+
largest: components[0]?.length || 0,
|
|
2317
|
+
sizes: components.map(c => c.length),
|
|
2318
|
+
isolatedNodes,
|
|
2319
|
+
},
|
|
2320
|
+
degreeDistribution: {
|
|
2321
|
+
min: degrees[0],
|
|
2322
|
+
max: degrees[degrees.length - 1],
|
|
2323
|
+
avg: Math.round(avgDegree * 100) / 100,
|
|
2324
|
+
median: medianDegree,
|
|
2325
|
+
},
|
|
2326
|
+
averageClusteringCoefficient: Math.round(avgClustering * 10000) / 10000,
|
|
2327
|
+
relationshipTypes: relTypeCount,
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2080
2330
|
// === MIGRATION TOOLS ===
|
|
2081
2331
|
async getMigrationStatus() {
|
|
2082
2332
|
if (!this.db)
|
|
@@ -2216,6 +2466,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2216
2466
|
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.runMigrations(), null, 2) }] };
|
|
2217
2467
|
case "rollbackMigration":
|
|
2218
2468
|
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.rollbackMigration(validatedArgs.targetVersion), null, 2) }] };
|
|
2469
|
+
// Graph Analytics tools (graphology)
|
|
2470
|
+
case "getGraphMetrics":
|
|
2471
|
+
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.getGraphMetrics(validatedArgs.entityNames, validatedArgs.metrics, validatedArgs.limit || 10), null, 2) }] };
|
|
2472
|
+
case "detectCommunities":
|
|
2473
|
+
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.detectCommunities(validatedArgs.resolution || 1.0), null, 2) }] };
|
|
2474
|
+
case "analyzeGraphStructure":
|
|
2475
|
+
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.analyzeGraphStructure(), null, 2) }] };
|
|
2219
2476
|
default:
|
|
2220
2477
|
throw new Error(`Unknown tool: ${name}`);
|
|
2221
2478
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ToolDefinition } from './types.js';
|
|
2
|
+
export declare const getGraphMetricsTool: ToolDefinition;
|
|
3
|
+
export declare const detectCommunitiesTool: ToolDefinition;
|
|
4
|
+
export declare const analyzeGraphStructureTool: ToolDefinition;
|
|
5
|
+
export declare const graphAnalyticsTools: {
|
|
6
|
+
getGraphMetrics: ToolDefinition;
|
|
7
|
+
detectCommunities: ToolDefinition;
|
|
8
|
+
analyzeGraphStructure: ToolDefinition;
|
|
9
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// === GET GRAPH METRICS TOOL ===
|
|
3
|
+
const getGraphMetricsCapability = {
|
|
4
|
+
description: 'Calculate centrality metrics for entities using graphology graph analysis',
|
|
5
|
+
parameters: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
entityNames: {
|
|
9
|
+
type: 'array',
|
|
10
|
+
description: 'Entity names to calculate metrics for. If empty, returns top entities by each metric.',
|
|
11
|
+
items: { type: 'string' },
|
|
12
|
+
},
|
|
13
|
+
metrics: {
|
|
14
|
+
type: 'array',
|
|
15
|
+
description: 'Which metrics to compute: degree, betweenness, closeness, pagerank',
|
|
16
|
+
items: { type: 'string' },
|
|
17
|
+
},
|
|
18
|
+
limit: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
description: 'Max entities to return when entityNames is empty (default 10)',
|
|
21
|
+
default: 10,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: [],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const getGraphMetricsDescription = () => `<description>
|
|
28
|
+
Calculate centrality metrics for entities using graphology graph analysis algorithms.
|
|
29
|
+
**Identifies the most important, influential, and bridging entities in your knowledge graph.**
|
|
30
|
+
Supports degree, betweenness, closeness centrality and PageRank.
|
|
31
|
+
</description>
|
|
32
|
+
|
|
33
|
+
<importantNotes>
|
|
34
|
+
- (!important!) **Builds in-memory graph** from SQLite data — lightweight for typical knowledge graphs
|
|
35
|
+
- (!important!) Returns centrality scores normalized to 0-1 range
|
|
36
|
+
- (!important!) When no entityNames provided, returns top entities ranked by each metric
|
|
37
|
+
- (!important!) Useful for identifying knowledge hubs, bridges, and peripheral entities
|
|
38
|
+
</importantNotes>
|
|
39
|
+
|
|
40
|
+
<whenToUseThisTool>
|
|
41
|
+
- To find the most connected/important entities in your knowledge graph
|
|
42
|
+
- To identify bridge entities that connect different knowledge domains
|
|
43
|
+
- To discover peripheral entities that may need more connections
|
|
44
|
+
- For knowledge graph health analysis and optimization
|
|
45
|
+
- Before consolidation — to identify which entities are structurally important
|
|
46
|
+
</whenToUseThisTool>
|
|
47
|
+
|
|
48
|
+
<features>
|
|
49
|
+
- Degree centrality: most connected entities (hub detection)
|
|
50
|
+
- Betweenness centrality: entities bridging different clusters
|
|
51
|
+
- Closeness centrality: entities with shortest average distance to all others
|
|
52
|
+
- PageRank: recursive importance based on connections to important entities
|
|
53
|
+
- Supports both targeted (specific entities) and discovery (top-N) modes
|
|
54
|
+
</features>
|
|
55
|
+
|
|
56
|
+
<bestPractices>
|
|
57
|
+
- Use without entityNames first to discover important entities
|
|
58
|
+
- Combine with detectCommunities for deeper structural analysis
|
|
59
|
+
- High betweenness + low degree = critical bridge (fragile point)
|
|
60
|
+
- High degree + high PageRank = knowledge hub
|
|
61
|
+
- Run periodically to track how graph structure evolves
|
|
62
|
+
</bestPractices>
|
|
63
|
+
|
|
64
|
+
<parameters>
|
|
65
|
+
- entityNames: (optional) Specific entities to analyze. If omitted, returns top entities by each metric.
|
|
66
|
+
- metrics: (optional) Array of metrics to compute: "degree", "betweenness", "closeness", "pagerank". Default: all.
|
|
67
|
+
- limit: (optional) Max results per metric when entityNames is empty. Default: 10.
|
|
68
|
+
</parameters>
|
|
69
|
+
|
|
70
|
+
<examples>
|
|
71
|
+
- Top entities: {} (returns top 10 by all metrics)
|
|
72
|
+
- Specific entities: {"entityNames": ["Entity A", "Entity B"]}
|
|
73
|
+
- PageRank only: {"metrics": ["pagerank"], "limit": 20}
|
|
74
|
+
</examples>`;
|
|
75
|
+
const getGraphMetricsSchema = {
|
|
76
|
+
entityNames: z.array(z.string().describe('Entity name')).optional().describe('Entity names to analyze'),
|
|
77
|
+
metrics: z.array(z.string().describe('Metric name')).optional().describe('Metrics to compute: degree, betweenness, closeness, pagerank'),
|
|
78
|
+
limit: z.number().optional().default(10).describe('Max results per metric'),
|
|
79
|
+
};
|
|
80
|
+
export const getGraphMetricsTool = {
|
|
81
|
+
capability: getGraphMetricsCapability,
|
|
82
|
+
description: getGraphMetricsDescription,
|
|
83
|
+
schema: getGraphMetricsSchema,
|
|
84
|
+
annotations: { readOnlyHint: true },
|
|
85
|
+
};
|
|
86
|
+
// === DETECT COMMUNITIES TOOL ===
|
|
87
|
+
const detectCommunitiesCapability = {
|
|
88
|
+
description: 'Detect communities/clusters in the knowledge graph using Louvain algorithm',
|
|
89
|
+
parameters: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
resolution: {
|
|
93
|
+
type: 'number',
|
|
94
|
+
description: 'Resolution parameter for Louvain (higher = more communities). Default: 1.0',
|
|
95
|
+
default: 1.0,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: [],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const detectCommunitiesDescription = () => `<description>
|
|
102
|
+
Detect communities (clusters) in the knowledge graph using the Louvain modularity algorithm.
|
|
103
|
+
**Reveals natural groupings and topic clusters within your knowledge base.**
|
|
104
|
+
Automatically identifies which entities belong together based on connection patterns.
|
|
105
|
+
</description>
|
|
106
|
+
|
|
107
|
+
<importantNotes>
|
|
108
|
+
- (!important!) Uses Louvain algorithm — fast and effective for modularity optimization
|
|
109
|
+
- (!important!) Returns community assignments for every entity
|
|
110
|
+
- (!important!) Higher resolution parameter produces more, smaller communities
|
|
111
|
+
- (!important!) Modularity score indicates clustering quality (0-1, higher = better defined clusters)
|
|
112
|
+
</importantNotes>
|
|
113
|
+
|
|
114
|
+
<whenToUseThisTool>
|
|
115
|
+
- To discover natural topic clusters in your knowledge graph
|
|
116
|
+
- To understand how your knowledge is organized structurally
|
|
117
|
+
- Before reorganization — to see which entities naturally group together
|
|
118
|
+
- For identifying isolated clusters that may need cross-linking
|
|
119
|
+
- To validate that related entities are actually well-connected
|
|
120
|
+
</whenToUseThisTool>
|
|
121
|
+
|
|
122
|
+
<features>
|
|
123
|
+
- Louvain community detection with configurable resolution
|
|
124
|
+
- Returns named communities with member entities
|
|
125
|
+
- Modularity score for clustering quality assessment
|
|
126
|
+
- Community size distribution for balance analysis
|
|
127
|
+
- Cross-community relationship identification
|
|
128
|
+
</features>
|
|
129
|
+
|
|
130
|
+
<bestPractices>
|
|
131
|
+
- Start with default resolution (1.0), adjust if clusters are too large/small
|
|
132
|
+
- Resolution < 1.0 = fewer, larger communities; > 1.0 = more, smaller communities
|
|
133
|
+
- Single-entity communities may indicate orphaned or poorly connected entities
|
|
134
|
+
- Compare community structure over time to track knowledge evolution
|
|
135
|
+
- Use with getGraphMetrics to find important entities within each community
|
|
136
|
+
</bestPractices>
|
|
137
|
+
|
|
138
|
+
<parameters>
|
|
139
|
+
- resolution: (optional) Louvain resolution. Default: 1.0. Lower = fewer clusters, Higher = more clusters.
|
|
140
|
+
</parameters>
|
|
141
|
+
|
|
142
|
+
<examples>
|
|
143
|
+
- Default clustering: {}
|
|
144
|
+
- Fine-grained: {"resolution": 2.0}
|
|
145
|
+
- Coarse: {"resolution": 0.5}
|
|
146
|
+
</examples>`;
|
|
147
|
+
const detectCommunitiesSchema = {
|
|
148
|
+
resolution: z.number().optional().default(1.0).describe('Louvain resolution parameter'),
|
|
149
|
+
};
|
|
150
|
+
export const detectCommunitiesTool = {
|
|
151
|
+
capability: detectCommunitiesCapability,
|
|
152
|
+
description: detectCommunitiesDescription,
|
|
153
|
+
schema: detectCommunitiesSchema,
|
|
154
|
+
annotations: { readOnlyHint: true },
|
|
155
|
+
};
|
|
156
|
+
// === ANALYZE GRAPH STRUCTURE TOOL ===
|
|
157
|
+
const analyzeGraphStructureCapability = {
|
|
158
|
+
description: 'Analyze overall knowledge graph structure: density, components, diameter, clustering',
|
|
159
|
+
parameters: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {},
|
|
162
|
+
required: [],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const analyzeGraphStructureDescription = () => `<description>
|
|
166
|
+
Analyze the overall structural properties of your knowledge graph.
|
|
167
|
+
**Provides a health dashboard for graph connectivity, density, and structure.**
|
|
168
|
+
Complements getKnowledgeGraphStats (which counts entities/relations) with structural analysis.
|
|
169
|
+
</description>
|
|
170
|
+
|
|
171
|
+
<importantNotes>
|
|
172
|
+
- (!important!) Returns graph-theoretic metrics: density, components, diameter, clustering coefficient
|
|
173
|
+
- (!important!) Identifies disconnected components (isolated subgraphs)
|
|
174
|
+
- (!important!) Detects structural issues: low density, fragmentation, bottlenecks
|
|
175
|
+
- (!important!) Computationally heavier than stats — use periodically, not every session
|
|
176
|
+
</importantNotes>
|
|
177
|
+
|
|
178
|
+
<whenToUseThisTool>
|
|
179
|
+
- For periodic graph health assessment (weekly or after major changes)
|
|
180
|
+
- To detect fragmentation — disconnected components that should be linked
|
|
181
|
+
- To measure graph density — is the knowledge well-interconnected?
|
|
182
|
+
- Before and after consolidation — to measure structural improvement
|
|
183
|
+
- When graph feels "sparse" and you want to quantify it
|
|
184
|
+
</whenToUseThisTool>
|
|
185
|
+
|
|
186
|
+
<features>
|
|
187
|
+
- Graph density (actual edges / possible edges)
|
|
188
|
+
- Connected component analysis with sizes
|
|
189
|
+
- Average clustering coefficient (local connectivity)
|
|
190
|
+
- Degree distribution statistics (min, max, avg, median)
|
|
191
|
+
- Isolated node detection
|
|
192
|
+
- Relationship type distribution
|
|
193
|
+
</features>
|
|
194
|
+
|
|
195
|
+
<bestPractices>
|
|
196
|
+
- Run after major entity additions to check integration
|
|
197
|
+
- Compare density over time — decreasing density may indicate growing but disconnected knowledge
|
|
198
|
+
- Multiple small components suggest domain silos that need cross-linking
|
|
199
|
+
- Use alongside detectCommunities for complete structural understanding
|
|
200
|
+
</bestPractices>
|
|
201
|
+
|
|
202
|
+
<parameters>
|
|
203
|
+
- None required — analyzes the entire graph structure
|
|
204
|
+
</parameters>
|
|
205
|
+
|
|
206
|
+
<examples>
|
|
207
|
+
- Full analysis: {} (no parameters needed)
|
|
208
|
+
</examples>`;
|
|
209
|
+
const analyzeGraphStructureSchema = {};
|
|
210
|
+
export const analyzeGraphStructureTool = {
|
|
211
|
+
capability: analyzeGraphStructureCapability,
|
|
212
|
+
description: analyzeGraphStructureDescription,
|
|
213
|
+
schema: analyzeGraphStructureSchema,
|
|
214
|
+
annotations: { readOnlyHint: true },
|
|
215
|
+
};
|
|
216
|
+
// === EXPORT ===
|
|
217
|
+
export const graphAnalyticsTools = {
|
|
218
|
+
getGraphMetrics: getGraphMetricsTool,
|
|
219
|
+
detectCommunities: detectCommunitiesTool,
|
|
220
|
+
analyzeGraphStructure: analyzeGraphStructureTool,
|
|
221
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ToolDefinition, MCPTool } from './types.js';
|
|
2
2
|
export declare const allTools: {
|
|
3
|
+
getGraphMetrics: ToolDefinition;
|
|
4
|
+
detectCommunities: ToolDefinition;
|
|
5
|
+
analyzeGraphStructure: ToolDefinition;
|
|
3
6
|
readGraph: ToolDefinition;
|
|
4
7
|
searchNodes: ToolDefinition;
|
|
5
8
|
openNodes: ToolDefinition;
|
|
@@ -53,6 +56,7 @@ export declare function getToolsByCategory(): {
|
|
|
53
56
|
knowledgeGraph: string[];
|
|
54
57
|
rag: string[];
|
|
55
58
|
graphQuery: string[];
|
|
59
|
+
graphAnalytics: string[];
|
|
56
60
|
migration: string[];
|
|
57
61
|
all: string[];
|
|
58
62
|
};
|
|
@@ -73,12 +77,14 @@ export declare function getSystemInfo(): {
|
|
|
73
77
|
knowledgeGraph: number;
|
|
74
78
|
rag: number;
|
|
75
79
|
graphQuery: number;
|
|
80
|
+
graphAnalytics: number;
|
|
76
81
|
total: number;
|
|
77
82
|
};
|
|
78
83
|
availableTools: {
|
|
79
84
|
knowledgeGraph: string[];
|
|
80
85
|
rag: string[];
|
|
81
86
|
graphQuery: string[];
|
|
87
|
+
graphAnalytics: string[];
|
|
82
88
|
migration: string[];
|
|
83
89
|
all: string[];
|
|
84
90
|
};
|
|
@@ -2,14 +2,15 @@ import { z } from 'zod';
|
|
|
2
2
|
import { knowledgeGraphTools } from './knowledge-graph-tools.js';
|
|
3
3
|
import { ragTools } from './rag-tools.js';
|
|
4
4
|
import { graphQueryTools } from './graph-query-tools.js';
|
|
5
|
+
import { graphAnalyticsTools } from './graph-analytics-tools.js';
|
|
5
6
|
import { migrationTools } from './migration-tools.js';
|
|
6
7
|
// Central registry of all tools
|
|
7
8
|
export const allTools = {
|
|
8
9
|
...knowledgeGraphTools,
|
|
9
10
|
...ragTools,
|
|
10
11
|
...graphQueryTools,
|
|
12
|
+
...graphAnalyticsTools,
|
|
11
13
|
...migrationTools,
|
|
12
|
-
// Add other tool categories here as needed
|
|
13
14
|
};
|
|
14
15
|
// Global settings for tool descriptions
|
|
15
16
|
export const globalSettings = {
|
|
@@ -153,6 +154,7 @@ export function getToolsByCategory() {
|
|
|
153
154
|
knowledgeGraph: Object.keys(knowledgeGraphTools),
|
|
154
155
|
rag: Object.keys(ragTools),
|
|
155
156
|
graphQuery: Object.keys(graphQueryTools),
|
|
157
|
+
graphAnalytics: Object.keys(graphAnalyticsTools),
|
|
156
158
|
migration: Object.keys(migrationTools),
|
|
157
159
|
all: Object.keys(allTools),
|
|
158
160
|
};
|
|
@@ -178,6 +180,7 @@ export function getSystemInfo() {
|
|
|
178
180
|
knowledgeGraph: categories.knowledgeGraph.length,
|
|
179
181
|
rag: categories.rag.length,
|
|
180
182
|
graphQuery: categories.graphQuery.length,
|
|
183
|
+
graphAnalytics: categories.graphAnalytics.length,
|
|
181
184
|
total: categories.all.length,
|
|
182
185
|
},
|
|
183
186
|
availableTools: categories,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rag-memory-epf-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "MCP server for project-local RAG memory with knowledge graph and multilingual vector search",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "bripin123",
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"@huggingface/transformers": "^3.5.1",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
29
29
|
"better-sqlite3": "^12.8.0",
|
|
30
|
+
"graphology": "^0.26.0",
|
|
31
|
+
"graphology-communities-louvain": "^2.0.2",
|
|
32
|
+
"graphology-metrics": "^2.4.0",
|
|
33
|
+
"graphology-shortest-path": "^2.1.0",
|
|
34
|
+
"graphology-types": "^0.24.8",
|
|
30
35
|
"sqlite-vec": "^0.1.7",
|
|
31
36
|
"tiktoken": "^1.0.17",
|
|
32
37
|
"zod": "^3.25.28"
|