rag-memory-epf-mcp 3.2.1 → 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
|
@@ -128,6 +128,9 @@ storeDocument(id, content, metadata)
|
|
|
128
128
|
|
|
129
129
|
## Changelog
|
|
130
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
|
+
|
|
131
134
|
### v3.2.0
|
|
132
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.
|
|
133
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.
|
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
|
|
@@ -2079,6 +2087,246 @@ class RAGKnowledgeGraphManager {
|
|
|
2079
2087
|
chunks: chunkCount.count
|
|
2080
2088
|
};
|
|
2081
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
|
+
}
|
|
2082
2330
|
// === MIGRATION TOOLS ===
|
|
2083
2331
|
async getMigrationStatus() {
|
|
2084
2332
|
if (!this.db)
|
|
@@ -2218,6 +2466,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2218
2466
|
return { content: [{ type: "text", text: JSON.stringify(await ragKgManager.runMigrations(), null, 2) }] };
|
|
2219
2467
|
case "rollbackMigration":
|
|
2220
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) }] };
|
|
2221
2476
|
default:
|
|
2222
2477
|
throw new Error(`Unknown tool: ${name}`);
|
|
2223
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"
|