@bytebase/dbhub 0.2.2 → 0.3.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 (3) hide show
  1. package/README.md +43 -27
  2. package/dist/index.js +506 -138
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
  <a href="https://dbhub.ai/" target="_blank">
3
3
  <picture>
4
- <img src="https://raw.githubusercontent.com/bytebase/dbhub/main/resources/images/logo-full.svg" width="50%">
4
+ <img src="https://raw.githubusercontent.com/bytebase/dbhub/main/resources/images/logo-full.webp" width="50%">
5
5
  </picture>
6
6
  </a>
7
7
  </p>
@@ -20,7 +20,7 @@ DBHub is a universal database gateway implementing the Model Context Protocol (M
20
20
  | Clients | | | | |
21
21
  | | | +--->+ MySQL |
22
22
  | | | | | |
23
- | | | +--->+ Other Databases |
23
+ | | | +--->+ MariaDB |
24
24
  | | | | | |
25
25
  +------------------+ +--------------+ +------------------+
26
26
  MCP Clients MCP Server Databases
@@ -36,28 +36,28 @@ https://demo.dbhub.ai/sse connects a [sample employee database](https://github.c
36
36
 
37
37
  ### Database Resources
38
38
 
39
- | Resource Name | URI Format | PostgreSQL | MySQL | SQL Server | SQLite |
40
- | ----------------------------- | ----------------------------------------------------------- | :--------: | :---: | :--------: | :----: |
41
- | schemas | `db://schemas` | ✅ | ✅ | ✅ | ✅ |
42
- | tables_in_schema | `db://schemas/{schemaName}/tables` | ✅ | ✅ | ✅ | ✅ |
43
- | table_structure_in_schema | `db://schemas/{schemaName}/tables/{tableName}` | ✅ | ✅ | ✅ | ✅ |
44
- | indexes_in_table | `db://schemas/{schemaName}/tables/{tableName}/indexes` | ✅ | ✅ | ✅ | ✅ |
45
- | procedures_in_schema | `db://schemas/{schemaName}/procedures` | ✅ | ✅ | ✅ | ❌ |
46
- | procedure_details_in_schema | `db://schemas/{schemaName}/procedures/{procedureName}` | ✅ | ✅ | ✅ | ❌ |
39
+ | Resource Name | URI Format | PostgreSQL | MySQL | MariaDB | SQL Server | SQLite |
40
+ | --------------------------- | ------------------------------------------------------ | :--------: | :---: | :-----: | :--------: | :----: |
41
+ | schemas | `db://schemas` | ✅ | ✅ | ✅ | ✅ | ✅ |
42
+ | tables_in_schema | `db://schemas/{schemaName}/tables` | ✅ | ✅ | ✅ | ✅ | ✅ |
43
+ | table_structure_in_schema | `db://schemas/{schemaName}/tables/{tableName}` | ✅ | ✅ | ✅ | ✅ | ✅ |
44
+ | indexes_in_table | `db://schemas/{schemaName}/tables/{tableName}/indexes` | ✅ | ✅ | ✅ | ✅ | ✅ |
45
+ | procedures_in_schema | `db://schemas/{schemaName}/procedures` | ✅ | ✅ | ✅ | ✅ | ❌ |
46
+ | procedure_details_in_schema | `db://schemas/{schemaName}/procedures/{procedureName}` | ✅ | ✅ | ✅ | ✅ | ❌ |
47
47
 
48
48
  ### Database Tools
49
49
 
50
- | Tool | Command Name | PostgreSQL | MySQL | SQL Server | SQLite |
51
- | --------------- | ----------------- | :--------: | :---: | :--------: | :----: |
52
- | Execute Query | `run_query` | ✅ | ✅ | ✅ | |
53
- | List Connectors | `list_connectors` | ✅ | ✅ | ✅ | |
50
+ | Tool | Command Name | PostgreSQL | MySQL | MariaDB | SQL Server | SQLite |
51
+ | --------------- | ----------------- | :--------: | :---: | :-----: | :--------: | ------ |
52
+ | Execute Query | `run_query` | ✅ | ✅ | ✅ | ✅ | |
53
+ | List Connectors | `list_connectors` | ✅ | ✅ | ✅ | ✅ | |
54
54
 
55
55
  ### Prompt Capabilities
56
56
 
57
- | Prompt | Command Name | PostgreSQL | MySQL | SQL Server | SQLite |
58
- | ------------------- | --------------- | :--------: | :---: | :--------: | :----: |
59
- | Generate SQL | `generate_sql` | ✅ | ✅ | ✅ | |
60
- | Explain DB Elements | `explain_db` | ✅ | ✅ | ✅ | |
57
+ | Prompt | Command Name | PostgreSQL | MySQL | MariaDB | SQL Server | SQLite |
58
+ | ------------------- | -------------- | :--------: | :---: | :-----: | :--------: | ------ |
59
+ | Generate SQL | `generate_sql` | ✅ | ✅ | ✅ | ✅ | |
60
+ | Explain DB Elements | `explain_db` | ✅ | ✅ | ✅ | ✅ | |
61
61
 
62
62
  ## Installation
63
63
 
@@ -156,7 +156,7 @@ npx @bytebase/dbhub --transport sse --port 8080 --demo
156
156
  You can use DBHub in demo mode with a sample employee database for testing:
157
157
 
158
158
  ```bash
159
- pnpm dev --demo
159
+ npx @bytebase/dbhub --demo
160
160
  ```
161
161
 
162
162
  For real databases, a Database Source Name (DSN) is required. You can provide this in several ways:
@@ -164,14 +164,14 @@ For real databases, a Database Source Name (DSN) is required. You can provide th
164
164
  - **Command line argument** (highest priority):
165
165
 
166
166
  ```bash
167
- pnpm dev --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
167
+ npx @bytebase/dbhub --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
168
168
  ```
169
169
 
170
170
  - **Environment variable** (second priority):
171
171
 
172
172
  ```bash
173
173
  export DSN="postgres://user:password@localhost:5432/dbname?sslmode=disable"
174
- pnpm dev
174
+ npx @bytebase/dbhub
175
175
  ```
176
176
 
177
177
  - **Environment file** (third priority):
@@ -181,14 +181,26 @@ For real databases, a Database Source Name (DSN) is required. You can provide th
181
181
  DSN=postgres://user:password@localhost:5432/dbname?sslmode=disable
182
182
  ```
183
183
 
184
+ > [!WARNING]
185
+ > When running in Docker, use `host.docker.internal` instead of `localhost` to connect to databases running on your host machine. For example: `mysql://user:password@host.docker.internal:3306/dbname`
186
+
184
187
  DBHub supports the following database connection string formats:
185
188
 
186
- | Database | DSN Format | Example |
187
- | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
188
- | PostgreSQL | `postgres://[user]:[password]@[host]:[port]/[database]` | `postgres://user:password@localhost:5432/dbname?sslmode=disable` |
189
- | SQLite | `sqlite:///[path/to/file]` or `sqlite::memory:` | `sqlite:///path/to/database.db` or `sqlite::memory:` |
190
- | SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname` |
191
- | MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname` |
189
+ | Database | DSN Format | Example |
190
+ | ---------- | -------------------------------------------------------- | ---------------------------------------------------------------- |
191
+ | MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname` |
192
+ | MariaDB | `mariadb://[user]:[password]@[host]:[port]/[database]` | `mariadb://user:password@localhost:3306/dbname` |
193
+ | PostgreSQL | `postgres://[user]:[password]@[host]:[port]/[database]` | `postgres://user:password@localhost:5432/dbname?sslmode=disable` |
194
+ | SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname` |
195
+ | SQLite | `sqlite:///[path/to/file]` or `sqlite::memory:` | `sqlite:///path/to/database.db` or `sqlite::memory:` |
196
+
197
+ #### SQL Server
198
+
199
+ Extra query parameters:
200
+
201
+ #### authentication
202
+
203
+ - `authentication=azure-active-directory-access-token`. Only applicable when running from Azure. See [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/javascript/sdk/authentication/credential-chains#use-defaultazurecredential-for-flexibility).
192
204
 
193
205
  ### Transport
194
206
 
@@ -254,3 +266,7 @@ npx @modelcontextprotocol/inspector
254
266
  ```
255
267
 
256
268
  Connect to the DBHub server `/sse` endpoint
269
+
270
+ ## Star History
271
+
272
+ [![Star History Chart](https://api.star-history.com/svg?repos=bytebase/dbhub&type=Date)](https://www.star-history.com/#bytebase/dbhub&Date)
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ var ConnectorRegistry = _ConnectorRegistry;
60
60
  // src/connectors/postgres/index.ts
61
61
  var { Pool } = pg;
62
62
  var PostgresDSNParser = class {
63
- parse(dsn) {
63
+ async parse(dsn) {
64
64
  if (!this.isValidDSN(dsn)) {
65
65
  throw new Error(`Invalid PostgreSQL DSN: ${dsn}`);
66
66
  }
@@ -72,7 +72,7 @@ var PostgresDSNParser = class {
72
72
  database: url.pathname.substring(1),
73
73
  // Remove leading '/'
74
74
  user: url.username,
75
- password: url.password
75
+ password: url.password ? decodeURIComponent(url.password) : ""
76
76
  };
77
77
  url.searchParams.forEach((value, key) => {
78
78
  if (key === "sslmode") {
@@ -105,7 +105,7 @@ var PostgresConnector = class {
105
105
  }
106
106
  async connect(dsn) {
107
107
  try {
108
- const config = this.dsnParser.parse(dsn);
108
+ const config = await this.dsnParser.parse(dsn);
109
109
  this.pool = new Pool(config);
110
110
  const client = await this.pool.connect();
111
111
  console.error("Successfully connected to PostgreSQL database");
@@ -363,8 +363,9 @@ ConnectorRegistry.register(postgresConnector);
363
363
 
364
364
  // src/connectors/sqlserver/index.ts
365
365
  import sql from "mssql";
366
+ import { DefaultAzureCredential } from "@azure/identity";
366
367
  var SQLServerDSNParser = class {
367
- parse(dsn) {
368
+ async parse(dsn) {
368
369
  if (!this.isValidDSN(dsn)) {
369
370
  throw new Error("Invalid SQL Server DSN format. Expected: sqlserver://username:password@host:port/database");
370
371
  }
@@ -373,7 +374,7 @@ var SQLServerDSNParser = class {
373
374
  const port = url.port ? parseInt(url.port, 10) : 1433;
374
375
  const database = url.pathname.substring(1);
375
376
  const user = url.username;
376
- const password = url.password;
377
+ const password = url.password ? decodeURIComponent(url.password) : "";
377
378
  const options = {};
378
379
  for (const [key, value] of url.searchParams.entries()) {
379
380
  if (key === "encrypt") {
@@ -384,9 +385,11 @@ var SQLServerDSNParser = class {
384
385
  options.connectTimeout = parseInt(value, 10);
385
386
  } else if (key === "requestTimeout") {
386
387
  options.requestTimeout = parseInt(value, 10);
388
+ } else if (key === "authentication") {
389
+ options.authentication = value;
387
390
  }
388
391
  }
389
- return {
392
+ const config = {
390
393
  user,
391
394
  password,
392
395
  server: host,
@@ -401,6 +404,22 @@ var SQLServerDSNParser = class {
401
404
  requestTimeout: options.requestTimeout ?? 15e3
402
405
  }
403
406
  };
407
+ if (options.authentication === "azure-active-directory-access-token") {
408
+ try {
409
+ const credential = new DefaultAzureCredential();
410
+ const token = await credential.getToken("https://database.windows.net/");
411
+ config.authentication = {
412
+ type: "azure-active-directory-access-token",
413
+ options: {
414
+ token: token.token
415
+ }
416
+ };
417
+ } catch (error) {
418
+ const errorMessage = error instanceof Error ? error.message : String(error);
419
+ throw new Error(`Failed to get Azure AD token: ${errorMessage}`);
420
+ }
421
+ }
422
+ return config;
404
423
  }
405
424
  getSampleDSN() {
406
425
  return "sqlserver://username:password@localhost:1433/database?encrypt=true";
@@ -422,7 +441,7 @@ var SQLServerConnector = class {
422
441
  }
423
442
  async connect(dsn) {
424
443
  try {
425
- this.config = this.dsnParser.parse(dsn);
444
+ this.config = await this.dsnParser.parse(dsn);
426
445
  if (!this.config.options) {
427
446
  this.config.options = {};
428
447
  }
@@ -443,9 +462,9 @@ var SQLServerConnector = class {
443
462
  }
444
463
  try {
445
464
  const result = await this.connection.request().query(`
446
- SELECT SCHEMA_NAME
447
- FROM INFORMATION_SCHEMA.SCHEMATA
448
- ORDER BY SCHEMA_NAME
465
+ SELECT SCHEMA_NAME
466
+ FROM INFORMATION_SCHEMA.SCHEMATA
467
+ ORDER BY SCHEMA_NAME
449
468
  `);
450
469
  return result.recordset.map((row) => row.SCHEMA_NAME);
451
470
  } catch (error) {
@@ -460,10 +479,10 @@ var SQLServerConnector = class {
460
479
  const schemaToUse = schema || "dbo";
461
480
  const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
462
481
  const query = `
463
- SELECT TABLE_NAME
464
- FROM INFORMATION_SCHEMA.TABLES
465
- WHERE TABLE_SCHEMA = @schema
466
- ORDER BY TABLE_NAME
482
+ SELECT TABLE_NAME
483
+ FROM INFORMATION_SCHEMA.TABLES
484
+ WHERE TABLE_SCHEMA = @schema
485
+ ORDER BY TABLE_NAME
467
486
  `;
468
487
  const result = await request.query(query);
469
488
  return result.recordset.map((row) => row.TABLE_NAME);
@@ -479,10 +498,10 @@ var SQLServerConnector = class {
479
498
  const schemaToUse = schema || "dbo";
480
499
  const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
481
500
  const query = `
482
- SELECT COUNT(*) as count
483
- FROM INFORMATION_SCHEMA.TABLES
484
- WHERE TABLE_NAME = @tableName
485
- AND TABLE_SCHEMA = @schema
501
+ SELECT COUNT(*) as count
502
+ FROM INFORMATION_SCHEMA.TABLES
503
+ WHERE TABLE_NAME = @tableName
504
+ AND TABLE_SCHEMA = @schema
486
505
  `;
487
506
  const result = await request.query(query);
488
507
  return result.recordset[0].count > 0;
@@ -498,28 +517,24 @@ var SQLServerConnector = class {
498
517
  const schemaToUse = schema || "dbo";
499
518
  const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
500
519
  const query = `
501
- SELECT
502
- i.name AS index_name,
503
- i.is_unique,
504
- i.is_primary_key,
505
- c.name AS column_name,
506
- ic.key_ordinal
507
- FROM
508
- sys.indexes i
509
- INNER JOIN
510
- sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
511
- INNER JOIN
512
- sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
513
- INNER JOIN
514
- sys.tables t ON i.object_id = t.object_id
515
- INNER JOIN
516
- sys.schemas s ON t.schema_id = s.schema_id
517
- WHERE
518
- t.name = @tableName
519
- AND s.name = @schema
520
- ORDER BY
521
- i.name,
522
- ic.key_ordinal
520
+ SELECT i.name AS index_name,
521
+ i.is_unique,
522
+ i.is_primary_key,
523
+ c.name AS column_name,
524
+ ic.key_ordinal
525
+ FROM sys.indexes i
526
+ INNER JOIN
527
+ sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
528
+ INNER JOIN
529
+ sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
530
+ INNER JOIN
531
+ sys.tables t ON i.object_id = t.object_id
532
+ INNER JOIN
533
+ sys.schemas s ON t.schema_id = s.schema_id
534
+ WHERE t.name = @tableName
535
+ AND s.name = @schema
536
+ ORDER BY i.name,
537
+ ic.key_ordinal
523
538
  `;
524
539
  const result = await request.query(query);
525
540
  const indexMap = /* @__PURE__ */ new Map();
@@ -560,15 +575,14 @@ var SQLServerConnector = class {
560
575
  const schemaToUse = schema || "dbo";
561
576
  const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
562
577
  const query = `
563
- SELECT
564
- COLUMN_NAME as column_name,
565
- DATA_TYPE as data_type,
566
- IS_NULLABLE as is_nullable,
567
- COLUMN_DEFAULT as column_default
568
- FROM INFORMATION_SCHEMA.COLUMNS
569
- WHERE TABLE_NAME = @tableName
570
- AND TABLE_SCHEMA = @schema
571
- ORDER BY ORDINAL_POSITION
578
+ SELECT COLUMN_NAME as column_name,
579
+ DATA_TYPE as data_type,
580
+ IS_NULLABLE as is_nullable,
581
+ COLUMN_DEFAULT as column_default
582
+ FROM INFORMATION_SCHEMA.COLUMNS
583
+ WHERE TABLE_NAME = @tableName
584
+ AND TABLE_SCHEMA = @schema
585
+ ORDER BY ORDINAL_POSITION
572
586
  `;
573
587
  const result = await request.query(query);
574
588
  return result.recordset;
@@ -584,11 +598,11 @@ var SQLServerConnector = class {
584
598
  const schemaToUse = schema || "dbo";
585
599
  const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
586
600
  const query = `
587
- SELECT ROUTINE_NAME
588
- FROM INFORMATION_SCHEMA.ROUTINES
589
- WHERE ROUTINE_SCHEMA = @schema
590
- AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')
591
- ORDER BY ROUTINE_NAME
601
+ SELECT ROUTINE_NAME
602
+ FROM INFORMATION_SCHEMA.ROUTINES
603
+ WHERE ROUTINE_SCHEMA = @schema
604
+ AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')
605
+ ORDER BY ROUTINE_NAME
592
606
  `;
593
607
  const result = await request.query(query);
594
608
  return result.recordset.map((row) => row.ROUTINE_NAME);
@@ -604,13 +618,12 @@ var SQLServerConnector = class {
604
618
  const schemaToUse = schema || "dbo";
605
619
  const request = this.connection.request().input("procedureName", sql.VarChar, procedureName).input("schema", sql.VarChar, schemaToUse);
606
620
  const routineQuery = `
607
- SELECT
608
- ROUTINE_NAME as procedure_name,
609
- ROUTINE_TYPE,
610
- DATA_TYPE as return_data_type
611
- FROM INFORMATION_SCHEMA.ROUTINES
612
- WHERE ROUTINE_NAME = @procedureName
613
- AND ROUTINE_SCHEMA = @schema
621
+ SELECT ROUTINE_NAME as procedure_name,
622
+ ROUTINE_TYPE,
623
+ DATA_TYPE as return_data_type
624
+ FROM INFORMATION_SCHEMA.ROUTINES
625
+ WHERE ROUTINE_NAME = @procedureName
626
+ AND ROUTINE_SCHEMA = @schema
614
627
  `;
615
628
  const routineResult = await request.query(routineQuery);
616
629
  if (routineResult.recordset.length === 0) {
@@ -618,16 +631,15 @@ var SQLServerConnector = class {
618
631
  }
619
632
  const routine = routineResult.recordset[0];
620
633
  const parameterQuery = `
621
- SELECT
622
- PARAMETER_NAME,
623
- PARAMETER_MODE,
624
- DATA_TYPE,
625
- CHARACTER_MAXIMUM_LENGTH,
626
- ORDINAL_POSITION
627
- FROM INFORMATION_SCHEMA.PARAMETERS
628
- WHERE SPECIFIC_NAME = @procedureName
629
- AND SPECIFIC_SCHEMA = @schema
630
- ORDER BY ORDINAL_POSITION
634
+ SELECT PARAMETER_NAME,
635
+ PARAMETER_MODE,
636
+ DATA_TYPE,
637
+ CHARACTER_MAXIMUM_LENGTH,
638
+ ORDINAL_POSITION
639
+ FROM INFORMATION_SCHEMA.PARAMETERS
640
+ WHERE SPECIFIC_NAME = @procedureName
641
+ AND SPECIFIC_SCHEMA = @schema
642
+ ORDER BY ORDINAL_POSITION
631
643
  `;
632
644
  const parameterResult = await request.query(parameterQuery);
633
645
  let parameterList = "";
@@ -638,12 +650,12 @@ var SQLServerConnector = class {
638
650
  }).join(", ");
639
651
  }
640
652
  const definitionQuery = `
641
- SELECT definition
642
- FROM sys.sql_modules sm
643
- JOIN sys.objects o ON sm.object_id = o.object_id
644
- JOIN sys.schemas s ON o.schema_id = s.schema_id
645
- WHERE o.name = @procedureName
646
- AND s.name = @schema
653
+ SELECT definition
654
+ FROM sys.sql_modules sm
655
+ JOIN sys.objects o ON sm.object_id = o.object_id
656
+ JOIN sys.schemas s ON o.schema_id = s.schema_id
657
+ WHERE o.name = @procedureName
658
+ AND s.name = @schema
647
659
  `;
648
660
  const definitionResult = await request.query(definitionQuery);
649
661
  let definition = void 0;
@@ -701,7 +713,7 @@ ConnectorRegistry.register(sqlServerConnector);
701
713
  // src/connectors/sqlite/index.ts
702
714
  import Database from "better-sqlite3";
703
715
  var SQLiteDSNParser = class {
704
- parse(dsn) {
716
+ async parse(dsn) {
705
717
  if (!this.isValidDSN(dsn)) {
706
718
  throw new Error(`Invalid SQLite DSN: ${dsn}`);
707
719
  }
@@ -744,7 +756,7 @@ var SQLiteConnector = class {
744
756
  }
745
757
  // Default to in-memory database
746
758
  async connect(dsn, initScript) {
747
- const config = this.dsnParser.parse(dsn);
759
+ const config = await this.dsnParser.parse(dsn);
748
760
  this.dbPath = config.dbPath;
749
761
  try {
750
762
  this.db = new Database(this.dbPath);
@@ -908,7 +920,7 @@ ConnectorRegistry.register(sqliteConnector);
908
920
  // src/connectors/mysql/index.ts
909
921
  import mysql from "mysql2/promise";
910
922
  var MySQLDSNParser = class {
911
- parse(dsn) {
923
+ async parse(dsn) {
912
924
  if (!this.isValidDSN(dsn)) {
913
925
  throw new Error(`Invalid MySQL DSN: ${dsn}`);
914
926
  }
@@ -920,7 +932,7 @@ var MySQLDSNParser = class {
920
932
  database: url.pathname.substring(1),
921
933
  // Remove leading '/'
922
934
  user: url.username,
923
- password: url.password
935
+ password: url.password ? decodeURIComponent(url.password) : ""
924
936
  };
925
937
  url.searchParams.forEach((value, key) => {
926
938
  if (key === "ssl") {
@@ -953,7 +965,7 @@ var MySQLConnector = class {
953
965
  }
954
966
  async connect(dsn) {
955
967
  try {
956
- const config = this.dsnParser.parse(dsn);
968
+ const config = await this.dsnParser.parse(dsn);
957
969
  this.pool = mysql.createPool(config);
958
970
  const [rows] = await this.pool.query("SELECT 1");
959
971
  console.error("Successfully connected to MySQL database");
@@ -974,11 +986,11 @@ var MySQLConnector = class {
974
986
  }
975
987
  try {
976
988
  const [rows] = await this.pool.query(`
977
- SELECT schema_name
978
- FROM information_schema.schemata
979
- ORDER BY schema_name
989
+ SELECT SCHEMA_NAME
990
+ FROM INFORMATION_SCHEMA.SCHEMATA
991
+ ORDER BY SCHEMA_NAME
980
992
  `);
981
- return rows.map((row) => row.schema_name);
993
+ return rows.map((row) => row.SCHEMA_NAME);
982
994
  } catch (error) {
983
995
  console.error("Error getting schemas:", error);
984
996
  throw error;
@@ -989,15 +1001,15 @@ var MySQLConnector = class {
989
1001
  throw new Error("Not connected to database");
990
1002
  }
991
1003
  try {
992
- const schemaClause = schema ? "WHERE table_schema = ?" : "WHERE table_schema = DATABASE()";
1004
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
993
1005
  const queryParams = schema ? [schema] : [];
994
1006
  const [rows] = await this.pool.query(`
995
- SELECT table_name
996
- FROM information_schema.tables
1007
+ SELECT TABLE_NAME
1008
+ FROM INFORMATION_SCHEMA.TABLES
997
1009
  ${schemaClause}
998
- ORDER BY table_name
1010
+ ORDER BY TABLE_NAME
999
1011
  `, queryParams);
1000
- return rows.map((row) => row.table_name);
1012
+ return rows.map((row) => row.TABLE_NAME);
1001
1013
  } catch (error) {
1002
1014
  console.error("Error getting tables:", error);
1003
1015
  throw error;
@@ -1008,15 +1020,15 @@ var MySQLConnector = class {
1008
1020
  throw new Error("Not connected to database");
1009
1021
  }
1010
1022
  try {
1011
- const schemaClause = schema ? "WHERE table_schema = ?" : "WHERE table_schema = DATABASE()";
1023
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1012
1024
  const queryParams = schema ? [schema, tableName] : [tableName];
1013
1025
  const [rows] = await this.pool.query(`
1014
- SELECT COUNT(*) as count
1015
- FROM information_schema.tables
1026
+ SELECT COUNT(*) AS COUNT
1027
+ FROM INFORMATION_SCHEMA.TABLES
1016
1028
  ${schemaClause}
1017
- AND table_name = ?
1029
+ AND TABLE_NAME = ?
1018
1030
  `, queryParams);
1019
- return rows[0].count > 0;
1031
+ return rows[0].COUNT > 0;
1020
1032
  } catch (error) {
1021
1033
  console.error("Error checking if table exists:", error);
1022
1034
  throw error;
@@ -1036,7 +1048,7 @@ var MySQLConnector = class {
1036
1048
  NON_UNIQUE,
1037
1049
  SEQ_IN_INDEX
1038
1050
  FROM
1039
- information_schema.STATISTICS
1051
+ INFORMATION_SCHEMA.STATISTICS
1040
1052
  WHERE
1041
1053
  ${schemaClause}
1042
1054
  AND TABLE_NAME = ?
@@ -1080,18 +1092,18 @@ var MySQLConnector = class {
1080
1092
  throw new Error("Not connected to database");
1081
1093
  }
1082
1094
  try {
1083
- const schemaClause = schema ? "WHERE table_schema = ?" : "WHERE table_schema = DATABASE()";
1095
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1084
1096
  const queryParams = schema ? [schema, tableName] : [tableName];
1085
1097
  const [rows] = await this.pool.query(`
1086
1098
  SELECT
1087
- column_name,
1088
- data_type,
1089
- is_nullable,
1090
- column_default
1091
- FROM information_schema.columns
1099
+ COLUMN_NAME as column_name,
1100
+ DATA_TYPE as data_type,
1101
+ IS_NULLABLE as is_nullable,
1102
+ COLUMN_DEFAULT as column_default
1103
+ FROM INFORMATION_SCHEMA.COLUMNS
1092
1104
  ${schemaClause}
1093
- AND table_name = ?
1094
- ORDER BY ordinal_position
1105
+ AND TABLE_NAME = ?
1106
+ ORDER BY ORDINAL_POSITION
1095
1107
  `, queryParams);
1096
1108
  return rows;
1097
1109
  } catch (error) {
@@ -1104,15 +1116,15 @@ var MySQLConnector = class {
1104
1116
  throw new Error("Not connected to database");
1105
1117
  }
1106
1118
  try {
1107
- const schemaClause = schema ? "WHERE routine_schema = ?" : "WHERE routine_schema = DATABASE()";
1119
+ const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
1108
1120
  const queryParams = schema ? [schema] : [];
1109
1121
  const [rows] = await this.pool.query(`
1110
- SELECT routine_name
1111
- FROM information_schema.routines
1122
+ SELECT ROUTINE_NAME
1123
+ FROM INFORMATION_SCHEMA.ROUTINES
1112
1124
  ${schemaClause}
1113
- ORDER BY routine_name
1125
+ ORDER BY ROUTINE_NAME
1114
1126
  `, queryParams);
1115
- return rows.map((row) => row.routine_name);
1127
+ return rows.map((row) => row.ROUTINE_NAME);
1116
1128
  } catch (error) {
1117
1129
  console.error("Error getting stored procedures:", error);
1118
1130
  throw error;
@@ -1123,39 +1135,39 @@ var MySQLConnector = class {
1123
1135
  throw new Error("Not connected to database");
1124
1136
  }
1125
1137
  try {
1126
- const schemaClause = schema ? "WHERE r.routine_schema = ?" : "WHERE r.routine_schema = DATABASE()";
1138
+ const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
1127
1139
  const queryParams = schema ? [schema, procedureName] : [procedureName];
1128
1140
  const [rows] = await this.pool.query(`
1129
1141
  SELECT
1130
- r.routine_name AS procedure_name,
1142
+ r.ROUTINE_NAME AS procedure_name,
1131
1143
  CASE
1132
- WHEN r.routine_type = 'PROCEDURE' THEN 'procedure'
1144
+ WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
1133
1145
  ELSE 'function'
1134
1146
  END AS procedure_type,
1135
- LOWER(r.routine_type) AS routine_type,
1136
- r.routine_definition,
1137
- r.dtd_identifier AS return_type,
1147
+ LOWER(r.ROUTINE_TYPE) AS routine_type,
1148
+ r.ROUTINE_DEFINITION,
1149
+ r.DTD_IDENTIFIER AS return_type,
1138
1150
  (
1139
1151
  SELECT GROUP_CONCAT(
1140
- CONCAT(p.parameter_name, ' ', p.parameter_mode, ' ', p.data_type)
1141
- ORDER BY p.ordinal_position
1152
+ CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
1153
+ ORDER BY p.ORDINAL_POSITION
1142
1154
  SEPARATOR ', '
1143
1155
  )
1144
- FROM information_schema.parameters p
1145
- WHERE p.specific_schema = r.routine_schema
1146
- AND p.specific_name = r.routine_name
1147
- AND p.parameter_name IS NOT NULL
1156
+ FROM INFORMATION_SCHEMA.PARAMETERS p
1157
+ WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
1158
+ AND p.SPECIFIC_NAME = r.ROUTINE_NAME
1159
+ AND p.PARAMETER_NAME IS NOT NULL
1148
1160
  ) AS parameter_list
1149
- FROM information_schema.routines r
1161
+ FROM INFORMATION_SCHEMA.ROUTINES r
1150
1162
  ${schemaClause}
1151
- AND r.routine_name = ?
1163
+ AND r.ROUTINE_NAME = ?
1152
1164
  `, queryParams);
1153
1165
  if (rows.length === 0) {
1154
1166
  const schemaName = schema || "current schema";
1155
1167
  throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
1156
1168
  }
1157
1169
  const procedure = rows[0];
1158
- let definition = procedure.routine_definition;
1170
+ let definition = procedure.ROUTINE_DEFINITION;
1159
1171
  try {
1160
1172
  const schemaValue = schema || await this.getCurrentSchema();
1161
1173
  if (procedure.procedure_type === "procedure") {
@@ -1183,15 +1195,15 @@ var MySQLConnector = class {
1183
1195
  }
1184
1196
  if (!definition) {
1185
1197
  const [bodyRows] = await this.pool.query(`
1186
- SELECT routine_definition, routine_body
1187
- FROM information_schema.routines
1188
- WHERE routine_schema = ? AND routine_name = ?
1198
+ SELECT ROUTINE_DEFINITION, ROUTINE_BODY
1199
+ FROM INFORMATION_SCHEMA.ROUTINES
1200
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
1189
1201
  `, [schemaValue, procedureName]);
1190
1202
  if (bodyRows && bodyRows.length > 0) {
1191
- if (bodyRows[0].routine_definition) {
1192
- definition = bodyRows[0].routine_definition;
1193
- } else if (bodyRows[0].routine_body) {
1194
- definition = bodyRows[0].routine_body;
1203
+ if (bodyRows[0].ROUTINE_DEFINITION) {
1204
+ definition = bodyRows[0].ROUTINE_DEFINITION;
1205
+ } else if (bodyRows[0].ROUTINE_BODY) {
1206
+ definition = bodyRows[0].ROUTINE_BODY;
1195
1207
  }
1196
1208
  }
1197
1209
  }
@@ -1214,8 +1226,8 @@ var MySQLConnector = class {
1214
1226
  }
1215
1227
  // Helper method to get current schema (database) name
1216
1228
  async getCurrentSchema() {
1217
- const [rows] = await this.pool.query("SELECT DATABASE() as db");
1218
- return rows[0].db;
1229
+ const [rows] = await this.pool.query("SELECT DATABASE() AS DB");
1230
+ return rows[0].DB;
1219
1231
  }
1220
1232
  async executeQuery(query) {
1221
1233
  if (!this.pool) {
@@ -1247,6 +1259,348 @@ var MySQLConnector = class {
1247
1259
  var mysqlConnector = new MySQLConnector();
1248
1260
  ConnectorRegistry.register(mysqlConnector);
1249
1261
 
1262
+ // src/connectors/mariadb/index.ts
1263
+ import mariadb from "mariadb";
1264
+ var MariadbDSNParser = class {
1265
+ async parse(dsn) {
1266
+ if (!this.isValidDSN(dsn)) {
1267
+ throw new Error(`Invalid MariaDB DSN: ${dsn}`);
1268
+ }
1269
+ try {
1270
+ const url = new URL(dsn);
1271
+ const config = {
1272
+ host: url.hostname,
1273
+ port: url.port ? parseInt(url.port) : 3306,
1274
+ database: url.pathname.substring(1),
1275
+ user: url.username,
1276
+ password: decodeURIComponent(url.password)
1277
+ };
1278
+ url.searchParams.forEach((value, key) => {
1279
+ if (key === "ssl") {
1280
+ config.ssl = value === "true" ? {} : void 0;
1281
+ }
1282
+ });
1283
+ return config;
1284
+ } catch (error) {
1285
+ throw new Error(`Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`);
1286
+ }
1287
+ }
1288
+ getSampleDSN() {
1289
+ return "mariadb://root:password@localhost:3306/db";
1290
+ }
1291
+ isValidDSN(dsn) {
1292
+ try {
1293
+ const url = new URL(dsn);
1294
+ return url.protocol === "mariadb:";
1295
+ } catch (error) {
1296
+ return false;
1297
+ }
1298
+ }
1299
+ };
1300
+ var MariaDBConnector = class {
1301
+ constructor() {
1302
+ this.id = "mariadb";
1303
+ this.name = "MariaDB";
1304
+ this.dsnParser = new MariadbDSNParser();
1305
+ this.pool = null;
1306
+ }
1307
+ async connect(dsn) {
1308
+ try {
1309
+ const config = await this.dsnParser.parse(dsn);
1310
+ this.pool = mariadb.createPool(config);
1311
+ console.error("Testing connection to MariaDB...");
1312
+ const [rows] = await this.pool.query("SELECT 1");
1313
+ console.error("Successfully connected to MariaDB database");
1314
+ } catch (err) {
1315
+ console.error("Failed to connect to MariaDB database:", err);
1316
+ throw err;
1317
+ }
1318
+ }
1319
+ async disconnect() {
1320
+ if (this.pool) {
1321
+ await this.pool.end();
1322
+ this.pool = null;
1323
+ }
1324
+ }
1325
+ async getSchemas() {
1326
+ if (!this.pool) {
1327
+ throw new Error("Not connected to database");
1328
+ }
1329
+ try {
1330
+ const [rows] = await this.pool.query(`
1331
+ SELECT SCHEMA_NAME
1332
+ FROM INFORMATION_SCHEMA.SCHEMATA
1333
+ ORDER BY SCHEMA_NAME
1334
+ `);
1335
+ return rows.map((row) => row.SCHEMA_NAME);
1336
+ } catch (error) {
1337
+ console.error("Error getting schemas:", error);
1338
+ throw error;
1339
+ }
1340
+ }
1341
+ async getTables(schema) {
1342
+ if (!this.pool) {
1343
+ throw new Error("Not connected to database");
1344
+ }
1345
+ try {
1346
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1347
+ const queryParams = schema ? [schema] : [];
1348
+ const [rows] = await this.pool.query(`
1349
+ SELECT TABLE_NAME
1350
+ FROM INFORMATION_SCHEMA.TABLES
1351
+ ${schemaClause}
1352
+ ORDER BY TABLE_NAME
1353
+ `, queryParams);
1354
+ return rows.map((row) => row.TABLE_NAME);
1355
+ } catch (error) {
1356
+ console.error("Error getting tables:", error);
1357
+ throw error;
1358
+ }
1359
+ }
1360
+ async tableExists(tableName, schema) {
1361
+ if (!this.pool) {
1362
+ throw new Error("Not connected to database");
1363
+ }
1364
+ try {
1365
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1366
+ const queryParams = schema ? [schema, tableName] : [tableName];
1367
+ const [rows] = await this.pool.query(`
1368
+ SELECT COUNT(*) AS COUNT
1369
+ FROM INFORMATION_SCHEMA.TABLES
1370
+ ${schemaClause}
1371
+ AND TABLE_NAME = ?
1372
+ `, queryParams);
1373
+ return rows[0].COUNT > 0;
1374
+ } catch (error) {
1375
+ console.error("Error checking if table exists:", error);
1376
+ throw error;
1377
+ }
1378
+ }
1379
+ async getTableIndexes(tableName, schema) {
1380
+ if (!this.pool) {
1381
+ throw new Error("Not connected to database");
1382
+ }
1383
+ try {
1384
+ const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
1385
+ const queryParams = schema ? [schema, tableName] : [tableName];
1386
+ const [indexRows] = await this.pool.query(`
1387
+ SELECT
1388
+ INDEX_NAME,
1389
+ COLUMN_NAME,
1390
+ NON_UNIQUE,
1391
+ SEQ_IN_INDEX
1392
+ FROM
1393
+ INFORMATION_SCHEMA.STATISTICS
1394
+ WHERE
1395
+ ${schemaClause}
1396
+ AND TABLE_NAME = ?
1397
+ ORDER BY
1398
+ INDEX_NAME,
1399
+ SEQ_IN_INDEX
1400
+ `, queryParams);
1401
+ const indexMap = /* @__PURE__ */ new Map();
1402
+ for (const row of indexRows) {
1403
+ const indexName = row.INDEX_NAME;
1404
+ const columnName = row.COLUMN_NAME;
1405
+ const isUnique = row.NON_UNIQUE === 0;
1406
+ const isPrimary = indexName === "PRIMARY";
1407
+ if (!indexMap.has(indexName)) {
1408
+ indexMap.set(indexName, {
1409
+ columns: [],
1410
+ is_unique: isUnique,
1411
+ is_primary: isPrimary
1412
+ });
1413
+ }
1414
+ const indexInfo = indexMap.get(indexName);
1415
+ indexInfo.columns.push(columnName);
1416
+ }
1417
+ const results = [];
1418
+ indexMap.forEach((indexInfo, indexName) => {
1419
+ results.push({
1420
+ index_name: indexName,
1421
+ column_names: indexInfo.columns,
1422
+ is_unique: indexInfo.is_unique,
1423
+ is_primary: indexInfo.is_primary
1424
+ });
1425
+ });
1426
+ return results;
1427
+ } catch (error) {
1428
+ console.error("Error getting table indexes:", error);
1429
+ throw error;
1430
+ }
1431
+ }
1432
+ async getTableSchema(tableName, schema) {
1433
+ if (!this.pool) {
1434
+ throw new Error("Not connected to database");
1435
+ }
1436
+ try {
1437
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1438
+ const queryParams = schema ? [schema, tableName] : [tableName];
1439
+ const [rows] = await this.pool.query(`
1440
+ SELECT
1441
+ COLUMN_NAME as column_name,
1442
+ DATA_TYPE as data_type,
1443
+ IS_NULLABLE as is_nullable,
1444
+ COLUMN_DEFAULT as column_default
1445
+ FROM INFORMATION_SCHEMA.COLUMNS
1446
+ ${schemaClause}
1447
+ AND TABLE_NAME = ?
1448
+ ORDER BY ORDINAL_POSITION
1449
+ `, queryParams);
1450
+ return rows;
1451
+ } catch (error) {
1452
+ console.error("Error getting table schema:", error);
1453
+ throw error;
1454
+ }
1455
+ }
1456
+ async getStoredProcedures(schema) {
1457
+ if (!this.pool) {
1458
+ throw new Error("Not connected to database");
1459
+ }
1460
+ try {
1461
+ const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
1462
+ const queryParams = schema ? [schema] : [];
1463
+ const [rows] = await this.pool.query(`
1464
+ SELECT ROUTINE_NAME
1465
+ FROM INFORMATION_SCHEMA.ROUTINES
1466
+ ${schemaClause}
1467
+ ORDER BY ROUTINE_NAME
1468
+ `, queryParams);
1469
+ return rows.map((row) => row.ROUTINE_NAME);
1470
+ } catch (error) {
1471
+ console.error("Error getting stored procedures:", error);
1472
+ throw error;
1473
+ }
1474
+ }
1475
+ async getStoredProcedureDetail(procedureName, schema) {
1476
+ if (!this.pool) {
1477
+ throw new Error("Not connected to database");
1478
+ }
1479
+ try {
1480
+ const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
1481
+ const queryParams = schema ? [schema, procedureName] : [procedureName];
1482
+ const [rows] = await this.pool.query(`
1483
+ SELECT
1484
+ r.ROUTINE_NAME AS procedure_name,
1485
+ CASE
1486
+ WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
1487
+ ELSE 'function'
1488
+ END AS procedure_type,
1489
+ LOWER(r.ROUTINE_TYPE) AS routine_type,
1490
+ r.ROUTINE_DEFINITION,
1491
+ r.DTD_IDENTIFIER AS return_type,
1492
+ (
1493
+ SELECT GROUP_CONCAT(
1494
+ CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
1495
+ ORDER BY p.ORDINAL_POSITION
1496
+ SEPARATOR ', '
1497
+ )
1498
+ FROM INFORMATION_SCHEMA.PARAMETERS p
1499
+ WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
1500
+ AND p.SPECIFIC_NAME = r.ROUTINE_NAME
1501
+ AND p.PARAMETER_NAME IS NOT NULL
1502
+ ) AS parameter_list
1503
+ FROM INFORMATION_SCHEMA.ROUTINES r
1504
+ ${schemaClause}
1505
+ AND r.ROUTINE_NAME = ?
1506
+ `, queryParams);
1507
+ if (rows.length === 0) {
1508
+ const schemaName = schema || "current schema";
1509
+ throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
1510
+ }
1511
+ const procedure = rows[0];
1512
+ let definition = procedure.ROUTINE_DEFINITION;
1513
+ try {
1514
+ const schemaValue = schema || await this.getCurrentSchema();
1515
+ if (procedure.procedure_type === "procedure") {
1516
+ try {
1517
+ const [defRows] = await this.pool.query(`
1518
+ SHOW CREATE PROCEDURE ${schemaValue}.${procedureName}
1519
+ `);
1520
+ if (defRows && defRows.length > 0) {
1521
+ definition = defRows[0]["Create Procedure"];
1522
+ }
1523
+ } catch (err) {
1524
+ console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
1525
+ }
1526
+ } else {
1527
+ try {
1528
+ const [defRows] = await this.pool.query(`
1529
+ SHOW CREATE FUNCTION ${schemaValue}.${procedureName}
1530
+ `);
1531
+ if (defRows && defRows.length > 0) {
1532
+ definition = defRows[0]["Create Function"];
1533
+ }
1534
+ } catch (innerErr) {
1535
+ console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
1536
+ }
1537
+ }
1538
+ if (!definition) {
1539
+ const [bodyRows] = await this.pool.query(`
1540
+ SELECT ROUTINE_DEFINITION, ROUTINE_BODY
1541
+ FROM INFORMATION_SCHEMA.ROUTINES
1542
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
1543
+ `, [schemaValue, procedureName]);
1544
+ if (bodyRows && bodyRows.length > 0) {
1545
+ if (bodyRows[0].ROUTINE_DEFINITION) {
1546
+ definition = bodyRows[0].ROUTINE_DEFINITION;
1547
+ } else if (bodyRows[0].ROUTINE_BODY) {
1548
+ definition = bodyRows[0].ROUTINE_BODY;
1549
+ }
1550
+ }
1551
+ }
1552
+ } catch (error) {
1553
+ console.error(`Error getting procedure/function details: ${error}`);
1554
+ }
1555
+ return {
1556
+ procedure_name: procedure.procedure_name,
1557
+ procedure_type: procedure.procedure_type,
1558
+ language: "sql",
1559
+ // MariaDB procedures are generally in SQL
1560
+ parameter_list: procedure.parameter_list || "",
1561
+ return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
1562
+ definition: definition || void 0
1563
+ };
1564
+ } catch (error) {
1565
+ console.error("Error getting stored procedure detail:", error);
1566
+ throw error;
1567
+ }
1568
+ }
1569
+ // Helper method to get current schema (database) name
1570
+ async getCurrentSchema() {
1571
+ const [rows] = await this.pool.query("SELECT DATABASE() AS DB");
1572
+ return rows[0].DB;
1573
+ }
1574
+ async executeQuery(query) {
1575
+ if (!this.pool) {
1576
+ throw new Error("Not connected to database");
1577
+ }
1578
+ const safetyCheck = this.validateQuery(query);
1579
+ if (!safetyCheck.isValid) {
1580
+ throw new Error(safetyCheck.message || "Query validation failed");
1581
+ }
1582
+ try {
1583
+ const [rows, fields] = await this.pool.query(query);
1584
+ return { rows, fields };
1585
+ } catch (error) {
1586
+ console.error("Error executing query:", error);
1587
+ throw error;
1588
+ }
1589
+ }
1590
+ validateQuery(query) {
1591
+ const normalizedQuery = query.trim().toLowerCase();
1592
+ if (!normalizedQuery.startsWith("select")) {
1593
+ return {
1594
+ isValid: false,
1595
+ message: "Only SELECT queries are allowed for security reasons."
1596
+ };
1597
+ }
1598
+ return { isValid: true };
1599
+ }
1600
+ };
1601
+ var mariadbConnector = new MariaDBConnector();
1602
+ ConnectorRegistry.register(mariadbConnector);
1603
+
1250
1604
  // src/server.ts
1251
1605
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
1252
1606
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
@@ -1437,6 +1791,17 @@ function resolvePort() {
1437
1791
  }
1438
1792
  return { port: 8080, source: "default" };
1439
1793
  }
1794
+ function redactDSN(dsn) {
1795
+ try {
1796
+ const url = new URL(dsn);
1797
+ if (url.password) {
1798
+ url.password = "*******";
1799
+ }
1800
+ return url.toString();
1801
+ } catch (error) {
1802
+ return dsn.replace(/\/\/([^:]+):([^@]+)@/, "//$1:***@");
1803
+ }
1804
+ }
1440
1805
 
1441
1806
  // src/config/demo-loader.ts
1442
1807
  import fs2 from "fs";
@@ -1595,7 +1960,7 @@ async function tablesResourceHandler(uri, variables, _extra) {
1595
1960
  }
1596
1961
 
1597
1962
  // src/resources/schema.ts
1598
- async function schemaResourceHandler(uri, variables, _extra) {
1963
+ async function tableStructureResourceHandler(uri, variables, _extra) {
1599
1964
  const connector = ConnectorManager.getCurrentConnector();
1600
1965
  const tableName = Array.isArray(variables.tableName) ? variables.tableName[0] : variables.tableName;
1601
1966
  const schemaName = variables.schemaName ? Array.isArray(variables.schemaName) ? variables.schemaName[0] : variables.schemaName : void 0;
@@ -1796,7 +2161,7 @@ function registerResources(server) {
1796
2161
  server.resource(
1797
2162
  "table_structure_in_schema",
1798
2163
  new ResourceTemplate("db://schemas/{schemaName}/tables/{tableName}", { list: void 0 }),
1799
- schemaResourceHandler
2164
+ tableStructureResourceHandler
1800
2165
  );
1801
2166
  server.resource(
1802
2167
  "indexes_in_table",
@@ -1846,7 +2211,6 @@ async function runQueryToolHandler({ query }, _extra) {
1846
2211
 
1847
2212
  // src/tools/list-connectors.ts
1848
2213
  async function listConnectorsToolHandler(_args, _extra) {
1849
- const connectors = ConnectorManager.getAvailableConnectors();
1850
2214
  const samples = ConnectorRegistry.getAllSampleDSNs();
1851
2215
  let activeConnectorId = null;
1852
2216
  try {
@@ -2169,6 +2533,10 @@ ${possibleTableMatches.join("\n")}`
2169
2533
  "EXPLANATION_ERROR"
2170
2534
  );
2171
2535
  }
2536
+ return formatPromptErrorResponse(
2537
+ `Unable to process request for schema: ${schema}, table: ${table}`,
2538
+ "UNHANDLED_REQUEST"
2539
+ );
2172
2540
  }
2173
2541
  function determineTablePurpose(tableName, columns) {
2174
2542
  const lowerTableName = tableName.toLowerCase();
@@ -2333,7 +2701,7 @@ See documentation for more details on configuring database connections.
2333
2701
  registerTools(server);
2334
2702
  registerPrompts(server);
2335
2703
  const connectorManager = new ConnectorManager();
2336
- console.error(`Connecting with DSN: ${dsnData.dsn}`);
2704
+ console.error(`Connecting with DSN: ${redactDSN(dsnData.dsn)}`);
2337
2705
  console.error(`DSN source: ${dsnData.source}`);
2338
2706
  if (dsnData.isDemo) {
2339
2707
  console.error("Running in demo mode with sample employee database");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Universal Database MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -16,11 +16,13 @@
16
16
  "author": "",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
+ "@azure/identity": "^4.8.0",
19
20
  "@modelcontextprotocol/sdk": "^1.6.1",
20
21
  "better-sqlite3": "^11.9.0",
21
22
  "dotenv": "^16.4.7",
22
23
  "express": "^4.18.2",
23
24
  "mssql": "^11.0.1",
25
+ "mariadb": "^3.4.0",
24
26
  "mysql2": "^3.13.0",
25
27
  "pg": "^8.13.3",
26
28
  "zod": "^3.24.2"