sqlcipher-mcp-server 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -282
- package/index.js +14 -226
- package/package.json +29 -27
- package/server-http.js +14 -0
- package/src/config/constants.js +40 -0
- package/src/config/environment.js +63 -0
- package/src/handlers/http-handlers.js +89 -0
- package/src/handlers/mcp-handlers.js +69 -0
- package/src/server/http-server.js +54 -0
- package/src/server/mcp-server.js +76 -0
- package/src/services/database-service.js +55 -0
- package/src/utils/errors.js +69 -0
- package/src/utils/formatters.js +64 -0
- package/src/utils/validators.js +58 -0
package/index.js
CHANGED
|
@@ -1,226 +1,14 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from '
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Get database password from environment variable
|
|
17
|
-
const DB_PASSWORD = process.env.SQLCIPHER_PASSWORD;
|
|
18
|
-
|
|
19
|
-
// Create MCP server instance
|
|
20
|
-
const server = new Server(
|
|
21
|
-
{
|
|
22
|
-
name: 'sqlcipher-mcp-server',
|
|
23
|
-
version: '1.0.0',
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
capabilities: {
|
|
27
|
-
tools: {},
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* List available tools
|
|
34
|
-
*/
|
|
35
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
36
|
-
return {
|
|
37
|
-
tools: [
|
|
38
|
-
{
|
|
39
|
-
name: 'execute_query',
|
|
40
|
-
description: 'Execute a SELECT query on a SQLCipher-encrypted SQLite database. Only read-only queries are allowed.',
|
|
41
|
-
inputSchema: {
|
|
42
|
-
type: 'object',
|
|
43
|
-
properties: {
|
|
44
|
-
database_path: {
|
|
45
|
-
type: 'string',
|
|
46
|
-
description: 'Path to the SQLCipher database file',
|
|
47
|
-
},
|
|
48
|
-
query: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
description: 'SQL SELECT query to execute (read-only)',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
required: ['database_path', 'query'],
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Handle tool execution requests
|
|
62
|
-
*/
|
|
63
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
64
|
-
const { name, arguments: args } = request.params;
|
|
65
|
-
|
|
66
|
-
if (name === 'execute_query') {
|
|
67
|
-
try {
|
|
68
|
-
// Validate required parameters
|
|
69
|
-
if (!args || typeof args !== 'object') {
|
|
70
|
-
throw new Error('Invalid arguments: arguments must be an object');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { database_path, query } = args;
|
|
74
|
-
|
|
75
|
-
// Validate database_path
|
|
76
|
-
if (!database_path || typeof database_path !== 'string') {
|
|
77
|
-
throw new Error('database_path is required and must be a string');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Validate query
|
|
81
|
-
if (!query || typeof query !== 'string') {
|
|
82
|
-
throw new Error('query is required and must be a string');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Connect to database (password is optional - will work with unencrypted databases)
|
|
86
|
-
let db = null;
|
|
87
|
-
try {
|
|
88
|
-
db = await connectDatabase(database_path, DB_PASSWORD);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
return {
|
|
91
|
-
content: [
|
|
92
|
-
{
|
|
93
|
-
type: 'text',
|
|
94
|
-
text: `Failed to connect to database: ${error.message}`,
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
isError: true,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Execute query
|
|
102
|
-
try {
|
|
103
|
-
const result = executeQuery(db, query);
|
|
104
|
-
|
|
105
|
-
// Format results for response
|
|
106
|
-
const responseText = formatQueryResults(result);
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
content: [
|
|
110
|
-
{
|
|
111
|
-
type: 'text',
|
|
112
|
-
text: responseText,
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
};
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: 'text',
|
|
121
|
-
text: `Query execution failed: ${error.message}`,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
isError: true,
|
|
125
|
-
};
|
|
126
|
-
} finally {
|
|
127
|
-
// Always close the database connection
|
|
128
|
-
closeConnection(db);
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return {
|
|
132
|
-
content: [
|
|
133
|
-
{
|
|
134
|
-
type: 'text',
|
|
135
|
-
text: `Error: ${error.message}`,
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
isError: true,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
return {
|
|
143
|
-
content: [
|
|
144
|
-
{
|
|
145
|
-
type: 'text',
|
|
146
|
-
text: `Unknown tool: ${name}`,
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
isError: true,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Format query results as a readable string
|
|
156
|
-
*
|
|
157
|
-
* @param {Object} result - Query result object with columns, rows, and rowCount
|
|
158
|
-
* @returns {string} Formatted result string
|
|
159
|
-
*/
|
|
160
|
-
function formatQueryResults(result) {
|
|
161
|
-
const { columns, rows, rowCount } = result;
|
|
162
|
-
|
|
163
|
-
if (rowCount === 0) {
|
|
164
|
-
return `Query executed successfully. No rows returned.\nColumns: ${columns.join(', ')}`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Build table-like output
|
|
168
|
-
let output = `Query executed successfully. ${rowCount} row(s) returned.\n\n`;
|
|
169
|
-
|
|
170
|
-
// Add column headers
|
|
171
|
-
output += `Columns: ${columns.join(' | ')}\n`;
|
|
172
|
-
output += '-'.repeat(columns.join(' | ').length) + '\n';
|
|
173
|
-
|
|
174
|
-
// Add rows (limit to first 1000 rows for display)
|
|
175
|
-
const displayRows = rows.slice(0, 1000);
|
|
176
|
-
for (const row of displayRows) {
|
|
177
|
-
const values = columns.map(col => {
|
|
178
|
-
const value = row[col];
|
|
179
|
-
// Handle null/undefined
|
|
180
|
-
if (value === null || value === undefined) {
|
|
181
|
-
return 'NULL';
|
|
182
|
-
}
|
|
183
|
-
// Convert to string and truncate long values
|
|
184
|
-
const str = String(value);
|
|
185
|
-
return str.length > 50 ? str.substring(0, 47) + '...' : str;
|
|
186
|
-
});
|
|
187
|
-
output += values.join(' | ') + '\n';
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (rows.length > 1000) {
|
|
191
|
-
output += `\n... (showing first 1000 of ${rowCount} rows)`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Add JSON representation for programmatic access
|
|
195
|
-
output += '\n\nJSON representation:\n';
|
|
196
|
-
output += JSON.stringify(result, null, 2);
|
|
197
|
-
|
|
198
|
-
return output;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Start the MCP server
|
|
203
|
-
*/
|
|
204
|
-
async function main() {
|
|
205
|
-
// Check if password is set (warn but don't fail - might be set later)
|
|
206
|
-
if (!DB_PASSWORD) {
|
|
207
|
-
console.error(
|
|
208
|
-
'Warning: SQLCIPHER_PASSWORD environment variable is not set. ' +
|
|
209
|
-
'Database connections will fail until this is configured.'
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Create stdio transport
|
|
214
|
-
const transport = new StdioServerTransport();
|
|
215
|
-
|
|
216
|
-
// Connect server to transport
|
|
217
|
-
await server.connect(transport);
|
|
218
|
-
|
|
219
|
-
console.error('SQLCipher MCP Server running on stdio');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Start the server
|
|
223
|
-
main().catch((error) => {
|
|
224
|
-
console.error('Fatal error starting server:', error);
|
|
225
|
-
process.exit(1);
|
|
226
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SQLCipher MCP Server - Entry Point
|
|
5
|
+
* Provides read-only access to SQLCipher-encrypted SQLite databases via MCP
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { startMcpServer } from './src/server/mcp-server.js';
|
|
9
|
+
|
|
10
|
+
// Start the server
|
|
11
|
+
startMcpServer().catch((error) => {
|
|
12
|
+
console.error('Fatal error starting server:', error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
package/package.json
CHANGED
|
@@ -1,54 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlcipher-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "MCP Server for querying SQLCipher-encrypted SQLite databases",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"sqlcipher",
|
|
8
|
+
"sqlite",
|
|
9
|
+
"database",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"encrypted-database",
|
|
12
|
+
"cursor-ide"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/sathiraguruge/SQLLiteMCP",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/sathiraguruge/SQLLiteMCP/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/sathiraguruge/SQLLiteMCP.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Sathira Guruge",
|
|
5
24
|
"main": "index.js",
|
|
6
|
-
"type": "module",
|
|
7
25
|
"bin": {
|
|
8
26
|
"sqlcipher-mcp-server": "./index.js"
|
|
9
27
|
},
|
|
28
|
+
"type": "module",
|
|
10
29
|
"files": [
|
|
11
30
|
"index.js",
|
|
31
|
+
"server-http.js",
|
|
32
|
+
"src/**/*",
|
|
12
33
|
"lib/**/*",
|
|
13
34
|
"README.md",
|
|
14
35
|
"LICENSE"
|
|
15
36
|
],
|
|
16
37
|
"scripts": {
|
|
17
38
|
"start": "node index.js",
|
|
18
|
-
"start:http": "node server-http.js",
|
|
19
|
-
"test:http": "node server-http.js",
|
|
20
39
|
"dev": "nodemon index.js",
|
|
40
|
+
"start:http": "node server-http.js",
|
|
21
41
|
"dev:http": "nodemon server-http.js",
|
|
42
|
+
"test:http": "node server-http.js",
|
|
22
43
|
"prepublishOnly": "npm test || exit 0"
|
|
23
44
|
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"mcp",
|
|
26
|
-
"sqlcipher",
|
|
27
|
-
"sqlite",
|
|
28
|
-
"database",
|
|
29
|
-
"model-context-protocol",
|
|
30
|
-
"encrypted-database",
|
|
31
|
-
"cursor-ide"
|
|
32
|
-
],
|
|
33
|
-
"author": "Sathira Guruge",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"repository": {
|
|
36
|
-
"type": "git",
|
|
37
|
-
"url": "https://github.com/sathiraguruge/SQLLiteMCP.git"
|
|
38
|
-
},
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/sathiraguruge/SQLLiteMCP/issues"
|
|
41
|
-
},
|
|
42
|
-
"homepage": "https://github.com/sathiraguruge/SQLLiteMCP",
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=18.0.0"
|
|
45
|
-
},
|
|
46
45
|
"dependencies": {
|
|
47
46
|
"@journeyapps/sqlcipher": "^5.1.1",
|
|
48
|
-
"@modelcontextprotocol/sdk": "^
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
49
48
|
"express": "^4.22.1"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"nodemon": "^3.1.11"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
53
55
|
}
|
|
54
56
|
}
|
package/server-http.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SQLCipher MCP HTTP Server - Entry Point
|
|
5
|
+
* HTTP wrapper for testing the SQLCipher MCP Server with tools like Postman
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { startHttpServer } from './src/server/http-server.js';
|
|
9
|
+
|
|
10
|
+
// Start the server
|
|
11
|
+
startHttpServer().catch((error) => {
|
|
12
|
+
console.error('Fatal error starting HTTP server:', error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Constants
|
|
3
|
+
* Central location for all application constants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SERVER_CONFIG = {
|
|
7
|
+
name: 'sqlcipher-mcp-server',
|
|
8
|
+
version: '1.0.2',
|
|
9
|
+
description: 'MCP Server for querying SQLCipher-encrypted SQLite databases',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const QUERY_CONFIG = {
|
|
13
|
+
maxDisplayRows: 1000,
|
|
14
|
+
maxValueLength: 50,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const HTTP_CONFIG = {
|
|
18
|
+
defaultPort: 3000,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const TOOL_DEFINITIONS = {
|
|
22
|
+
execute_query: {
|
|
23
|
+
name: 'execute_query',
|
|
24
|
+
description: 'Execute a SELECT query on a SQLCipher-encrypted SQLite database. Only read-only queries are allowed. Database path can be provided as parameter or via SQLCIPHER_DATABASE_PATH environment variable.',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
database_path: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Path to the SQLCipher database file (optional if SQLCIPHER_DATABASE_PATH is set)',
|
|
31
|
+
},
|
|
32
|
+
query: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'SQL SELECT query to execute (read-only)',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['query'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration
|
|
3
|
+
* Manages environment variables and application configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get database password from environment variable
|
|
8
|
+
* @returns {string|undefined} Database password or undefined if not set
|
|
9
|
+
*/
|
|
10
|
+
export function getDatabasePassword() {
|
|
11
|
+
return process.env.SQLCIPHER_PASSWORD;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get default database path from environment variable
|
|
16
|
+
* @returns {string|undefined} Database path or undefined if not set
|
|
17
|
+
*/
|
|
18
|
+
export function getDatabasePath() {
|
|
19
|
+
return process.env.SQLCIPHER_DATABASE_PATH;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get HTTP server port from environment variable
|
|
24
|
+
* @param {number} defaultPort - Default port to use if not set
|
|
25
|
+
* @returns {number} Port number
|
|
26
|
+
*/
|
|
27
|
+
export function getPort(defaultPort = 3000) {
|
|
28
|
+
return process.env.PORT || defaultPort;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if password is configured
|
|
33
|
+
* @returns {boolean} True if password is set
|
|
34
|
+
*/
|
|
35
|
+
export function isPasswordConfigured() {
|
|
36
|
+
return !!getDatabasePassword();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if database path is configured
|
|
41
|
+
* @returns {boolean} True if database path is set
|
|
42
|
+
*/
|
|
43
|
+
export function isDatabasePathConfigured() {
|
|
44
|
+
return !!getDatabasePath();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get configuration warnings for missing environment variables
|
|
49
|
+
* @returns {string[]} Array of warning messages
|
|
50
|
+
*/
|
|
51
|
+
export function getConfigurationWarnings() {
|
|
52
|
+
const warnings = [];
|
|
53
|
+
|
|
54
|
+
if (!isPasswordConfigured()) {
|
|
55
|
+
warnings.push('SQLCIPHER_PASSWORD environment variable is not set. Database connections may fail if password is required.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isDatabasePathConfigured()) {
|
|
59
|
+
warnings.push('SQLCIPHER_DATABASE_PATH environment variable is not set. Database path must be provided in each query.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return warnings;
|
|
63
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Route Handlers
|
|
3
|
+
* Handlers for HTTP API endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SERVER_CONFIG } from '../config/constants.js';
|
|
7
|
+
import { getDatabasePassword, isPasswordConfigured } from '../config/environment.js';
|
|
8
|
+
import { validateDatabasePath, validateQuery } from '../utils/validators.js';
|
|
9
|
+
import { executeQueryOnDatabase } from '../services/database-service.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle health check endpoint
|
|
13
|
+
* @param {Object} req - Express request object
|
|
14
|
+
* @param {Object} res - Express response object
|
|
15
|
+
*/
|
|
16
|
+
export function handleHealthCheck(req, res) {
|
|
17
|
+
res.json({
|
|
18
|
+
status: 'ok',
|
|
19
|
+
message: 'SQLCipher MCP HTTP Server is running',
|
|
20
|
+
passwordConfigured: isPasswordConfigured(),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle server info endpoint
|
|
26
|
+
* @param {Object} req - Express request object
|
|
27
|
+
* @param {Object} res - Express response object
|
|
28
|
+
*/
|
|
29
|
+
export function handleInfo(req, res) {
|
|
30
|
+
res.json({
|
|
31
|
+
name: SERVER_CONFIG.name,
|
|
32
|
+
version: SERVER_CONFIG.version,
|
|
33
|
+
description: 'HTTP wrapper for SQLCipher MCP Server',
|
|
34
|
+
endpoints: {
|
|
35
|
+
health: 'GET /health',
|
|
36
|
+
query: 'POST /api/query',
|
|
37
|
+
info: 'GET /api/info',
|
|
38
|
+
},
|
|
39
|
+
passwordConfigured: isPasswordConfigured(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handle query execution endpoint
|
|
45
|
+
* @param {Object} req - Express request object
|
|
46
|
+
* @param {Object} res - Express response object
|
|
47
|
+
*/
|
|
48
|
+
export async function handleQuery(req, res) {
|
|
49
|
+
try {
|
|
50
|
+
const { database_path, query } = req.body;
|
|
51
|
+
|
|
52
|
+
// Validate database_path
|
|
53
|
+
try {
|
|
54
|
+
validateDatabasePath(database_path);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return res.status(400).json({ error: error.message });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate query
|
|
60
|
+
try {
|
|
61
|
+
validateQuery(query);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return res.status(400).json({ error: error.message });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get database password
|
|
67
|
+
const password = getDatabasePassword();
|
|
68
|
+
|
|
69
|
+
// Execute query
|
|
70
|
+
try {
|
|
71
|
+
const result = await executeQueryOnDatabase(database_path, password, query);
|
|
72
|
+
|
|
73
|
+
// Return successful response
|
|
74
|
+
res.json({
|
|
75
|
+
success: true,
|
|
76
|
+
data: result,
|
|
77
|
+
message: `Query executed successfully. ${result.rowCount} row(s) returned.`,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
res.status(400).json({
|
|
81
|
+
error: `Query execution failed: ${error.message}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
res.status(500).json({
|
|
86
|
+
error: `Internal server error: ${error.message}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Handlers
|
|
3
|
+
* Handlers for MCP tool requests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TOOL_DEFINITIONS } from '../config/constants.js';
|
|
7
|
+
import { getDatabasePassword } from '../config/environment.js';
|
|
8
|
+
import { validateArguments, validateQuery, resolveDatabasePath } from '../utils/validators.js';
|
|
9
|
+
import { formatQueryResults } from '../utils/formatters.js';
|
|
10
|
+
import { createMcpErrorResponse, createMcpSuccessResponse } from '../utils/errors.js';
|
|
11
|
+
import { executeQueryOnDatabase } from '../services/database-service.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle list tools request
|
|
15
|
+
* @returns {Object} List of available tools
|
|
16
|
+
*/
|
|
17
|
+
export function handleListTools() {
|
|
18
|
+
return {
|
|
19
|
+
tools: [TOOL_DEFINITIONS.execute_query],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handle execute_query tool request
|
|
25
|
+
* @param {Object} args - Tool arguments
|
|
26
|
+
* @param {string} [args.database_path] - Database path (optional if env var set)
|
|
27
|
+
* @param {string} args.query - SQL query to execute
|
|
28
|
+
* @returns {Promise<Object>} MCP response object
|
|
29
|
+
*/
|
|
30
|
+
export async function handleExecuteQuery(args) {
|
|
31
|
+
try {
|
|
32
|
+
// Validate arguments
|
|
33
|
+
validateArguments(args);
|
|
34
|
+
|
|
35
|
+
const { database_path, query } = args;
|
|
36
|
+
|
|
37
|
+
// Validate query
|
|
38
|
+
validateQuery(query);
|
|
39
|
+
|
|
40
|
+
// Resolve database path
|
|
41
|
+
const dbPath = resolveDatabasePath(database_path);
|
|
42
|
+
|
|
43
|
+
// Get database password
|
|
44
|
+
const password = getDatabasePassword();
|
|
45
|
+
|
|
46
|
+
// Execute query
|
|
47
|
+
try {
|
|
48
|
+
const result = await executeQueryOnDatabase(dbPath, password, query);
|
|
49
|
+
|
|
50
|
+
// Format results for response
|
|
51
|
+
const responseText = formatQueryResults(result);
|
|
52
|
+
|
|
53
|
+
return createMcpSuccessResponse(responseText);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return createMcpErrorResponse(`Query execution failed: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return createMcpErrorResponse(`Error: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle unknown tool request
|
|
64
|
+
* @param {string} toolName - Name of the unknown tool
|
|
65
|
+
* @returns {Object} Error response
|
|
66
|
+
*/
|
|
67
|
+
export function handleUnknownTool(toolName) {
|
|
68
|
+
return createMcpErrorResponse(`Unknown tool: ${toolName}`);
|
|
69
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Server
|
|
3
|
+
* Express HTTP server setup and initialization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import { HTTP_CONFIG } from '../config/constants.js';
|
|
8
|
+
import { getPort, isPasswordConfigured } from '../config/environment.js';
|
|
9
|
+
import { handleHealthCheck, handleInfo, handleQuery } from '../handlers/http-handlers.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create and configure Express app
|
|
13
|
+
* @returns {express.Application} Configured Express app
|
|
14
|
+
*/
|
|
15
|
+
export function createHttpApp() {
|
|
16
|
+
const app = express();
|
|
17
|
+
|
|
18
|
+
// Middleware to parse JSON request bodies
|
|
19
|
+
app.use(express.json());
|
|
20
|
+
|
|
21
|
+
// Register routes
|
|
22
|
+
app.get('/health', handleHealthCheck);
|
|
23
|
+
app.get('/api/info', handleInfo);
|
|
24
|
+
app.post('/api/query', handleQuery);
|
|
25
|
+
|
|
26
|
+
return app;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the HTTP server
|
|
31
|
+
* @param {number} [port] - Port to listen on (defaults to env or 3000)
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
export async function startHttpServer(port) {
|
|
35
|
+
const app = createHttpApp();
|
|
36
|
+
const serverPort = port || getPort(HTTP_CONFIG.defaultPort);
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
app.listen(serverPort, () => {
|
|
40
|
+
console.log(`SQLCipher MCP HTTP Server running on http://localhost:${serverPort}`);
|
|
41
|
+
console.log(`Health check: http://localhost:${serverPort}/health`);
|
|
42
|
+
console.log(`API info: http://localhost:${serverPort}/api/info`);
|
|
43
|
+
|
|
44
|
+
if (!isPasswordConfigured()) {
|
|
45
|
+
console.warn('\n⚠️ Warning: SQLCIPHER_PASSWORD environment variable is not set.');
|
|
46
|
+
console.warn(' Database queries will fail until this is configured.\n');
|
|
47
|
+
} else {
|
|
48
|
+
console.log('✅ Database password is configured.\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|