@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.
- package/.env.example +26 -0
- package/LICENSE +21 -0
- package/README.md +412 -0
- package/dist/config/index.js +27 -0
- package/dist/config/index.test.js +23 -0
- package/dist/errors/AppError.js +27 -0
- package/dist/errors/ErrorHandler.js +46 -0
- package/dist/errors/ErrorHandler.test.js +146 -0
- package/dist/index.js +234 -0
- package/dist/mcp/prompts.js +229 -0
- package/dist/mcp/resources.js +26 -0
- package/dist/mcp/tools.js +258 -0
- package/dist/models/mcp-client-config.js +34 -0
- package/dist/models/mcp-client-config.test.js +173 -0
- package/dist/models/mcp.types.js +4 -0
- package/dist/services/falkordb.service.js +175 -0
- package/dist/services/falkordb.service.test.js +489 -0
- package/dist/services/logger.service.js +151 -0
- package/dist/services/logger.service.test.js +115 -0
- package/dist/services/redis.service.js +179 -0
- package/dist/services/redis.service.test.js +399 -0
- package/dist/utils/connection-parser.js +71 -0
- package/dist/utils/connection-parser.test.js +232 -0
- package/package.json +99 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { parseFalkorDBConnectionString } from './connection-parser';
|
|
2
|
+
describe('Connection Parser Utility', () => {
|
|
3
|
+
// Mock console.error to avoid noise in test output
|
|
4
|
+
let consoleErrorSpy;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
7
|
+
});
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
consoleErrorSpy.mockRestore();
|
|
10
|
+
});
|
|
11
|
+
describe('parseFalkorDBConnectionString', () => {
|
|
12
|
+
it('should return default options for empty string', () => {
|
|
13
|
+
// Act
|
|
14
|
+
const result = parseFalkorDBConnectionString('');
|
|
15
|
+
// Assert
|
|
16
|
+
expect(result).toEqual({
|
|
17
|
+
host: 'localhost',
|
|
18
|
+
port: 6379
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('should return default options for undefined input', () => {
|
|
22
|
+
// Act
|
|
23
|
+
const result = parseFalkorDBConnectionString(undefined);
|
|
24
|
+
// Assert
|
|
25
|
+
expect(result).toEqual({
|
|
26
|
+
host: 'localhost',
|
|
27
|
+
port: 6379
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it('should return default options for null input', () => {
|
|
31
|
+
// Act
|
|
32
|
+
const result = parseFalkorDBConnectionString(null);
|
|
33
|
+
// Assert
|
|
34
|
+
expect(result).toEqual({
|
|
35
|
+
host: 'localhost',
|
|
36
|
+
port: 6379
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('should parse simple host', () => {
|
|
40
|
+
// Act
|
|
41
|
+
const result = parseFalkorDBConnectionString('redis.example.com');
|
|
42
|
+
// Assert
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
host: 'redis.example.com',
|
|
45
|
+
port: 6379
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it('should parse host with port', () => {
|
|
49
|
+
// Act
|
|
50
|
+
const result = parseFalkorDBConnectionString('redis.example.com:1234');
|
|
51
|
+
// Assert
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
host: 'redis.example.com',
|
|
54
|
+
port: 1234
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('should parse host with invalid port (use default)', () => {
|
|
58
|
+
// Act
|
|
59
|
+
const result = parseFalkorDBConnectionString('redis.example.com:invalid');
|
|
60
|
+
// Assert
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
host: 'redis.example.com',
|
|
63
|
+
port: 6379
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it('should parse connection string with protocol prefix', () => {
|
|
67
|
+
// Act
|
|
68
|
+
const result = parseFalkorDBConnectionString('falkordb://redis.example.com:1234');
|
|
69
|
+
// Assert
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
host: 'redis.example.com',
|
|
72
|
+
port: 1234
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it('should parse connection string with username and password', () => {
|
|
76
|
+
// Act
|
|
77
|
+
const result = parseFalkorDBConnectionString('falkordb://user:pass@redis.example.com:1234');
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
host: 'redis.example.com',
|
|
81
|
+
port: 1234,
|
|
82
|
+
username: 'user',
|
|
83
|
+
password: 'pass'
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it('should parse connection string with only password', () => {
|
|
87
|
+
// Act
|
|
88
|
+
const result = parseFalkorDBConnectionString('falkordb://mypassword@redis.example.com:1234');
|
|
89
|
+
// Assert
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
host: 'redis.example.com',
|
|
92
|
+
port: 1234,
|
|
93
|
+
password: 'mypassword'
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
it('should parse connection string with empty username', () => {
|
|
97
|
+
// Act
|
|
98
|
+
const result = parseFalkorDBConnectionString('falkordb://:password@redis.example.com:1234');
|
|
99
|
+
// Assert
|
|
100
|
+
expect(result).toEqual({
|
|
101
|
+
host: 'redis.example.com',
|
|
102
|
+
port: 1234,
|
|
103
|
+
password: 'password'
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
it('should parse connection string with empty password', () => {
|
|
107
|
+
// Act
|
|
108
|
+
const result = parseFalkorDBConnectionString('falkordb://username:@redis.example.com:1234');
|
|
109
|
+
// Assert
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
host: 'redis.example.com',
|
|
112
|
+
port: 1234,
|
|
113
|
+
username: 'username'
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('should handle missing host part with default', () => {
|
|
117
|
+
// Act
|
|
118
|
+
const result = parseFalkorDBConnectionString('falkordb://:1234');
|
|
119
|
+
// Assert
|
|
120
|
+
expect(result).toEqual({
|
|
121
|
+
host: 'localhost',
|
|
122
|
+
port: 1234
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it('should handle missing port part with default', () => {
|
|
126
|
+
// Act
|
|
127
|
+
const result = parseFalkorDBConnectionString('falkordb://redis.example.com:');
|
|
128
|
+
// Assert
|
|
129
|
+
expect(result).toEqual({
|
|
130
|
+
host: 'redis.example.com',
|
|
131
|
+
port: 6379
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('should handle auth with missing host part', () => {
|
|
135
|
+
// Act
|
|
136
|
+
const result = parseFalkorDBConnectionString('falkordb://user:pass@:1234');
|
|
137
|
+
// Assert
|
|
138
|
+
expect(result).toEqual({
|
|
139
|
+
host: 'localhost',
|
|
140
|
+
port: 1234,
|
|
141
|
+
username: 'user',
|
|
142
|
+
password: 'pass'
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
it('should handle complex real-world connection string', () => {
|
|
146
|
+
// Act
|
|
147
|
+
const result = parseFalkorDBConnectionString('falkordb://admin:secret123@prod-redis.company.com:16379');
|
|
148
|
+
// Assert
|
|
149
|
+
expect(result).toEqual({
|
|
150
|
+
host: 'prod-redis.company.com',
|
|
151
|
+
port: 16379,
|
|
152
|
+
username: 'admin',
|
|
153
|
+
password: 'secret123'
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
it('should handle localhost with auth', () => {
|
|
157
|
+
// Act
|
|
158
|
+
const result = parseFalkorDBConnectionString('falkordb://user:pass@localhost:6379');
|
|
159
|
+
// Assert
|
|
160
|
+
expect(result).toEqual({
|
|
161
|
+
host: 'localhost',
|
|
162
|
+
port: 6379,
|
|
163
|
+
username: 'user',
|
|
164
|
+
password: 'pass'
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it('should handle IPv4 address', () => {
|
|
168
|
+
// Act
|
|
169
|
+
const result = parseFalkorDBConnectionString('falkordb://192.168.1.100:6379');
|
|
170
|
+
// Assert
|
|
171
|
+
expect(result).toEqual({
|
|
172
|
+
host: '192.168.1.100',
|
|
173
|
+
port: 6379
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
it('should handle IPv4 address with auth', () => {
|
|
177
|
+
// Act
|
|
178
|
+
const result = parseFalkorDBConnectionString('falkordb://user:pass@192.168.1.100:6379');
|
|
179
|
+
// Assert
|
|
180
|
+
expect(result).toEqual({
|
|
181
|
+
host: '192.168.1.100',
|
|
182
|
+
port: 6379,
|
|
183
|
+
username: 'user',
|
|
184
|
+
password: 'pass'
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
it('should return defaults on parsing error and log error', () => {
|
|
188
|
+
// Arrange - create a scenario that might cause parsing issues
|
|
189
|
+
// We'll simulate this by mocking parseInt to throw
|
|
190
|
+
const originalParseInt = global.parseInt;
|
|
191
|
+
global.parseInt = jest.fn(() => {
|
|
192
|
+
throw new Error('Parsing error');
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
// Act
|
|
196
|
+
const result = parseFalkorDBConnectionString('falkordb://host:port');
|
|
197
|
+
// Assert
|
|
198
|
+
expect(result).toEqual({
|
|
199
|
+
host: 'localhost',
|
|
200
|
+
port: 6379
|
|
201
|
+
});
|
|
202
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error parsing connection string:', expect.any(Error));
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
// Cleanup
|
|
206
|
+
global.parseInt = originalParseInt;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
it('should handle edge case with multiple @ symbols', () => {
|
|
210
|
+
// Act
|
|
211
|
+
const result = parseFalkorDBConnectionString('falkordb://user@domain:pass@host:1234');
|
|
212
|
+
// Assert - after fixing parser to use lastIndexOf('@')
|
|
213
|
+
expect(result).toEqual({
|
|
214
|
+
host: 'host',
|
|
215
|
+
port: 1234,
|
|
216
|
+
username: 'user@domain',
|
|
217
|
+
password: 'pass'
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
it('should handle edge case with multiple : in auth', () => {
|
|
221
|
+
// Act
|
|
222
|
+
const result = parseFalkorDBConnectionString('falkordb://user:pass:extra@host:1234');
|
|
223
|
+
// Assert - after fixing parser to properly rejoin password with multiple ':'
|
|
224
|
+
expect(result).toEqual({
|
|
225
|
+
host: 'host',
|
|
226
|
+
port: 1234,
|
|
227
|
+
username: 'user',
|
|
228
|
+
password: 'pass:extra'
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@falkordb/mcpserver",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Model Context Protocol server for FalkorDB graph databases - enables AI assistants to query and manage graph data using natural language",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0",
|
|
9
|
+
"npm": ">=8.0.0"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/FalkorDB/FalkorDB-MCPServer.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/FalkorDB/FalkorDB-MCPServer/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/FalkorDB/FalkorDB-MCPServer#readme",
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Katie Mulliken",
|
|
21
|
+
"email": "katie@mulliken.net",
|
|
22
|
+
"url": "https://github.com/SecKatie"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"falkordb",
|
|
29
|
+
"graph-database",
|
|
30
|
+
"knowledge-graph",
|
|
31
|
+
"ai",
|
|
32
|
+
"claude",
|
|
33
|
+
"opencypher",
|
|
34
|
+
"cypher",
|
|
35
|
+
"redis",
|
|
36
|
+
"graph",
|
|
37
|
+
"database",
|
|
38
|
+
"typescript",
|
|
39
|
+
"server",
|
|
40
|
+
"chatbot",
|
|
41
|
+
"llm",
|
|
42
|
+
"anthropic",
|
|
43
|
+
"neo4j",
|
|
44
|
+
"graph-query"
|
|
45
|
+
],
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/**/*",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
".env.example"
|
|
51
|
+
],
|
|
52
|
+
"bin": {
|
|
53
|
+
"falkordb-mcp": "./dist/index.js"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc",
|
|
57
|
+
"start": "node dist/index.js",
|
|
58
|
+
"dev": "nodemon --watch 'src/**/*.ts' --exec 'npm run build && npm start'",
|
|
59
|
+
"dev:ts": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts",
|
|
60
|
+
"lint": "eslint . --ext .ts",
|
|
61
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
62
|
+
"test": "jest",
|
|
63
|
+
"test:watch": "jest --watch",
|
|
64
|
+
"test:coverage": "jest --coverage",
|
|
65
|
+
"test:ci": "jest --coverage --ci --watchAll=false",
|
|
66
|
+
"inspect": "npm run build && npx -y @modelcontextprotocol/inspector node $PWD/dist/index.js",
|
|
67
|
+
"clean": "rm -rf dist coverage",
|
|
68
|
+
"prepublish": "npm run test:ci && npm run lint && npm run build",
|
|
69
|
+
"prepublishOnly": "npm run clean && npm run test:ci && npm run lint && npm run build"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/jest": "^30.0.0",
|
|
73
|
+
"@types/node": "^20.14.0",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
75
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
76
|
+
"eslint": "^9.31.0",
|
|
77
|
+
"jest": "^30.0.5",
|
|
78
|
+
"nodemon": "^3.1.10",
|
|
79
|
+
"ts-jest": "^29.4.0",
|
|
80
|
+
"ts-node": "^10.9.2",
|
|
81
|
+
"typescript": "^5.8.3"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
85
|
+
"dotenv": "^17.2.1",
|
|
86
|
+
"falkordb": "^6.3.0",
|
|
87
|
+
"platformdirs": "^4.3.8-rc3",
|
|
88
|
+
"redis": "^4.0.0",
|
|
89
|
+
"zod": "^3.23.0"
|
|
90
|
+
},
|
|
91
|
+
"publishConfig": {
|
|
92
|
+
"access": "public",
|
|
93
|
+
"registry": "https://registry.npmjs.org/"
|
|
94
|
+
},
|
|
95
|
+
"funding": {
|
|
96
|
+
"type": "github",
|
|
97
|
+
"url": "https://github.com/sponsors/SecKatie"
|
|
98
|
+
}
|
|
99
|
+
}
|