@cmd233/mcp-database-server 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ExecuteAutomation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,29 @@
1
+ // Import adapters using dynamic imports
2
+ import { SqliteAdapter } from './sqlite-adapter.js';
3
+ import { SqlServerAdapter } from './sqlserver-adapter.js';
4
+ import { PostgresqlAdapter } from './postgresql-adapter.js';
5
+ import { MysqlAdapter } from './mysql-adapter.js';
6
+ /**
7
+ * Factory function to create the appropriate database adapter
8
+ */
9
+ export function createDbAdapter(type, connectionInfo) {
10
+ switch (type.toLowerCase()) {
11
+ case 'sqlite':
12
+ // For SQLite, if connectionInfo is a string, use it directly as path
13
+ if (typeof connectionInfo === 'string') {
14
+ return new SqliteAdapter(connectionInfo);
15
+ }
16
+ else {
17
+ return new SqliteAdapter(connectionInfo.path);
18
+ }
19
+ case 'sqlserver':
20
+ return new SqlServerAdapter(connectionInfo);
21
+ case 'postgresql':
22
+ case 'postgres':
23
+ return new PostgresqlAdapter(connectionInfo);
24
+ case 'mysql':
25
+ return new MysqlAdapter(connectionInfo);
26
+ default:
27
+ throw new Error(`Unsupported database type: ${type}`);
28
+ }
29
+ }
@@ -0,0 +1,95 @@
1
+ import { createDbAdapter } from './adapter.js';
2
+ // Store the active database adapter
3
+ let dbAdapter = null;
4
+ /**
5
+ * Initialize the database connection
6
+ * @param connectionInfo Connection information object or SQLite path string
7
+ * @param dbType Database type ('sqlite' or 'sqlserver')
8
+ */
9
+ export async function initDatabase(connectionInfo, dbType = 'sqlite') {
10
+ try {
11
+ // If connectionInfo is a string, assume it's a SQLite path
12
+ if (typeof connectionInfo === 'string') {
13
+ connectionInfo = { path: connectionInfo };
14
+ }
15
+ // Create appropriate adapter based on database type
16
+ dbAdapter = createDbAdapter(dbType, connectionInfo);
17
+ // Initialize the connection
18
+ await dbAdapter.init();
19
+ }
20
+ catch (error) {
21
+ throw new Error(`Failed to initialize database: ${error.message}`);
22
+ }
23
+ }
24
+ /**
25
+ * Execute a SQL query and get all results
26
+ * @param query SQL query to execute
27
+ * @param params Query parameters
28
+ * @returns Promise with query results
29
+ */
30
+ export function dbAll(query, params = []) {
31
+ if (!dbAdapter) {
32
+ throw new Error("Database not initialized");
33
+ }
34
+ return dbAdapter.all(query, params);
35
+ }
36
+ /**
37
+ * Execute a SQL query that modifies data
38
+ * @param query SQL query to execute
39
+ * @param params Query parameters
40
+ * @returns Promise with result info
41
+ */
42
+ export function dbRun(query, params = []) {
43
+ if (!dbAdapter) {
44
+ throw new Error("Database not initialized");
45
+ }
46
+ return dbAdapter.run(query, params);
47
+ }
48
+ /**
49
+ * Execute multiple SQL statements
50
+ * @param query SQL statements to execute
51
+ * @returns Promise that resolves when execution completes
52
+ */
53
+ export function dbExec(query) {
54
+ if (!dbAdapter) {
55
+ throw new Error("Database not initialized");
56
+ }
57
+ return dbAdapter.exec(query);
58
+ }
59
+ /**
60
+ * Close the database connection
61
+ */
62
+ export function closeDatabase() {
63
+ if (!dbAdapter) {
64
+ return Promise.resolve();
65
+ }
66
+ return dbAdapter.close();
67
+ }
68
+ /**
69
+ * Get database metadata
70
+ */
71
+ export function getDatabaseMetadata() {
72
+ if (!dbAdapter) {
73
+ throw new Error("Database not initialized");
74
+ }
75
+ return dbAdapter.getMetadata();
76
+ }
77
+ /**
78
+ * Get database-specific query for listing tables
79
+ */
80
+ export function getListTablesQuery() {
81
+ if (!dbAdapter) {
82
+ throw new Error("Database not initialized");
83
+ }
84
+ return dbAdapter.getListTablesQuery();
85
+ }
86
+ /**
87
+ * Get database-specific query for describing a table
88
+ * @param tableName Table name
89
+ */
90
+ export function getDescribeTableQuery(tableName) {
91
+ if (!dbAdapter) {
92
+ throw new Error("Database not initialized");
93
+ }
94
+ return dbAdapter.getDescribeTableQuery(tableName);
95
+ }
@@ -0,0 +1,205 @@
1
+ import mysql from "mysql2/promise";
2
+ import { Signer } from "@aws-sdk/rds-signer";
3
+ /**
4
+ * MySQL database adapter implementation
5
+ */
6
+ export class MysqlAdapter {
7
+ constructor(connectionInfo) {
8
+ this.connection = null;
9
+ this.host = connectionInfo.host;
10
+ this.database = connectionInfo.database;
11
+ this.awsIamAuth = connectionInfo.awsIamAuth || false;
12
+ this.awsRegion = connectionInfo.awsRegion;
13
+ this.config = {
14
+ host: connectionInfo.host,
15
+ database: connectionInfo.database,
16
+ port: connectionInfo.port || 3306,
17
+ user: connectionInfo.user,
18
+ password: connectionInfo.password,
19
+ connectTimeout: connectionInfo.connectionTimeout || 30000,
20
+ multipleStatements: true,
21
+ };
22
+ if (typeof connectionInfo.ssl === 'object' || typeof connectionInfo.ssl === 'string') {
23
+ this.config.ssl = connectionInfo.ssl;
24
+ }
25
+ else if (connectionInfo.ssl === true) {
26
+ // For AWS IAM authentication, configure SSL appropriately for RDS
27
+ if (this.awsIamAuth) {
28
+ this.config.ssl = {
29
+ rejectUnauthorized: false // AWS RDS handles certificate validation
30
+ };
31
+ }
32
+ else {
33
+ this.config.ssl = {};
34
+ }
35
+ }
36
+ // Validate port
37
+ if (connectionInfo.port && typeof connectionInfo.port !== 'number') {
38
+ const parsedPort = parseInt(connectionInfo.port, 10);
39
+ if (isNaN(parsedPort)) {
40
+ throw new Error(`Invalid port value for MySQL: ${connectionInfo.port}`);
41
+ }
42
+ this.config.port = parsedPort;
43
+ }
44
+ // Log the port for debugging
45
+ console.error(`[DEBUG] MySQL connection will use port: ${this.config.port}`);
46
+ }
47
+ /**
48
+ * Generate AWS RDS authentication token
49
+ */
50
+ async generateAwsAuthToken() {
51
+ if (!this.awsRegion) {
52
+ throw new Error("AWS region is required for IAM authentication");
53
+ }
54
+ if (!this.config.user) {
55
+ throw new Error("AWS username is required for IAM authentication");
56
+ }
57
+ try {
58
+ console.info(`[INFO] Generating AWS auth token for region: ${this.awsRegion}, host: ${this.host}, user: ${this.config.user}`);
59
+ const signer = new Signer({
60
+ region: this.awsRegion,
61
+ hostname: this.host,
62
+ port: this.config.port || 3306,
63
+ username: this.config.user,
64
+ });
65
+ const token = await signer.getAuthToken();
66
+ console.info(`[INFO] AWS auth token generated successfully`);
67
+ return token;
68
+ }
69
+ catch (err) {
70
+ console.error(`[ERROR] Failed to generate AWS auth token: ${err.message}`);
71
+ throw new Error(`AWS IAM authentication failed: ${err.message}. Please check your AWS credentials and IAM permissions.`);
72
+ }
73
+ }
74
+ /**
75
+ * Initialize MySQL connection
76
+ */
77
+ async init() {
78
+ try {
79
+ console.info(`[INFO] Connecting to MySQL: ${this.host}, Database: ${this.database}`);
80
+ // Handle AWS IAM authentication
81
+ if (this.awsIamAuth) {
82
+ console.info(`[INFO] Using AWS IAM authentication for user: ${this.config.user}`);
83
+ try {
84
+ const authToken = await this.generateAwsAuthToken();
85
+ // Create a new config with the generated token as password
86
+ const awsConfig = {
87
+ ...this.config,
88
+ password: authToken
89
+ };
90
+ this.connection = await mysql.createConnection(awsConfig);
91
+ }
92
+ catch (err) {
93
+ console.error(`[ERROR] AWS IAM authentication failed: ${err.message}`);
94
+ throw new Error(`AWS IAM authentication failed: ${err.message}`);
95
+ }
96
+ }
97
+ else {
98
+ this.connection = await mysql.createConnection(this.config);
99
+ }
100
+ console.info(`[INFO] MySQL connection established successfully`);
101
+ }
102
+ catch (err) {
103
+ console.error(`[ERROR] MySQL connection error: ${err.message}`);
104
+ if (this.awsIamAuth) {
105
+ throw new Error(`Failed to connect to MySQL with AWS IAM authentication: ${err.message}. Please verify your AWS credentials, IAM permissions, and RDS configuration.`);
106
+ }
107
+ else {
108
+ throw new Error(`Failed to connect to MySQL: ${err.message}`);
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Execute a SQL query and get all results
114
+ */
115
+ async all(query, params = []) {
116
+ if (!this.connection) {
117
+ throw new Error("Database not initialized");
118
+ }
119
+ try {
120
+ const [rows] = await this.connection.execute(query, params);
121
+ return Array.isArray(rows) ? rows : [];
122
+ }
123
+ catch (err) {
124
+ throw new Error(`MySQL query error: ${err.message}`);
125
+ }
126
+ }
127
+ /**
128
+ * Execute a SQL query that modifies data
129
+ */
130
+ async run(query, params = []) {
131
+ if (!this.connection) {
132
+ throw new Error("Database not initialized");
133
+ }
134
+ try {
135
+ const [result] = await this.connection.execute(query, params);
136
+ const changes = result.affectedRows || 0;
137
+ const lastID = result.insertId || 0;
138
+ return { changes, lastID };
139
+ }
140
+ catch (err) {
141
+ throw new Error(`MySQL query error: ${err.message}`);
142
+ }
143
+ }
144
+ /**
145
+ * Execute multiple SQL statements
146
+ */
147
+ async exec(query) {
148
+ if (!this.connection) {
149
+ throw new Error("Database not initialized");
150
+ }
151
+ try {
152
+ await this.connection.query(query);
153
+ }
154
+ catch (err) {
155
+ throw new Error(`MySQL batch error: ${err.message}`);
156
+ }
157
+ }
158
+ /**
159
+ * Close the database connection
160
+ */
161
+ async close() {
162
+ if (this.connection) {
163
+ await this.connection.end();
164
+ this.connection = null;
165
+ }
166
+ }
167
+ /**
168
+ * Get database metadata
169
+ */
170
+ getMetadata() {
171
+ return {
172
+ name: "MySQL",
173
+ type: "mysql",
174
+ server: this.host,
175
+ database: this.database,
176
+ };
177
+ }
178
+ /**
179
+ * Get database-specific query for listing tables
180
+ */
181
+ getListTablesQuery() {
182
+ return `SELECT table_name AS name FROM information_schema.tables WHERE table_schema = '${this.database}'`;
183
+ }
184
+ /**
185
+ * Get database-specific query for describing a table
186
+ */
187
+ getDescribeTableQuery(tableName) {
188
+ // MySQL DESCRIBE returns columns with different names, so we use a query that matches the expected format
189
+ return `
190
+ SELECT
191
+ COLUMN_NAME as name,
192
+ DATA_TYPE as type,
193
+ CASE WHEN IS_NULLABLE = 'NO' THEN 1 ELSE 0 END as notnull,
194
+ CASE WHEN COLUMN_KEY = 'PRI' THEN 1 ELSE 0 END as pk,
195
+ COLUMN_DEFAULT as dflt_value
196
+ FROM
197
+ INFORMATION_SCHEMA.COLUMNS
198
+ WHERE
199
+ TABLE_NAME = '${tableName}'
200
+ AND TABLE_SCHEMA = '${this.database}'
201
+ ORDER BY
202
+ ORDINAL_POSITION
203
+ `;
204
+ }
205
+ }
@@ -0,0 +1,169 @@
1
+ import pg from 'pg';
2
+ /**
3
+ * PostgreSQL database adapter implementation
4
+ */
5
+ export class PostgresqlAdapter {
6
+ constructor(connectionInfo) {
7
+ this.client = null;
8
+ this.host = connectionInfo.host;
9
+ this.database = connectionInfo.database;
10
+ // Create PostgreSQL connection config
11
+ this.config = {
12
+ host: connectionInfo.host,
13
+ database: connectionInfo.database,
14
+ port: connectionInfo.port || 5432,
15
+ user: connectionInfo.user,
16
+ password: connectionInfo.password,
17
+ ssl: connectionInfo.ssl,
18
+ // Add connection timeout if provided (in milliseconds)
19
+ connectionTimeoutMillis: connectionInfo.connectionTimeout || 30000,
20
+ };
21
+ }
22
+ /**
23
+ * Initialize PostgreSQL connection
24
+ */
25
+ async init() {
26
+ try {
27
+ console.error(`[INFO] Connecting to PostgreSQL: ${this.host}, Database: ${this.database}`);
28
+ console.error(`[DEBUG] Connection details:`, {
29
+ host: this.host,
30
+ database: this.database,
31
+ port: this.config.port,
32
+ user: this.config.user,
33
+ connectionTimeoutMillis: this.config.connectionTimeoutMillis,
34
+ ssl: !!this.config.ssl
35
+ });
36
+ this.client = new pg.Client(this.config);
37
+ await this.client.connect();
38
+ console.error(`[INFO] PostgreSQL connection established successfully`);
39
+ }
40
+ catch (err) {
41
+ console.error(`[ERROR] PostgreSQL connection error: ${err.message}`);
42
+ throw new Error(`Failed to connect to PostgreSQL: ${err.message}`);
43
+ }
44
+ }
45
+ /**
46
+ * Execute a SQL query and get all results
47
+ * @param query SQL query to execute
48
+ * @param params Query parameters
49
+ * @returns Promise with query results
50
+ */
51
+ async all(query, params = []) {
52
+ if (!this.client) {
53
+ throw new Error("Database not initialized");
54
+ }
55
+ try {
56
+ // PostgreSQL uses $1, $2, etc. for parameterized queries
57
+ const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
58
+ const result = await this.client.query(preparedQuery, params);
59
+ return result.rows;
60
+ }
61
+ catch (err) {
62
+ throw new Error(`PostgreSQL query error: ${err.message}`);
63
+ }
64
+ }
65
+ /**
66
+ * Execute a SQL query that modifies data
67
+ * @param query SQL query to execute
68
+ * @param params Query parameters
69
+ * @returns Promise with result info
70
+ */
71
+ async run(query, params = []) {
72
+ if (!this.client) {
73
+ throw new Error("Database not initialized");
74
+ }
75
+ try {
76
+ // Replace ? with numbered parameters
77
+ const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
78
+ let lastID = 0;
79
+ let changes = 0;
80
+ // For INSERT queries, try to get the inserted ID
81
+ if (query.trim().toUpperCase().startsWith('INSERT')) {
82
+ // Add RETURNING clause to get the inserted ID if it doesn't already have one
83
+ const returningQuery = preparedQuery.includes('RETURNING')
84
+ ? preparedQuery
85
+ : `${preparedQuery} RETURNING id`;
86
+ const result = await this.client.query(returningQuery, params);
87
+ changes = result.rowCount || 0;
88
+ lastID = result.rows[0]?.id || 0;
89
+ }
90
+ else {
91
+ const result = await this.client.query(preparedQuery, params);
92
+ changes = result.rowCount || 0;
93
+ }
94
+ return { changes, lastID };
95
+ }
96
+ catch (err) {
97
+ throw new Error(`PostgreSQL query error: ${err.message}`);
98
+ }
99
+ }
100
+ /**
101
+ * Execute multiple SQL statements
102
+ * @param query SQL statements to execute
103
+ * @returns Promise that resolves when execution completes
104
+ */
105
+ async exec(query) {
106
+ if (!this.client) {
107
+ throw new Error("Database not initialized");
108
+ }
109
+ try {
110
+ await this.client.query(query);
111
+ }
112
+ catch (err) {
113
+ throw new Error(`PostgreSQL batch error: ${err.message}`);
114
+ }
115
+ }
116
+ /**
117
+ * Close the database connection
118
+ */
119
+ async close() {
120
+ if (this.client) {
121
+ await this.client.end();
122
+ this.client = null;
123
+ }
124
+ }
125
+ /**
126
+ * Get database metadata
127
+ */
128
+ getMetadata() {
129
+ return {
130
+ name: "PostgreSQL",
131
+ type: "postgresql",
132
+ server: this.host,
133
+ database: this.database
134
+ };
135
+ }
136
+ /**
137
+ * Get database-specific query for listing tables
138
+ */
139
+ getListTablesQuery() {
140
+ return "SELECT table_name as name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name";
141
+ }
142
+ /**
143
+ * Get database-specific query for describing a table
144
+ * @param tableName Table name
145
+ */
146
+ getDescribeTableQuery(tableName) {
147
+ return `
148
+ SELECT
149
+ c.column_name as name,
150
+ c.data_type as type,
151
+ CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END as notnull,
152
+ CASE WHEN pk.constraint_name IS NOT NULL THEN 1 ELSE 0 END as pk,
153
+ c.column_default as dflt_value
154
+ FROM
155
+ information_schema.columns c
156
+ LEFT JOIN
157
+ information_schema.key_column_usage kcu
158
+ ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name
159
+ LEFT JOIN
160
+ information_schema.table_constraints pk
161
+ ON kcu.constraint_name = pk.constraint_name AND pk.constraint_type = 'PRIMARY KEY'
162
+ WHERE
163
+ c.table_name = '${tableName}'
164
+ AND c.table_schema = 'public'
165
+ ORDER BY
166
+ c.ordinal_position
167
+ `;
168
+ }
169
+ }
@@ -0,0 +1,134 @@
1
+ import sqlite3 from "sqlite3";
2
+ /**
3
+ * SQLite database adapter implementation
4
+ */
5
+ export class SqliteAdapter {
6
+ constructor(dbPath) {
7
+ this.db = null;
8
+ this.dbPath = dbPath;
9
+ }
10
+ /**
11
+ * Initialize the SQLite database connection
12
+ */
13
+ async init() {
14
+ return new Promise((resolve, reject) => {
15
+ // Ensure the dbPath is accessible
16
+ console.error(`[INFO] Opening SQLite database at: ${this.dbPath}`);
17
+ this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
18
+ if (err) {
19
+ console.error(`[ERROR] SQLite connection error: ${err.message}`);
20
+ reject(err);
21
+ }
22
+ else {
23
+ console.error("[INFO] SQLite database opened successfully");
24
+ resolve();
25
+ }
26
+ });
27
+ });
28
+ }
29
+ /**
30
+ * Execute a SQL query and get all results
31
+ * @param query SQL query to execute
32
+ * @param params Query parameters
33
+ * @returns Promise with query results
34
+ */
35
+ async all(query, params = []) {
36
+ if (!this.db) {
37
+ throw new Error("Database not initialized");
38
+ }
39
+ return new Promise((resolve, reject) => {
40
+ this.db.all(query, params, (err, rows) => {
41
+ if (err) {
42
+ reject(err);
43
+ }
44
+ else {
45
+ resolve(rows);
46
+ }
47
+ });
48
+ });
49
+ }
50
+ /**
51
+ * Execute a SQL query that modifies data
52
+ * @param query SQL query to execute
53
+ * @param params Query parameters
54
+ * @returns Promise with result info
55
+ */
56
+ async run(query, params = []) {
57
+ if (!this.db) {
58
+ throw new Error("Database not initialized");
59
+ }
60
+ return new Promise((resolve, reject) => {
61
+ this.db.run(query, params, function (err) {
62
+ if (err) {
63
+ reject(err);
64
+ }
65
+ else {
66
+ resolve({ changes: this.changes, lastID: this.lastID });
67
+ }
68
+ });
69
+ });
70
+ }
71
+ /**
72
+ * Execute multiple SQL statements
73
+ * @param query SQL statements to execute
74
+ * @returns Promise that resolves when execution completes
75
+ */
76
+ async exec(query) {
77
+ if (!this.db) {
78
+ throw new Error("Database not initialized");
79
+ }
80
+ return new Promise((resolve, reject) => {
81
+ this.db.exec(query, (err) => {
82
+ if (err) {
83
+ reject(err);
84
+ }
85
+ else {
86
+ resolve();
87
+ }
88
+ });
89
+ });
90
+ }
91
+ /**
92
+ * Close the database connection
93
+ */
94
+ async close() {
95
+ return new Promise((resolve, reject) => {
96
+ if (!this.db) {
97
+ resolve();
98
+ return;
99
+ }
100
+ this.db.close((err) => {
101
+ if (err) {
102
+ reject(err);
103
+ }
104
+ else {
105
+ this.db = null;
106
+ resolve();
107
+ }
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Get database metadata
113
+ */
114
+ getMetadata() {
115
+ return {
116
+ name: "SQLite",
117
+ type: "sqlite",
118
+ path: this.dbPath
119
+ };
120
+ }
121
+ /**
122
+ * Get database-specific query for listing tables
123
+ */
124
+ getListTablesQuery() {
125
+ return "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
126
+ }
127
+ /**
128
+ * Get database-specific query for describing a table
129
+ * @param tableName Table name
130
+ */
131
+ getDescribeTableQuery(tableName) {
132
+ return `PRAGMA table_info(${tableName})`;
133
+ }
134
+ }