sqlcipher-mcp-server 1.0.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.
Files changed (4) hide show
  1. package/README.md +282 -0
  2. package/index.js +226 -0
  3. package/lib/database.js +216 -0
  4. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # SQLCipher MCP Server
2
+
3
+ An MCP (Model Context Protocol) server that provides read-only access to SQLCipher-encrypted SQLite databases. This server allows you to query encrypted SQLite databases using SQLCipher 3 default encryption settings.
4
+
5
+ ## Overview
6
+
7
+ This MCP server enables you to:
8
+ - Connect to SQLCipher-encrypted SQLite databases
9
+ - Execute SELECT queries (read-only mode)
10
+ - Retrieve query results in a structured format
11
+ - Use SQLCipher 3 default encryption settings
12
+
13
+ ## Prerequisites
14
+
15
+ - Node.js (v18 or higher)
16
+ - npm or yarn package manager
17
+ - Access to a SQLCipher-encrypted SQLite database file
18
+ - Database password (will be provided via environment variable)
19
+
20
+ ## Installation
21
+
22
+ ### Option 1: Install from npm (Recommended)
23
+
24
+ ```bash
25
+ npm install -g sqlcipher-mcp-server
26
+ ```
27
+
28
+ Or install locally in your project:
29
+
30
+ ```bash
31
+ npm install sqlcipher-mcp-server
32
+ ```
33
+
34
+ ### Option 2: Install from source
35
+
36
+ 1. Clone or download this repository
37
+ 2. Install dependencies:
38
+
39
+ ```bash
40
+ npm install
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ ### Environment Variable
46
+
47
+ Set the `SQLCIPHER_PASSWORD` environment variable with your database password:
48
+
49
+ **Windows (PowerShell):**
50
+ ```powershell
51
+ $env:SQLCIPHER_PASSWORD = "your_database_password"
52
+ ```
53
+
54
+ **Windows (Command Prompt):**
55
+ ```cmd
56
+ set SQLCIPHER_PASSWORD=your_database_password
57
+ ```
58
+
59
+ **Linux/macOS:**
60
+ ```bash
61
+ export SQLCIPHER_PASSWORD="your_database_password"
62
+ ```
63
+
64
+ **Permanent setup (recommended):**
65
+ - Add the environment variable to your system settings
66
+ - Or use a `.env` file with a tool like `dotenv` (requires additional setup)
67
+
68
+ ## Usage
69
+
70
+ ### Starting the Server
71
+
72
+ The MCP server communicates via stdio (standard input/output). Start it with:
73
+
74
+ ```bash
75
+ npm start
76
+ ```
77
+
78
+ Or directly:
79
+
80
+ ```bash
81
+ node index.js
82
+ ```
83
+
84
+ ### MCP Client Configuration
85
+
86
+ #### For Cursor IDE
87
+
88
+ 1. **Install the package globally:**
89
+ ```bash
90
+ npm install -g sqlcipher-mcp-server
91
+ ```
92
+
93
+ 2. **Find the global installation path:**
94
+ ```bash
95
+ npm root -g
96
+ ```
97
+
98
+ 3. **Configure Cursor IDE:**
99
+
100
+ Open Cursor's MCP settings file (location varies by OS):
101
+ - **Windows**: `%APPDATA%\Cursor\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`
102
+ - **macOS**: `~/Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
103
+ - **Linux**: `~/.config/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
104
+
105
+ Add this configuration:
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "sqlcipher": {
110
+ "command": "node",
111
+ "args": ["C:\\Users\\YourUsername\\AppData\\Roaming\\npm\\node_modules\\sqlcipher-mcp-server\\index.js"],
112
+ "env": {
113
+ "SQLCIPHER_PASSWORD": "your_database_password"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ **Note:** Replace the path with your actual global npm path from step 2.
121
+
122
+ 4. **Restart Cursor IDE** for changes to take effect.
123
+
124
+ #### Alternative: Using npx (no global installation)
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "sqlcipher": {
130
+ "command": "npx",
131
+ "args": ["-y", "sqlcipher-mcp-server"],
132
+ "env": {
133
+ "SQLCIPHER_PASSWORD": "your_database_password"
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ #### For other MCP clients
141
+
142
+ Configure your MCP client to use this server:
143
+
144
+ ```json
145
+ {
146
+ "mcpServers": {
147
+ "sqlcipher": {
148
+ "command": "node",
149
+ "args": ["path/to/sqlcipher-mcp-server/index.js"],
150
+ "env": {
151
+ "SQLCIPHER_PASSWORD": "your_database_password"
152
+ }
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ For detailed integration instructions, see [PUBLISHING.md](./PUBLISHING.md).
159
+
160
+ ### Available Tools
161
+
162
+ #### `execute_query`
163
+
164
+ Execute a SELECT query on a SQLCipher-encrypted database.
165
+
166
+ **Parameters:**
167
+ - `database_path` (string, required): Full path to the SQLCipher database file
168
+ - `query` (string, required): SQL SELECT query to execute
169
+
170
+ **Returns:**
171
+ - Formatted text output with query results
172
+ - JSON representation of results including:
173
+ - `columns`: Array of column names
174
+ - `rows`: Array of row objects
175
+ - `rowCount`: Number of rows returned
176
+
177
+ **Example Usage:**
178
+
179
+ ```javascript
180
+ // Example 1: Simple SELECT query
181
+ {
182
+ "tool": "execute_query",
183
+ "arguments": {
184
+ "database_path": "C:\\Users\\Username\\AppData\\Local\\AppName\\database.db",
185
+ "query": "SELECT * FROM users LIMIT 10"
186
+ }
187
+ }
188
+
189
+ // Example 2: SELECT with WHERE clause
190
+ {
191
+ "tool": "execute_query",
192
+ "arguments": {
193
+ "database_path": "C:\\Users\\Username\\AppData\\Local\\AppName\\database.db",
194
+ "query": "SELECT id, name, email FROM users WHERE active = 1"
195
+ }
196
+ }
197
+
198
+ // Example 3: SELECT with JOIN
199
+ {
200
+ "tool": "execute_query",
201
+ "arguments": {
202
+ "database_path": "C:\\Users\\Username\\AppData\\Local\\AppName\\database.db",
203
+ "query": "SELECT u.name, o.order_id FROM users u JOIN orders o ON u.id = o.user_id"
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Security Features
209
+
210
+ - **Read-Only Mode**: Only SELECT queries are allowed. INSERT, UPDATE, DELETE, and other modifying operations are blocked.
211
+ - **Password Protection**: Database password is read from environment variable and never exposed in logs or error messages.
212
+ - **Query Validation**: All queries are validated to ensure they are SELECT queries only.
213
+ - **Error Handling**: Sensitive information is not exposed in error messages.
214
+
215
+ ## Error Handling
216
+
217
+ The server handles various error scenarios:
218
+
219
+ - **Database file not found**: Returns clear error message
220
+ - **Invalid password**: Returns generic error (doesn't expose password details)
221
+ - **SQL syntax errors**: Returns specific syntax error messages
222
+ - **Table/column not found**: Returns specific error messages
223
+ - **Non-SELECT queries**: Returns error explaining read-only restriction
224
+
225
+ ## Database Location
226
+
227
+ The database file path should be provided each time you execute a query. Common locations for Windows applications:
228
+
229
+ - `C:\Users\<Username>\AppData\Local\<AppName>\database.db`
230
+ - `C:\Users\<Username>\AppData\Roaming\<AppName>\database.db`
231
+ - `C:\ProgramData\<AppName>\database.db`
232
+
233
+ ## Limitations
234
+
235
+ - **Read-Only**: Only SELECT queries are supported
236
+ - **Single Connection**: Each query opens and closes a new database connection
237
+ - **Result Size**: Results are limited to 1000 rows for display (full results available in JSON)
238
+ - **SQLCipher 3 Only**: Uses SQLCipher 3 default encryption settings
239
+
240
+ ## Troubleshooting
241
+
242
+ ### "Database password not found" Error
243
+ - Ensure `SQLCIPHER_PASSWORD` environment variable is set
244
+ - Restart your MCP client after setting the environment variable
245
+ - Check that the environment variable is available to the Node.js process
246
+
247
+ ### "Database file not found" Error
248
+ - Verify the database path is correct
249
+ - Use absolute paths instead of relative paths
250
+ - Check file permissions
251
+
252
+ ### "Invalid password" Error
253
+ - Verify the password matches the one used to encrypt the database
254
+ - Ensure SQLCipher 3 defaults are used (as this server expects)
255
+ - Check for extra spaces or special characters in the password
256
+
257
+ ### Connection Issues
258
+ - Ensure the database file is not locked by another application
259
+ - Verify the database file is a valid SQLCipher database
260
+ - Check that SQLCipher 3 encryption was used (not SQLCipher 4)
261
+
262
+ ## Development
263
+
264
+ ### Project Structure
265
+
266
+ ```
267
+ MyMCP/
268
+ ├── index.js # Main MCP server entry point
269
+ ├── lib/
270
+ │ └── database.js # Database connection and query logic
271
+ ├── package.json # Project dependencies
272
+ └── README.md # This file
273
+ ```
274
+
275
+ ### Dependencies
276
+
277
+ - `@modelcontextprotocol/sdk`: MCP SDK for Node.js
278
+ - `@journeyapps/sqlcipher`: SQLCipher bindings for Node.js
279
+
280
+ ## License
281
+
282
+ MIT
package/index.js ADDED
@@ -0,0 +1,226 @@
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
+ });
@@ -0,0 +1,216 @@
1
+ import sqlcipher from '@journeyapps/sqlcipher';
2
+ import fs from 'fs';
3
+
4
+ // Extract Database from the sqlcipher module object
5
+ const Database = sqlcipher.Database;
6
+
7
+ /**
8
+ * Connects to a SQLite database (encrypted or unencrypted)
9
+ * Supports both SQLCipher-encrypted and plain SQLite databases
10
+ *
11
+ * @param {string} dbPath - Path to the database file
12
+ * @param {string} [password] - Optional database password (for encrypted databases)
13
+ * @returns {Promise<Database>} Database connection instance
14
+ * @throws {Error} If database file doesn't exist or connection fails
15
+ */
16
+ export function connectDatabase(dbPath, password) {
17
+ return new Promise((resolve, reject) => {
18
+ // Validate database path exists
19
+ if (!fs.existsSync(dbPath)) {
20
+ return reject(new Error(`Database file not found: ${dbPath}`));
21
+ }
22
+
23
+ let db = null;
24
+ // Open database connection with callback
25
+ db = new Database(dbPath, (err) => {
26
+ if (err) {
27
+ return reject(new Error(`Failed to open database: ${err.message}`));
28
+ }
29
+
30
+ // If no password provided, treat as unencrypted SQLite database
31
+ if (!password || password.trim() === '') {
32
+ // Verify the database is accessible by running a simple query
33
+ db.get('SELECT 1', (getErr, row) => {
34
+ if (getErr) {
35
+ db.close((closeErr) => {
36
+ // Ignore close errors
37
+ });
38
+ return reject(new Error(`Failed to verify database: ${getErr.message}`));
39
+ }
40
+ resolve(db);
41
+ });
42
+ return;
43
+ }
44
+
45
+ // Password provided - treat as encrypted SQLCipher database
46
+ // Explicitly set SQLCipher 3 compatibility mode
47
+ // This ensures SQLCipher 3 defaults are used:
48
+ // - Page size: 1024 bytes
49
+ // - PBKDF2 iterations: 64,000
50
+ // - KDF algorithm: PBKDF2-HMAC-SHA1
51
+ // - HMAC algorithm: HMAC-SHA1
52
+ db.exec('PRAGMA cipher_compatibility = 3', (compatErr) => {
53
+ if (compatErr) {
54
+ db.close((closeErr) => {
55
+ // Ignore close errors
56
+ });
57
+ return reject(new Error(`Failed to set SQLCipher 3 compatibility: ${compatErr.message}`));
58
+ }
59
+
60
+ // Set SQLCipher 3 default encryption settings
61
+ // PRAGMA key sets the encryption key using SQLCipher 3 defaults
62
+ // PRAGMA key does NOT support parameterized queries, so we must embed the password directly
63
+ // Escape single quotes in password for SQL (double them) and escape backslashes
64
+ const escapedPassword = password.replace(/\\/g, '\\\\').replace(/'/g, "''");
65
+
66
+ // Use db.exec() with callback for PRAGMA key
67
+ db.exec(`PRAGMA key = '${escapedPassword}'`, (execErr) => {
68
+ if (execErr) {
69
+ db.close((closeErr) => {
70
+ // Ignore close errors
71
+ });
72
+ return reject(new Error(`Failed to set encryption key: ${execErr.message}`));
73
+ }
74
+
75
+ // Verify the database is accessible by running a simple query
76
+ // This will throw an error if the password is incorrect
77
+ db.get('SELECT 1', (getErr, row) => {
78
+ if (getErr) {
79
+ db.close((closeErr) => {
80
+ // Ignore close errors
81
+ });
82
+
83
+ if (getErr.message.includes('file is not a database') ||
84
+ getErr.message.includes('malformed database') ||
85
+ getErr.code === 'SQLITE_NOTADB') {
86
+ return reject(new Error('Invalid password or database is corrupted'));
87
+ }
88
+ return reject(new Error(`Failed to verify database: ${getErr.message}`));
89
+ }
90
+
91
+ resolve(db);
92
+ });
93
+ });
94
+ });
95
+ });
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Validates that a SQL query is a SELECT query (read-only)
101
+ *
102
+ * @param {string} query - SQL query string
103
+ * @returns {boolean} True if query is a SELECT query
104
+ * @throws {Error} If query is not a SELECT query
105
+ */
106
+ function validateSelectQuery(query) {
107
+ if (!query || typeof query !== 'string') {
108
+ throw new Error('Query must be a non-empty string');
109
+ }
110
+
111
+ // Trim and normalize whitespace
112
+ const normalizedQuery = query.trim().replace(/\s+/g, ' ');
113
+
114
+ // Check if query starts with SELECT (case-insensitive)
115
+ if (!normalizedQuery.match(/^SELECT\s+/i)) {
116
+ throw new Error('Only SELECT queries are allowed (read-only mode)');
117
+ }
118
+
119
+ // Additional check: ensure no DDL or DML keywords are present
120
+ const forbiddenKeywords = [
121
+ 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER',
122
+ 'TRUNCATE', 'REPLACE', 'PRAGMA'
123
+ ];
124
+
125
+ const upperQuery = normalizedQuery.toUpperCase();
126
+ for (const keyword of forbiddenKeywords) {
127
+ // Check for keyword followed by space or semicolon (to avoid false positives)
128
+ const regex = new RegExp(`\\b${keyword}\\s+`, 'i');
129
+ if (regex.test(normalizedQuery) && keyword !== 'SELECT') {
130
+ throw new Error(`Query contains forbidden keyword: ${keyword}. Only SELECT queries are allowed.`);
131
+ }
132
+ }
133
+
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * Executes a SELECT query on the database and returns results
139
+ *
140
+ * @param {Database} db - Database connection instance
141
+ * @param {string} query - SQL SELECT query to execute
142
+ * @returns {Promise<Object>} Query results with columns, rows, and rowCount
143
+ * @throws {Error} If query is invalid or execution fails
144
+ */
145
+ export function executeQuery(db, query) {
146
+ return new Promise((resolve, reject) => {
147
+ // Validate query is a SELECT query
148
+ try {
149
+ validateSelectQuery(query);
150
+ } catch (validationError) {
151
+ return reject(validationError);
152
+ }
153
+
154
+ // Prepare and execute the query with callback
155
+ const statement = db.prepare(query, (prepareErr) => {
156
+ if (prepareErr) {
157
+ return reject(new Error(`Query preparation failed: ${prepareErr.message}`));
158
+ }
159
+
160
+ // Execute query with callback - statement.all() requires a callback
161
+ statement.all((allErr, rows) => {
162
+ if (allErr) {
163
+ statement.finalize();
164
+ if (allErr.message.includes('no such table')) {
165
+ return reject(new Error(`Table not found: ${allErr.message}`));
166
+ } else if (allErr.message.includes('no such column')) {
167
+ return reject(new Error(`Column not found: ${allErr.message}`));
168
+ } else if (allErr.message.includes('syntax error')) {
169
+ return reject(new Error(`SQL syntax error: ${allErr.message}`));
170
+ }
171
+ return reject(new Error(`Query execution failed: ${allErr.message}`));
172
+ }
173
+
174
+ // Get column names from the first row
175
+ let columns = [];
176
+ if (rows && rows.length > 0) {
177
+ columns = Object.keys(rows[0]);
178
+ }
179
+
180
+ // Finalize the statement
181
+ statement.finalize();
182
+
183
+ resolve({
184
+ columns: columns,
185
+ rows: rows || [],
186
+ rowCount: rows ? rows.length : 0
187
+ });
188
+ });
189
+ });
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Closes a database connection
195
+ * Ensures all statements are finalized before closing
196
+ *
197
+ * @param {Database} db - Database connection instance
198
+ */
199
+ export function closeConnection(db) {
200
+ if (!db || typeof db.close !== 'function') {
201
+ return;
202
+ }
203
+
204
+ try {
205
+ // Close with callback to handle any errors gracefully
206
+ db.close((err) => {
207
+ if (err) {
208
+ // Log but don't throw - closing should be best effort
209
+ console.error('Error closing database connection:', err.message);
210
+ }
211
+ });
212
+ } catch (error) {
213
+ // Log but don't throw - closing should be best effort
214
+ console.error('Error closing database connection:', error.message);
215
+ }
216
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "sqlcipher-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for querying SQLCipher-encrypted SQLite databases",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "sqlcipher-mcp-server": "./index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "lib/**/*",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "start": "node index.js",
18
+ "start:http": "node server-http.js",
19
+ "test:http": "node server-http.js",
20
+ "dev": "nodemon index.js",
21
+ "dev:http": "nodemon server-http.js",
22
+ "prepublishOnly": "npm test || exit 0"
23
+ },
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
+ "dependencies": {
47
+ "@journeyapps/sqlcipher": "^5.1.1",
48
+ "@modelcontextprotocol/sdk": "^0.5.0",
49
+ "express": "^4.22.1"
50
+ },
51
+ "devDependencies": {
52
+ "nodemon": "^3.1.11"
53
+ }
54
+ }