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/index.js CHANGED
@@ -1,226 +1,14 @@
1
- #!/usr/bin/env node
2
-
3
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- } from '@modelcontextprotocol/sdk/types.js';
9
- import { connectDatabase, executeQuery, closeConnection } from './lib/database.js';
10
-
11
- /**
12
- * SQLCipher MCP Server
13
- * Provides read-only access to SQLCipher-encrypted SQLite databases
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.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": "^0.5.0",
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
+ }