@falkordb/mcpserver 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ import { ErrorHandler, errorHandler } from './ErrorHandler';
2
+ import { AppError, CommonErrors } from './AppError';
3
+ // Mock the logger service
4
+ jest.mock('../services/logger.service.js', () => ({
5
+ logger: {
6
+ error: jest.fn().mockResolvedValue(undefined),
7
+ info: jest.fn().mockResolvedValue(undefined),
8
+ errorSync: jest.fn(),
9
+ }
10
+ }));
11
+ // Get mock logger from the mocked module
12
+ let mockLogger;
13
+ describe('ErrorHandler', () => {
14
+ let handler;
15
+ beforeEach(async () => {
16
+ handler = new ErrorHandler();
17
+ jest.clearAllMocks();
18
+ // Import the mock logger
19
+ const loggerModule = await import('../services/logger.service.js');
20
+ mockLogger = loggerModule.logger;
21
+ });
22
+ describe('handleError', () => {
23
+ it('should handle operational errors gracefully', async () => {
24
+ // Arrange
25
+ const operationalError = new AppError(CommonErrors.OPERATION_FAILED, 'Test operational error', true);
26
+ // Act
27
+ await handler.handleError(operationalError);
28
+ // Assert
29
+ expect(mockLogger.error).toHaveBeenCalledWith('Unhandled error occurred', operationalError, expect.objectContaining({
30
+ timestamp: expect.any(String),
31
+ errorType: 'AppError'
32
+ }));
33
+ expect(mockLogger.info).toHaveBeenCalledWith('Operational error handled gracefully', {
34
+ errorName: operationalError.name,
35
+ errorMessage: operationalError.message
36
+ });
37
+ });
38
+ it('should handle programmer errors with critical logging', async () => {
39
+ // Arrange
40
+ const programmerError = new AppError(CommonErrors.INVALID_INPUT, 'Test programmer error', false // not operational
41
+ );
42
+ // Act
43
+ await handler.handleError(programmerError);
44
+ // Assert
45
+ expect(mockLogger.error).toHaveBeenCalledWith('Unhandled error occurred', programmerError, expect.objectContaining({
46
+ timestamp: expect.any(String),
47
+ errorType: 'AppError'
48
+ }));
49
+ expect(mockLogger.error).toHaveBeenCalledWith('Programmer error detected - may require process restart', programmerError, {
50
+ recommendation: 'Review code for bugs',
51
+ severity: 'critical'
52
+ });
53
+ });
54
+ it('should handle generic errors as programmer errors', async () => {
55
+ // Arrange
56
+ const genericError = new Error('Generic error message');
57
+ // Act
58
+ await handler.handleError(genericError);
59
+ // Assert
60
+ expect(mockLogger.error).toHaveBeenCalledWith('Unhandled error occurred', genericError, expect.objectContaining({
61
+ timestamp: expect.any(String),
62
+ errorType: 'Error'
63
+ }));
64
+ expect(mockLogger.error).toHaveBeenCalledWith('Programmer error detected - may require process restart', genericError, {
65
+ recommendation: 'Review code for bugs',
66
+ severity: 'critical'
67
+ });
68
+ });
69
+ });
70
+ describe('isTrustedError', () => {
71
+ it('should return true for operational AppError', () => {
72
+ // Arrange
73
+ const operationalError = new AppError(CommonErrors.OPERATION_FAILED, 'Test operational error', true);
74
+ // Act
75
+ const result = handler.isTrustedError(operationalError);
76
+ // Assert
77
+ expect(result).toBe(true);
78
+ });
79
+ it('should return false for non-operational AppError', () => {
80
+ // Arrange
81
+ const nonOperationalError = new AppError(CommonErrors.INVALID_INPUT, 'Test programmer error', false);
82
+ // Act
83
+ const result = handler.isTrustedError(nonOperationalError);
84
+ // Assert
85
+ expect(result).toBe(false);
86
+ });
87
+ it('should return false for generic Error', () => {
88
+ // Arrange
89
+ const genericError = new Error('Generic error');
90
+ // Act
91
+ const result = handler.isTrustedError(genericError);
92
+ // Assert
93
+ expect(result).toBe(false);
94
+ });
95
+ it('should return false for non-Error objects', () => {
96
+ // Arrange
97
+ const nonError = { message: 'Not an error' };
98
+ // Act
99
+ const result = handler.isTrustedError(nonError);
100
+ // Assert
101
+ expect(result).toBe(false);
102
+ });
103
+ });
104
+ describe('crashIfUntrustedError', () => {
105
+ let processExitSpy;
106
+ beforeEach(() => {
107
+ processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
108
+ throw new Error('process.exit called');
109
+ });
110
+ });
111
+ afterEach(() => {
112
+ processExitSpy.mockRestore();
113
+ });
114
+ it('should not crash for trusted operational errors', () => {
115
+ // Arrange
116
+ const operationalError = new AppError(CommonErrors.OPERATION_FAILED, 'Test operational error', true);
117
+ // Act & Assert
118
+ expect(() => handler.crashIfUntrustedError(operationalError)).not.toThrow();
119
+ expect(processExitSpy).not.toHaveBeenCalled();
120
+ expect(mockLogger.errorSync).not.toHaveBeenCalled();
121
+ });
122
+ it('should crash for untrusted programmer errors', () => {
123
+ // Arrange
124
+ const programmerError = new AppError(CommonErrors.INVALID_INPUT, 'Test programmer error', false);
125
+ // Act & Assert
126
+ expect(() => handler.crashIfUntrustedError(programmerError)).toThrow('process.exit called');
127
+ expect(mockLogger.errorSync).toHaveBeenCalledWith('Crashing process due to untrusted error', programmerError);
128
+ expect(processExitSpy).toHaveBeenCalledWith(1);
129
+ });
130
+ it('should crash for generic errors', () => {
131
+ // Arrange
132
+ const genericError = new Error('Generic error');
133
+ // Act & Assert
134
+ expect(() => handler.crashIfUntrustedError(genericError)).toThrow('process.exit called');
135
+ expect(mockLogger.errorSync).toHaveBeenCalledWith('Crashing process due to untrusted error', genericError);
136
+ expect(processExitSpy).toHaveBeenCalledWith(1);
137
+ });
138
+ });
139
+ describe('singleton instance', () => {
140
+ it('should export a singleton instance', () => {
141
+ // Assert
142
+ expect(errorHandler).toBeInstanceOf(ErrorHandler);
143
+ expect(errorHandler).toBe(errorHandler); // Same instance
144
+ });
145
+ });
146
+ });
package/dist/index.js ADDED
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module';
3
+ import { createServer } from 'http';
4
+ import { randomUUID } from 'crypto';
5
+ import { falkorDBService } from './services/falkordb.service.js';
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
+ import { redisService } from './services/redis.service.js';
10
+ import { errorHandler } from './errors/ErrorHandler.js';
11
+ import { logger } from './services/logger.service.js';
12
+ import { config } from './config/index.js';
13
+ import registerAllTools from './mcp/tools.js';
14
+ import registerAllResources from './mcp/resources.js';
15
+ import registerAllPrompts from './mcp/prompts.js';
16
+ const require = createRequire(import.meta.url);
17
+ const { version } = require('../package.json');
18
+ // Setup global error handlers following Node.js best practices
19
+ process.on('uncaughtException', (error) => {
20
+ logger.errorSync('Uncaught exception occurred', error);
21
+ void errorHandler.handleError(error).catch((handlerError) => {
22
+ logger.errorSync('Error while handling uncaught exception', handlerError instanceof Error ? handlerError : new Error(String(handlerError)));
23
+ });
24
+ errorHandler.crashIfUntrustedError(error);
25
+ });
26
+ process.on('unhandledRejection', (reason) => {
27
+ // Re-throw as error to be caught by uncaughtException handler
28
+ const error = reason instanceof Error ? reason : new Error(String(reason));
29
+ throw error;
30
+ });
31
+ // Graceful shutdown handler
32
+ let httpServer = null;
33
+ const gracefulShutdown = async (signal) => {
34
+ await logger.info(`Received ${signal}, shutting down gracefully`);
35
+ try {
36
+ if (httpServer) {
37
+ await new Promise((resolve, reject) => {
38
+ httpServer.close((err) => err ? reject(err) : resolve());
39
+ });
40
+ }
41
+ await falkorDBService.close();
42
+ await redisService.close();
43
+ await logger.info('All services closed successfully');
44
+ process.exit(0);
45
+ }
46
+ catch (error) {
47
+ await logger.error('Error during graceful shutdown', error instanceof Error ? error : new Error(String(error)));
48
+ process.exit(1);
49
+ }
50
+ };
51
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
52
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
53
+ // Create an MCP server
54
+ const server = new McpServer({
55
+ name: "falkordb",
56
+ version: version
57
+ }, {
58
+ capabilities: {
59
+ tools: {
60
+ listChanged: true,
61
+ },
62
+ resources: {
63
+ listChanged: true,
64
+ },
65
+ prompts: {
66
+ listChanged: true,
67
+ },
68
+ logging: {},
69
+ }
70
+ });
71
+ // Note: Current MCP TypeScript SDK doesn't directly support elicitation in tool handlers
72
+ // This is a conceptual implementation - you'd need to implement session access
73
+ // Configure logger to send notifications to MCP clients
74
+ logger.setMcpServer(server);
75
+ // Register all tools and resources
76
+ registerAllTools(server);
77
+ registerAllResources(server);
78
+ registerAllPrompts(server);
79
+ // Initialize services before starting server
80
+ async function initializeServices() {
81
+ await logger.info('Initializing FalkorDB MCP server...');
82
+ try {
83
+ await falkorDBService.initialize();
84
+ await redisService.initialize();
85
+ await logger.info('All services initialized successfully');
86
+ }
87
+ catch (error) {
88
+ await logger.error('Failed to initialize services', error instanceof Error ? error : new Error(String(error)));
89
+ throw error;
90
+ }
91
+ }
92
+ // Main server startup
93
+ async function startServer() {
94
+ try {
95
+ await initializeServices();
96
+ if (config.mcp.transport === 'http') {
97
+ await startHTTPServer();
98
+ }
99
+ else {
100
+ await startStdioServer();
101
+ }
102
+ }
103
+ catch (error) {
104
+ await logger.error('Failed to start MCP server', error instanceof Error ? error : new Error(String(error)));
105
+ await gracefulShutdown('STARTUP_ERROR');
106
+ }
107
+ }
108
+ async function startStdioServer() {
109
+ const transport = new StdioServerTransport();
110
+ await server.connect(transport);
111
+ await logger.info('MCP server started successfully (stdio transport)');
112
+ }
113
+ async function startHTTPServer() {
114
+ const port = config.server.port;
115
+ const apiKey = config.mcp.apiKey;
116
+ // Map session IDs to their transports for session management
117
+ const sessions = new Map();
118
+ httpServer = createServer(async (req, res) => {
119
+ // API key authentication for HTTP transport
120
+ if (apiKey) {
121
+ const authHeader = req.headers['authorization'];
122
+ if (!authHeader || authHeader !== `Bearer ${apiKey}`) {
123
+ res.writeHead(401, { 'Content-Type': 'application/json' });
124
+ res.end(JSON.stringify({ error: 'Unauthorized' }));
125
+ return;
126
+ }
127
+ }
128
+ const sessionId = req.headers['mcp-session-id'];
129
+ if (req.method === 'POST') {
130
+ // Read the request body
131
+ const body = await readRequestBody(req);
132
+ const parsedBody = JSON.parse(body);
133
+ if (sessionId && sessions.has(sessionId)) {
134
+ // Existing session — route to its transport
135
+ const transport = sessions.get(sessionId);
136
+ await transport.handleRequest(req, res, parsedBody);
137
+ }
138
+ else if (!sessionId && isInitializeRequest(parsedBody)) {
139
+ // New session initialization
140
+ const transport = new StreamableHTTPServerTransport({
141
+ sessionIdGenerator: () => randomUUID(),
142
+ });
143
+ transport.onclose = () => {
144
+ const sid = transport.sessionId;
145
+ if (sid)
146
+ sessions.delete(sid);
147
+ };
148
+ // Connect a fresh McpServer for this session
149
+ const sessionServer = createSessionServer();
150
+ await sessionServer.connect(transport);
151
+ await transport.handleRequest(req, res, parsedBody);
152
+ // Store session after handling (sessionId is set after init)
153
+ if (transport.sessionId) {
154
+ sessions.set(transport.sessionId, transport);
155
+ }
156
+ }
157
+ else {
158
+ res.writeHead(400, { 'Content-Type': 'application/json' });
159
+ res.end(JSON.stringify({ error: 'Bad Request: No valid session or initialization' }));
160
+ }
161
+ }
162
+ else if (req.method === 'GET') {
163
+ // SSE stream for server-initiated messages
164
+ if (sessionId && sessions.has(sessionId)) {
165
+ const transport = sessions.get(sessionId);
166
+ await transport.handleRequest(req, res);
167
+ }
168
+ else {
169
+ res.writeHead(400, { 'Content-Type': 'application/json' });
170
+ res.end(JSON.stringify({ error: 'Bad Request: Invalid or missing session ID' }));
171
+ }
172
+ }
173
+ else if (req.method === 'DELETE') {
174
+ // Session termination
175
+ if (sessionId && sessions.has(sessionId)) {
176
+ const transport = sessions.get(sessionId);
177
+ await transport.handleRequest(req, res);
178
+ sessions.delete(sessionId);
179
+ }
180
+ else {
181
+ res.writeHead(400, { 'Content-Type': 'application/json' });
182
+ res.end(JSON.stringify({ error: 'Bad Request: Invalid or missing session ID' }));
183
+ }
184
+ }
185
+ else {
186
+ res.writeHead(405, { 'Content-Type': 'application/json' });
187
+ res.end(JSON.stringify({ error: 'Method Not Allowed' }));
188
+ }
189
+ });
190
+ httpServer.listen(port, () => {
191
+ // Fire-and-forget: non-critical startup log
192
+ logger.info(`MCP server started successfully (HTTP transport on port ${port})`);
193
+ });
194
+ }
195
+ function createSessionServer() {
196
+ const sessionServer = new McpServer({
197
+ name: "falkordb",
198
+ version: version,
199
+ }, {
200
+ capabilities: {
201
+ tools: { listChanged: true },
202
+ resources: { listChanged: true },
203
+ prompts: { listChanged: true },
204
+ logging: {},
205
+ },
206
+ });
207
+ logger.setMcpServer(sessionServer);
208
+ registerAllTools(sessionServer);
209
+ registerAllResources(sessionServer);
210
+ registerAllPrompts(sessionServer);
211
+ return sessionServer;
212
+ }
213
+ function readRequestBody(req) {
214
+ return new Promise((resolve, reject) => {
215
+ let body = '';
216
+ req.on('data', (chunk) => { body += chunk.toString(); });
217
+ req.on('end', () => resolve(body));
218
+ req.on('error', reject);
219
+ });
220
+ }
221
+ function isInitializeRequest(body) {
222
+ if (typeof body === 'object' && body !== null && 'method' in body) {
223
+ return body.method === 'initialize';
224
+ }
225
+ if (Array.isArray(body)) {
226
+ return body.some(msg => typeof msg === 'object' && msg !== null && 'method' in msg && msg.method === 'initialize');
227
+ }
228
+ return false;
229
+ }
230
+ // Start the server
231
+ startServer().catch(async (error) => {
232
+ await logger.error('Fatal startup error', error instanceof Error ? error : new Error(String(error)));
233
+ process.exit(1);
234
+ });
@@ -0,0 +1,229 @@
1
+ import { z } from "zod";
2
+ // Define schemas as simple objects first to avoid TS2589 deep recursion
3
+ const userSetupArgsSchema = {
4
+ name: z.string().describe("The name of the user"),
5
+ };
6
+ const memoryQueryArgsSchema = {
7
+ query: z.string().describe("The query or topic to search for in memory"),
8
+ context: z.string().optional().describe("Additional context to help scope the search"),
9
+ relationship_depth: z.coerce.number().min(1).max(3).describe("How many relationship hops to traverse (1-3)")
10
+ };
11
+ function registerUserSetupPrompt(server) {
12
+ // Register user_setup prompt
13
+ server.registerPrompt("user_setup", {
14
+ title: "User Setup",
15
+ description: "Setup the user graph node and connect it to the rest of the relevant nodes",
16
+ argsSchema: userSetupArgsSchema, // Cast to any to prevent TS2589 (deep recursion) during type inference
17
+ }, async (args) => {
18
+ const { name } = z.object(userSetupArgsSchema).parse(args);
19
+ const userMessage = `# User Setup Task
20
+
21
+ You are working with a FalkorDB graph database to manage user information and relationships.
22
+
23
+ **User Information:**
24
+ - Name: ${name}
25
+
26
+ **Your task is to:**
27
+
28
+ 1. **Check if user exists**: Search for a node with the name "${name}" in the memory graph using a parameterized query: \`MATCH (u) WHERE u.name = $name RETURN u\` with params \`{name: "${name}"}\`
29
+ 2. **Create user node if needed**: If no node exists with this name, create a new user node using a parameterized query with the following properties:
30
+ - name: "${name}"
31
+ - type: "User"
32
+ - created_at: current timestamp
33
+ 3. **Establish relationships**: Ensure the user node is properly connected to relevant nodes in the graph, such as:
34
+ - Recent conversations or interactions
35
+ - Associated topics or interests
36
+ - Related entities or contexts
37
+
38
+ **Guidelines:**
39
+ - Use appropriate Cypher queries to search, create, and connect nodes
40
+ - Maintain data consistency and avoid duplicate user nodes
41
+ - Consider the existing graph structure when establishing new relationships
42
+ - Log any operations performed for debugging purposes
43
+
44
+ Please proceed with setting up the user "${name}" in the memory graph.`;
45
+ return {
46
+ messages: [
47
+ {
48
+ role: "user",
49
+ content: {
50
+ type: "text",
51
+ text: userMessage
52
+ }
53
+ }
54
+ ]
55
+ };
56
+ });
57
+ }
58
+ function registerMemoryQueryPrompt(server) {
59
+ server.registerPrompt("memory_query", {
60
+ title: "Memory Query",
61
+ description: "Query the memory graph to retrieve and analyze stored information",
62
+ argsSchema: memoryQueryArgsSchema, // Cast to any to prevent TS2589 (deep recursion) during type inference
63
+ }, async (args) => {
64
+ const { query, context, relationship_depth } = z.object(memoryQueryArgsSchema).parse(args);
65
+ const memoryMessage = `# Memory Query Task
66
+
67
+ You are working with a FalkorDB graph database to retrieve and analyze stored memory information.
68
+
69
+ **Query Information:**
70
+ - Search Query: ${query}
71
+ ${context ? `- Additional Context: ${context}` : ''}
72
+ - Relationship Depth: ${relationship_depth} hops
73
+
74
+ **Your task is to:**
75
+
76
+ 1. **Search for relevant nodes**: Use parameterized Cypher queries to find nodes that match or relate to the search query
77
+ - Always use query parameters (e.g., \`$query\`) instead of string interpolation to prevent injection
78
+ - Look for nodes with matching names, properties, or content
79
+ - Consider partial matches and semantic relationships
80
+
81
+ 2. **Traverse relationships**: Explore connected nodes up to ${relationship_depth} relationship hops to gather context:
82
+ - Follow relationships like RELATES_TO, MENTIONED_IN, CONNECTED_TO
83
+ - Include timestamps and relationship properties
84
+
85
+ 3. **Analyze and synthesize**: Process the retrieved information to:
86
+ - Identify key patterns and connections
87
+ - Extract relevant facts and relationships
88
+ - Organize information chronologically or by relevance
89
+
90
+ 4. **Provide structured results**: Format your findings including:
91
+ - Direct matches and their properties
92
+ - Related nodes and connection paths
93
+ - Temporal patterns if applicable
94
+ - Confidence levels for relationships
95
+
96
+ **Query Guidelines:**
97
+ - Use MATCH patterns to find relevant nodes
98
+ - Utilize WHERE clauses for filtering
99
+ - Consider using OPTIONAL MATCH for related information
100
+ - Include LIMIT clauses to manage result size
101
+ - Order results by relevance or timestamp
102
+
103
+ **Example Cypher patterns (always use parameterized queries):**
104
+ \`\`\`cypher
105
+ // Use $query parameter - never interpolate user input directly
106
+ MATCH (n) WHERE n.name CONTAINS $query OR n.content CONTAINS $query
107
+ MATCH (n)-[r*1..${relationship_depth}]-(related)
108
+ RETURN n, r, related ORDER BY n.timestamp DESC
109
+ // Pass params: {query: "${query}"}
110
+ \`\`\`
111
+
112
+ Please proceed with querying the memory graph for information about "${query}".`;
113
+ return {
114
+ messages: [
115
+ {
116
+ role: "user",
117
+ content: {
118
+ type: "text",
119
+ text: memoryMessage
120
+ }
121
+ }
122
+ ]
123
+ };
124
+ });
125
+ }
126
+ function registerGraphReorganizationPrompt(server) {
127
+ server.registerPrompt("graph_reorganization", {
128
+ title: "Graph Reorganization",
129
+ description: "Analyze and reorganize the graph structure for optimal performance and usability"
130
+ }, async () => {
131
+ const reorganizationMessage = `# Graph Reorganization Task
132
+
133
+ You are working with a FalkorDB graph database to optimize its structure for better performance and usability.
134
+
135
+ **Your task is to:**
136
+
137
+ 1. **Analyze Current Structure**: Examine the graph to identify structural issues:
138
+ - Find nodes with excessive connections (hubs with >50 relationships)
139
+ - Identify disconnected components or islands
140
+ - Locate duplicate or near-duplicate nodes
141
+ - Find outdated or stale connections
142
+ - Analyze relationship distribution and patterns
143
+
144
+ 2. **Performance Optimization**: Based on the analysis, implement improvements:
145
+ - **Merge duplicate nodes**: Combine nodes with similar properties or content
146
+ - **Create strategic hubs**: Add intermediate nodes to reduce direct connections
147
+ - **Remove dead connections**: Delete relationships to non-existent or irrelevant nodes
148
+ - **Add semantic clustering**: Group related nodes under topic or category nodes
149
+ - **Optimize relationship types**: Ensure relationship labels are descriptive and indexed
150
+
151
+ 3. **Usability Enhancement**: Improve graph navigation and querying:
152
+ - **Add metadata nodes**: Create nodes that summarize clusters or topics
153
+ - **Establish clear hierarchies**: Organize nodes in logical parent-child relationships
154
+ - **Create shortcut relationships**: Add direct paths for frequently accessed connections
155
+ - **Implement time-based organization**: Group nodes by temporal relevance
156
+
157
+ 4. **Validation and Testing**: After reorganization:
158
+ - Verify all critical paths remain intact
159
+ - Test common query patterns for performance
160
+ - Ensure no data loss occurred during restructuring
161
+ - Document changes made for future reference
162
+
163
+ **Optimization Strategies by Goal:**
164
+
165
+ **Performance Focus:**
166
+ - Minimize query traversal depth
167
+ - Balance node degree distribution
168
+ - Create efficient index structures
169
+ - Reduce redundant relationships
170
+
171
+ **Usability Focus:**
172
+ - Improve semantic organization
173
+ - Add descriptive metadata
174
+ - Create intuitive navigation paths
175
+ - Enhance discoverability
176
+
177
+ **Balanced Approach:**
178
+ - Apply moderate optimizations from both areas
179
+ - Prioritize changes with highest impact/effort ratio
180
+
181
+ **Analysis Queries to Run:**
182
+ \`\`\`cypher
183
+ // Find high-degree nodes
184
+ MATCH (n)
185
+ WITH n, size((n)--()) as degree
186
+ WHERE degree > 20
187
+ RETURN n, degree ORDER BY degree DESC LIMIT 10
188
+
189
+ // Find disconnected components
190
+ MATCH (n)
191
+ WHERE NOT (n)--()
192
+ RETURN count(n) as isolated_nodes
193
+
194
+ // Identify potential duplicates
195
+ MATCH (n1), (n2)
196
+ WHERE id(n1) < id(n2)
197
+ AND n1.name = n2.name
198
+ AND n1.type = n2.type
199
+ RETURN n1, n2
200
+
201
+ // Find stale nodes (older than 30 days with no recent connections)
202
+ MATCH (n)
203
+ WHERE n.created_at < datetime() - duration({days: 30})
204
+ AND NOT (n)-[:UPDATED_AT|ACCESSED_AT]-()
205
+ RETURN n LIMIT 20
206
+ \`\`\`
207
+
208
+ **Reorganization Operations:**
209
+ Implement the most impactful optimizations. Prioritize operations that provide the best balance of performance improvement and structural clarity.
210
+
211
+ Begin with analyzing the current graph structure and proceed with the reorganization plan.`;
212
+ return {
213
+ messages: [
214
+ {
215
+ role: "user",
216
+ content: {
217
+ type: "text",
218
+ text: reorganizationMessage
219
+ }
220
+ }
221
+ ]
222
+ };
223
+ });
224
+ }
225
+ export default function registerAllPrompts(server) {
226
+ registerUserSetupPrompt(server);
227
+ registerMemoryQueryPrompt(server);
228
+ registerGraphReorganizationPrompt(server);
229
+ }
@@ -0,0 +1,26 @@
1
+ import { falkorDBService } from '../services/falkordb.service.js';
2
+ import { logger } from '../services/logger.service.js';
3
+ export default function registerAllResources(server) {
4
+ // Register graph_list resource
5
+ server.registerResource("graph_list", "graph://listing", {
6
+ title: "List Graphs",
7
+ description: "List all graphs in the database",
8
+ mimeType: "text/plain",
9
+ }, async (uri) => {
10
+ try {
11
+ const graphNames = await falkorDBService.listGraphs();
12
+ const markdownList = graphNames.map(name => `- ${name}`).join('\n');
13
+ await logger.debug('Graph list resource accessed', { count: graphNames.length });
14
+ return {
15
+ contents: [{
16
+ uri: uri.href,
17
+ text: markdownList,
18
+ }]
19
+ };
20
+ }
21
+ catch (error) {
22
+ await logger.error('Failed to fetch graph list resource', error instanceof Error ? error : new Error(String(error)));
23
+ throw error;
24
+ }
25
+ });
26
+ }