@bytebase/dbhub 0.4.11 → 0.6.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.
- package/README.md +43 -41
- package/dist/index.js +125 -28
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ DBHub is a universal database gateway implementing the Model Context Protocol (M
|
|
|
28
28
|
MCP Clients MCP Server Databases
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
## Demo
|
|
31
|
+
## Demo HTTP Endpoint
|
|
32
32
|
|
|
33
|
-
https://demo.dbhub.ai/
|
|
33
|
+
https://demo.dbhub.ai/message connects a [sample employee database](https://github.com/bytebase/employee-sample-database). You can point Cursor or MCP Inspector to it to see it in action.
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|
|
|
@@ -49,10 +49,10 @@ https://demo.dbhub.ai/sse connects a [sample employee database](https://github.c
|
|
|
49
49
|
|
|
50
50
|
### Database Tools
|
|
51
51
|
|
|
52
|
-
| Tool | Command Name | PostgreSQL | MySQL | MariaDB | SQL Server | SQLite | Oracle |
|
|
53
|
-
| --------------- | ----------------- | :--------: | :---: | :-----: | :--------: | ------ | :----: |
|
|
54
|
-
| Execute SQL | `execute_sql` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
55
|
-
| List Connectors | `list_connectors` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
52
|
+
| Tool | Command Name | Description | PostgreSQL | MySQL | MariaDB | SQL Server | SQLite | Oracle |
|
|
53
|
+
| --------------- | ----------------- | ------------------------------------------------------------------- | :--------: | :---: | :-----: | :--------: | ------ | :----: |
|
|
54
|
+
| Execute SQL | `execute_sql` | Execute single or multiple SQL statements (separated by semicolons) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
55
|
+
| List Connectors | `list_connectors` | List all available database connectors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
56
56
|
|
|
57
57
|
### Prompt Capabilities
|
|
58
58
|
|
|
@@ -71,7 +71,7 @@ docker run --rm --init \
|
|
|
71
71
|
--name dbhub \
|
|
72
72
|
--publish 8080:8080 \
|
|
73
73
|
bytebase/dbhub \
|
|
74
|
-
--transport
|
|
74
|
+
--transport http \
|
|
75
75
|
--port 8080 \
|
|
76
76
|
--dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
|
|
77
77
|
```
|
|
@@ -82,7 +82,7 @@ docker run --rm --init \
|
|
|
82
82
|
--name dbhub \
|
|
83
83
|
--publish 8080:8080 \
|
|
84
84
|
bytebase/dbhub \
|
|
85
|
-
--transport
|
|
85
|
+
--transport http \
|
|
86
86
|
--port 8080 \
|
|
87
87
|
--demo
|
|
88
88
|
```
|
|
@@ -93,18 +93,18 @@ docker run --rm --init \
|
|
|
93
93
|
--name dbhub \
|
|
94
94
|
--publish 8080:8080 \
|
|
95
95
|
bytebase/dbhub \
|
|
96
|
-
--transport
|
|
96
|
+
--transport http \
|
|
97
97
|
--port 8080 \
|
|
98
98
|
--dsn "oracle://username:password@localhost:1521/service_name"
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
# Oracle example with thick mode for connecting to 11g or older
|
|
102
|
+
# Oracle example with thick mode for connecting to 11g or older
|
|
103
103
|
docker run --rm --init \
|
|
104
104
|
--name dbhub \
|
|
105
105
|
--publish 8080:8080 \
|
|
106
106
|
bytebase/dbhub-oracle-thick \
|
|
107
|
-
--transport
|
|
107
|
+
--transport http \
|
|
108
108
|
--port 8080 \
|
|
109
109
|
--dsn "oracle://username:password@localhost:1521/service_name"
|
|
110
110
|
```
|
|
@@ -113,12 +113,12 @@ docker run --rm --init \
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
# PostgreSQL example
|
|
116
|
-
npx @bytebase/dbhub --transport
|
|
116
|
+
npx @bytebase/dbhub --transport http --port 8080 --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
120
|
# Demo mode with sample employee database
|
|
121
|
-
npx @bytebase/dbhub --transport
|
|
121
|
+
npx @bytebase/dbhub --transport http --port 8080 --demo
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
> Note: The demo mode includes a bundled SQLite sample "employee" database with tables for employees, departments, salaries, and more.
|
|
@@ -170,7 +170,7 @@ npx @bytebase/dbhub --transport sse --port 8080 --demo
|
|
|
170
170
|
|
|
171
171
|

|
|
172
172
|
|
|
173
|
-
- Cursor supports both `stdio` and `
|
|
173
|
+
- Cursor supports both `stdio` and `http`.
|
|
174
174
|
- Follow [Cursor MCP guide](https://docs.cursor.com/context/model-context-protocol) and make sure to use [Agent](https://docs.cursor.com/chat/agent) mode.
|
|
175
175
|
|
|
176
176
|
## Usage
|
|
@@ -179,14 +179,14 @@ npx @bytebase/dbhub --transport sse --port 8080 --demo
|
|
|
179
179
|
|
|
180
180
|
You can specify the SSL mode using the `sslmode` parameter in your DSN string:
|
|
181
181
|
|
|
182
|
-
| Database | `sslmode=disable` | `sslmode=require` |
|
|
183
|
-
|
|
184
|
-
| PostgreSQL |
|
|
185
|
-
| MySQL |
|
|
186
|
-
| MariaDB |
|
|
187
|
-
| SQL Server |
|
|
188
|
-
| Oracle |
|
|
189
|
-
| SQLite |
|
|
182
|
+
| Database | `sslmode=disable` | `sslmode=require` | Default SSL Behavior |
|
|
183
|
+
| ---------- | :---------------: | :---------------: | :----------------------------: |
|
|
184
|
+
| PostgreSQL | ✅ | ✅ | Certificate verification |
|
|
185
|
+
| MySQL | ✅ | ✅ | Certificate verification |
|
|
186
|
+
| MariaDB | ✅ | ✅ | Certificate verification |
|
|
187
|
+
| SQL Server | ✅ | ✅ | Certificate verification |
|
|
188
|
+
| Oracle | ✅ | ✅ | N/A (use Oracle client config) |
|
|
189
|
+
| SQLite | ❌ | ❌ | N/A (file-based) |
|
|
190
190
|
|
|
191
191
|
**SSL Mode Options:**
|
|
192
192
|
|
|
@@ -196,6 +196,7 @@ You can specify the SSL mode using the `sslmode` parameter in your DSN string:
|
|
|
196
196
|
Without specifying `sslmode`, most databases default to certificate verification, which provides the highest level of security.
|
|
197
197
|
|
|
198
198
|
Example usage:
|
|
199
|
+
|
|
199
200
|
```bash
|
|
200
201
|
# Disable SSL
|
|
201
202
|
postgres://user:password@localhost:5432/dbname?sslmode=disable
|
|
@@ -255,14 +256,14 @@ For real databases, a Database Source Name (DSN) is required. You can provide th
|
|
|
255
256
|
|
|
256
257
|
DBHub supports the following database connection string formats:
|
|
257
258
|
|
|
258
|
-
| Database | DSN Format | Example
|
|
259
|
-
| ---------- | --------------------------------------------------------- |
|
|
260
|
-
| MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname?sslmode=disable`
|
|
261
|
-
| MariaDB | `mariadb://[user]:[password]@[host]:[port]/[database]` | `mariadb://user:password@localhost:3306/dbname?sslmode=disable`
|
|
262
|
-
| PostgreSQL | `postgres://[user]:[password]@[host]:[port]/[database]` | `postgres://user:password@localhost:5432/dbname?sslmode=disable`
|
|
263
|
-
| SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname?sslmode=disable`
|
|
264
|
-
| SQLite | `sqlite:///[path/to/file]` or `sqlite:///:memory:`
|
|
265
|
-
| Oracle | `oracle://[user]:[password]@[host]:[port]/[service_name]` | `oracle://username:password@localhost:1521/service_name?sslmode=disable`
|
|
259
|
+
| Database | DSN Format | Example |
|
|
260
|
+
| ---------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
261
|
+
| MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname?sslmode=disable` |
|
|
262
|
+
| MariaDB | `mariadb://[user]:[password]@[host]:[port]/[database]` | `mariadb://user:password@localhost:3306/dbname?sslmode=disable` |
|
|
263
|
+
| PostgreSQL | `postgres://[user]:[password]@[host]:[port]/[database]` | `postgres://user:password@localhost:5432/dbname?sslmode=disable` |
|
|
264
|
+
| SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname?sslmode=disable` |
|
|
265
|
+
| SQLite | `sqlite:///[path/to/file]` or `sqlite:///:memory:` | `sqlite:///path/to/database.db`, `sqlite:C:/Users/YourName/data/database.db (windows)` or `sqlite:///:memory:` |
|
|
266
|
+
| Oracle | `oracle://[user]:[password]@[host]:[port]/[service_name]` | `oracle://username:password@localhost:1521/service_name?sslmode=disable` |
|
|
266
267
|
|
|
267
268
|
#### Oracle
|
|
268
269
|
|
|
@@ -301,9 +302,9 @@ Extra query parameters:
|
|
|
301
302
|
npx @bytebase/dbhub --transport stdio --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
|
|
302
303
|
```
|
|
303
304
|
|
|
304
|
-
- **
|
|
305
|
+
- **http** - for browser and network clients:
|
|
305
306
|
```bash
|
|
306
|
-
npx @bytebase/dbhub --transport
|
|
307
|
+
npx @bytebase/dbhub --transport http --port 5678 --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
|
|
307
308
|
```
|
|
308
309
|
|
|
309
310
|
### Command line options
|
|
@@ -312,8 +313,8 @@ Extra query parameters:
|
|
|
312
313
|
| --------- | --------------------------------------------------------------- | ---------------------------- |
|
|
313
314
|
| demo | Run in demo mode with sample employee database | `false` |
|
|
314
315
|
| dsn | Database connection string | Required if not in demo mode |
|
|
315
|
-
| transport | Transport mode: `stdio` or `
|
|
316
|
-
| port | HTTP server port (only applicable when using `--transport=
|
|
316
|
+
| transport | Transport mode: `stdio` or `http` | `stdio` |
|
|
317
|
+
| port | HTTP server port (only applicable when using `--transport=http`) | `8080` |
|
|
317
318
|
| readonly | Restrict SQL execution to read-only operations | `false` |
|
|
318
319
|
|
|
319
320
|
The demo mode uses an in-memory SQLite database loaded with the [sample employee database](https://github.com/bytebase/dbhub/tree/main/resources/employee-sqlite) that includes tables for employees, departments, titles, salaries, department employees, and department managers. The sample database includes SQL scripts for table creation, data loading, and testing.
|
|
@@ -340,16 +341,17 @@ The demo mode uses an in-memory SQLite database loaded with the [sample employee
|
|
|
340
341
|
|
|
341
342
|
### Testing
|
|
342
343
|
|
|
343
|
-
The project uses Vitest for testing:
|
|
344
|
+
The project uses Vitest for comprehensive unit testing:
|
|
344
345
|
|
|
345
|
-
- Run tests
|
|
346
|
-
- Run tests in watch mode
|
|
346
|
+
- **Run all tests**: `pnpm test`
|
|
347
|
+
- **Run tests in watch mode**: `pnpm test:watch`
|
|
347
348
|
|
|
348
349
|
#### Pre-commit Hooks (for Developers)
|
|
349
350
|
|
|
350
351
|
The project includes pre-commit hooks to run tests automatically before each commit:
|
|
351
352
|
|
|
352
353
|
1. After cloning the repository, set up the pre-commit hooks:
|
|
354
|
+
|
|
353
355
|
```bash
|
|
354
356
|
./scripts/setup-husky.sh
|
|
355
357
|
```
|
|
@@ -365,17 +367,17 @@ The project includes pre-commit hooks to run tests automatically before each com
|
|
|
365
367
|
TRANSPORT=stdio DSN="postgres://user:password@localhost:5432/dbname?sslmode=disable" npx @modelcontextprotocol/inspector node /path/to/dbhub/dist/index.js
|
|
366
368
|
```
|
|
367
369
|
|
|
368
|
-
####
|
|
370
|
+
#### HTTP
|
|
369
371
|
|
|
370
372
|
```bash
|
|
371
|
-
# Start DBHub with
|
|
372
|
-
pnpm dev --transport=
|
|
373
|
+
# Start DBHub with HTTP transport
|
|
374
|
+
pnpm dev --transport=http --port=8080
|
|
373
375
|
|
|
374
376
|
# Start the MCP Inspector in another terminal
|
|
375
377
|
npx @modelcontextprotocol/inspector
|
|
376
378
|
```
|
|
377
379
|
|
|
378
|
-
Connect to the DBHub server `/
|
|
380
|
+
Connect to the DBHub server `/message` endpoint
|
|
379
381
|
|
|
380
382
|
## Contributors
|
|
381
383
|
|
package/dist/index.js
CHANGED
|
@@ -457,7 +457,26 @@ var PostgresConnector = class {
|
|
|
457
457
|
}
|
|
458
458
|
const client = await this.pool.connect();
|
|
459
459
|
try {
|
|
460
|
-
|
|
460
|
+
const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
461
|
+
if (statements.length === 1) {
|
|
462
|
+
return await client.query(statements[0]);
|
|
463
|
+
} else {
|
|
464
|
+
let allRows = [];
|
|
465
|
+
await client.query("BEGIN");
|
|
466
|
+
try {
|
|
467
|
+
for (const statement of statements) {
|
|
468
|
+
const result = await client.query(statement);
|
|
469
|
+
if (result.rows && result.rows.length > 0) {
|
|
470
|
+
allRows.push(...result.rows);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
await client.query("COMMIT");
|
|
474
|
+
} catch (error) {
|
|
475
|
+
await client.query("ROLLBACK");
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
return { rows: allRows };
|
|
479
|
+
}
|
|
461
480
|
} finally {
|
|
462
481
|
client.release();
|
|
463
482
|
}
|
|
@@ -1004,8 +1023,31 @@ var SQLiteConnector = class {
|
|
|
1004
1023
|
throw new Error("Not connected to SQLite database");
|
|
1005
1024
|
}
|
|
1006
1025
|
try {
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1026
|
+
const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
1027
|
+
if (statements.length === 1) {
|
|
1028
|
+
const rows = this.db.prepare(statements[0]).all();
|
|
1029
|
+
return { rows };
|
|
1030
|
+
} else {
|
|
1031
|
+
const readStatements = [];
|
|
1032
|
+
const writeStatements = [];
|
|
1033
|
+
for (const statement of statements) {
|
|
1034
|
+
const trimmedStatement = statement.toLowerCase().trim();
|
|
1035
|
+
if (trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma")) {
|
|
1036
|
+
readStatements.push(statement);
|
|
1037
|
+
} else {
|
|
1038
|
+
writeStatements.push(statement);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (writeStatements.length > 0) {
|
|
1042
|
+
this.db.exec(writeStatements.join("; "));
|
|
1043
|
+
}
|
|
1044
|
+
let allRows = [];
|
|
1045
|
+
for (const statement of readStatements) {
|
|
1046
|
+
const result = this.db.prepare(statement).all();
|
|
1047
|
+
allRows.push(...result);
|
|
1048
|
+
}
|
|
1049
|
+
return { rows: allRows };
|
|
1050
|
+
}
|
|
1009
1051
|
} catch (error) {
|
|
1010
1052
|
throw error;
|
|
1011
1053
|
}
|
|
@@ -1029,7 +1071,9 @@ var MySQLDSNParser = class {
|
|
|
1029
1071
|
database: url.pathname ? url.pathname.substring(1) : "",
|
|
1030
1072
|
// Remove leading '/' if exists
|
|
1031
1073
|
user: url.username,
|
|
1032
|
-
password: url.password
|
|
1074
|
+
password: url.password,
|
|
1075
|
+
multipleStatements: true
|
|
1076
|
+
// Enable native multi-statement support
|
|
1033
1077
|
};
|
|
1034
1078
|
url.forEachSearchParam((value, key) => {
|
|
1035
1079
|
if (key === "sslmode") {
|
|
@@ -1359,8 +1403,19 @@ var MySQLConnector = class {
|
|
|
1359
1403
|
throw new Error("Not connected to database");
|
|
1360
1404
|
}
|
|
1361
1405
|
try {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1406
|
+
const results = await this.pool.query(sql2);
|
|
1407
|
+
const [firstResult] = results;
|
|
1408
|
+
if (Array.isArray(firstResult) && firstResult.length > 0 && Array.isArray(firstResult[0]) && firstResult[0].length === 2) {
|
|
1409
|
+
let allRows = [];
|
|
1410
|
+
for (const [rows, _fields] of firstResult) {
|
|
1411
|
+
if (Array.isArray(rows)) {
|
|
1412
|
+
allRows.push(...rows);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return { rows: allRows };
|
|
1416
|
+
} else {
|
|
1417
|
+
return { rows: Array.isArray(firstResult) ? firstResult : [] };
|
|
1418
|
+
}
|
|
1364
1419
|
} catch (error) {
|
|
1365
1420
|
console.error("Error executing query:", error);
|
|
1366
1421
|
throw error;
|
|
@@ -1385,7 +1440,9 @@ var MariadbDSNParser = class {
|
|
|
1385
1440
|
database: url.pathname ? url.pathname.substring(1) : "",
|
|
1386
1441
|
// Remove leading '/' if exists
|
|
1387
1442
|
user: url.username,
|
|
1388
|
-
password: url.password
|
|
1443
|
+
password: url.password,
|
|
1444
|
+
multipleStatements: true
|
|
1445
|
+
// Enable native multi-statement support
|
|
1389
1446
|
};
|
|
1390
1447
|
url.forEachSearchParam((value, key) => {
|
|
1391
1448
|
if (key === "sslmode") {
|
|
@@ -1716,8 +1773,19 @@ var MariaDBConnector = class {
|
|
|
1716
1773
|
throw new Error("Not connected to database");
|
|
1717
1774
|
}
|
|
1718
1775
|
try {
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1776
|
+
const results = await this.pool.query(sql2);
|
|
1777
|
+
const [firstResult] = results;
|
|
1778
|
+
if (Array.isArray(firstResult) && firstResult.length > 0 && Array.isArray(firstResult[0]) && firstResult[0].length === 2) {
|
|
1779
|
+
let allRows = [];
|
|
1780
|
+
for (const [rows, _fields] of firstResult) {
|
|
1781
|
+
if (Array.isArray(rows)) {
|
|
1782
|
+
allRows.push(...rows);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return { rows: allRows };
|
|
1786
|
+
} else {
|
|
1787
|
+
return { rows: Array.isArray(firstResult) ? firstResult : [] };
|
|
1788
|
+
}
|
|
1721
1789
|
} catch (error) {
|
|
1722
1790
|
console.error("Error executing query:", error);
|
|
1723
1791
|
throw error;
|
|
@@ -2228,7 +2296,7 @@ ConnectorRegistry.register(new OracleConnector());
|
|
|
2228
2296
|
|
|
2229
2297
|
// src/server.ts
|
|
2230
2298
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2231
|
-
import {
|
|
2299
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2232
2300
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2233
2301
|
import express from "express";
|
|
2234
2302
|
import path3 from "path";
|
|
@@ -2405,11 +2473,11 @@ function resolveDSN() {
|
|
|
2405
2473
|
function resolveTransport() {
|
|
2406
2474
|
const args = parseCommandLineArgs();
|
|
2407
2475
|
if (args.transport) {
|
|
2408
|
-
const type = args.transport === "
|
|
2476
|
+
const type = args.transport === "http" ? "http" : "stdio";
|
|
2409
2477
|
return { type, source: "command line argument" };
|
|
2410
2478
|
}
|
|
2411
2479
|
if (process.env.TRANSPORT) {
|
|
2412
|
-
const type = process.env.TRANSPORT === "
|
|
2480
|
+
const type = process.env.TRANSPORT === "http" ? "http" : "stdio";
|
|
2413
2481
|
return { type, source: "environment variable" };
|
|
2414
2482
|
}
|
|
2415
2483
|
return { type: "stdio", source: "default" };
|
|
@@ -2836,18 +2904,25 @@ var allowedKeywords = {
|
|
|
2836
2904
|
|
|
2837
2905
|
// src/tools/execute-sql.ts
|
|
2838
2906
|
var executeSqlSchema = {
|
|
2839
|
-
sql: z.string().describe("SQL query to execute (
|
|
2907
|
+
sql: z.string().describe("SQL query or multiple SQL statements to execute (separated by semicolons)")
|
|
2840
2908
|
};
|
|
2909
|
+
function splitSQLStatements(sql2) {
|
|
2910
|
+
return sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
2911
|
+
}
|
|
2841
2912
|
function isReadOnlySQL(sql2, connectorType) {
|
|
2842
2913
|
const normalizedSQL = sql2.trim().toLowerCase();
|
|
2843
2914
|
const firstWord = normalizedSQL.split(/\s+/)[0];
|
|
2844
2915
|
const keywordList = allowedKeywords[connectorType] || allowedKeywords.default || [];
|
|
2845
2916
|
return keywordList.includes(firstWord);
|
|
2846
2917
|
}
|
|
2918
|
+
function areAllStatementsReadOnly(sql2, connectorType) {
|
|
2919
|
+
const statements = splitSQLStatements(sql2);
|
|
2920
|
+
return statements.every((statement) => isReadOnlySQL(statement, connectorType));
|
|
2921
|
+
}
|
|
2847
2922
|
async function executeSqlToolHandler({ sql: sql2 }, _extra) {
|
|
2848
2923
|
const connector = ConnectorManager.getCurrentConnector();
|
|
2849
2924
|
try {
|
|
2850
|
-
if (isReadOnlyMode() && !
|
|
2925
|
+
if (isReadOnlyMode() && !areAllStatementsReadOnly(sql2, connector.id)) {
|
|
2851
2926
|
return createToolErrorResponse(
|
|
2852
2927
|
`Read-only mode is enabled. Only the following SQL operations are allowed: ${allowedKeywords[connector.id]?.join(", ") || "none"}`,
|
|
2853
2928
|
"READONLY_VIOLATION"
|
|
@@ -3399,27 +3474,49 @@ See documentation for more details on configuring database connections.
|
|
|
3399
3474
|
console.error(`Running in ${activeModes.join(" and ")} mode - ${modeDescriptions.join(", ")}`);
|
|
3400
3475
|
}
|
|
3401
3476
|
console.error(generateBanner(SERVER_VERSION, activeModes));
|
|
3402
|
-
if (transportData.type === "
|
|
3477
|
+
if (transportData.type === "http") {
|
|
3403
3478
|
const app = express();
|
|
3404
|
-
|
|
3405
|
-
app.
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3479
|
+
app.use(express.json());
|
|
3480
|
+
app.use((req, res, next) => {
|
|
3481
|
+
const origin = req.headers.origin;
|
|
3482
|
+
if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
|
|
3483
|
+
return res.status(403).json({ error: "Forbidden origin" });
|
|
3484
|
+
}
|
|
3485
|
+
res.header("Access-Control-Allow-Origin", origin || "http://localhost");
|
|
3486
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
3487
|
+
res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
3488
|
+
res.header("Access-Control-Allow-Credentials", "true");
|
|
3489
|
+
if (req.method === "OPTIONS") {
|
|
3490
|
+
return res.sendStatus(200);
|
|
3491
|
+
}
|
|
3492
|
+
next();
|
|
3412
3493
|
});
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3494
|
+
const transport = new StreamableHTTPServerTransport({
|
|
3495
|
+
sessionIdGenerator: () => `session_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
3496
|
+
onsessioninitialized: (sessionId) => {
|
|
3497
|
+
console.error(`Session initialized: ${sessionId}`);
|
|
3498
|
+
},
|
|
3499
|
+
enableJsonResponse: false
|
|
3500
|
+
// Use SSE streaming
|
|
3501
|
+
});
|
|
3502
|
+
await server.connect(transport);
|
|
3503
|
+
app.all("/message", async (req, res) => {
|
|
3504
|
+
try {
|
|
3505
|
+
const parsedBody = req.method === "POST" ? req.body : void 0;
|
|
3506
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
console.error("Error handling request:", error);
|
|
3509
|
+
if (!res.headersSent) {
|
|
3510
|
+
res.status(500).json({ error: "Internal server error" });
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3416
3513
|
});
|
|
3417
3514
|
const portData = resolvePort();
|
|
3418
3515
|
const port = portData.port;
|
|
3419
3516
|
console.error(`Port source: ${portData.source}`);
|
|
3420
|
-
app.listen(port, () => {
|
|
3517
|
+
app.listen(port, "localhost", () => {
|
|
3421
3518
|
console.error(`DBHub server listening at http://localhost:${port}`);
|
|
3422
|
-
console.error(`Connect to MCP server at http://localhost:${port}/
|
|
3519
|
+
console.error(`Connect to MCP server at http://localhost:${port}/message`);
|
|
3423
3520
|
});
|
|
3424
3521
|
} else {
|
|
3425
3522
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebase/dbhub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Universal Database MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@azure/identity": "^4.8.0",
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
21
21
|
"better-sqlite3": "^11.9.0",
|
|
22
22
|
"dotenv": "^16.4.7",
|
|
23
23
|
"express": "^4.18.2",
|