@bytebase/dbhub 0.13.2 → 0.15.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 +22 -30
- package/dist/{chunk-KBVJEDZF.js → chunk-TPHNNFR5.js} +464 -217
- package/dist/index.js +264 -164
- package/dist/public/assets/index-BJ-1UrcV.css +1 -0
- package/dist/public/assets/index-DBYlgGks.js +147 -0
- package/dist/public/index.html +2 -2
- package/dist/{registry-AWAIN6WO.js → registry-XXEL5IXH.js} +1 -1
- package/package.json +3 -3
- package/dist/public/assets/index-gVrYRID4.css +0 -1
- package/dist/public/assets/index-hd88eD9m.js +0 -51
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
ConnectorManager,
|
|
6
6
|
ConnectorRegistry,
|
|
7
7
|
SafeURL,
|
|
8
|
-
buildDSNFromSource,
|
|
9
8
|
getDatabaseTypeFromDSN,
|
|
10
9
|
getDefaultPortForType,
|
|
11
10
|
getToolRegistry,
|
|
@@ -13,12 +12,11 @@ import {
|
|
|
13
12
|
mapArgumentsToArray,
|
|
14
13
|
obfuscateDSNPassword,
|
|
15
14
|
parseConnectionInfoFromDSN,
|
|
16
|
-
redactDSN,
|
|
17
15
|
resolvePort,
|
|
18
16
|
resolveSourceConfigs,
|
|
19
17
|
resolveTransport,
|
|
20
18
|
stripCommentsAndStrings
|
|
21
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-TPHNNFR5.js";
|
|
22
20
|
|
|
23
21
|
// src/connectors/postgres/index.ts
|
|
24
22
|
import pg from "pg";
|
|
@@ -148,6 +146,7 @@ var { Pool } = pg;
|
|
|
148
146
|
var PostgresDSNParser = class {
|
|
149
147
|
async parse(dsn, config) {
|
|
150
148
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
149
|
+
const queryTimeoutSeconds = config?.queryTimeoutSeconds;
|
|
151
150
|
if (!this.isValidDSN(dsn)) {
|
|
152
151
|
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
153
152
|
const expectedFormat = this.getSampleDSN();
|
|
@@ -159,7 +158,7 @@ Expected: ${expectedFormat}`
|
|
|
159
158
|
}
|
|
160
159
|
try {
|
|
161
160
|
const url = new SafeURL(dsn);
|
|
162
|
-
const
|
|
161
|
+
const poolConfig = {
|
|
163
162
|
host: url.hostname,
|
|
164
163
|
port: url.port ? parseInt(url.port) : 5432,
|
|
165
164
|
database: url.pathname ? url.pathname.substring(1) : "",
|
|
@@ -170,18 +169,21 @@ Expected: ${expectedFormat}`
|
|
|
170
169
|
url.forEachSearchParam((value, key) => {
|
|
171
170
|
if (key === "sslmode") {
|
|
172
171
|
if (value === "disable") {
|
|
173
|
-
|
|
172
|
+
poolConfig.ssl = false;
|
|
174
173
|
} else if (value === "require") {
|
|
175
|
-
|
|
174
|
+
poolConfig.ssl = { rejectUnauthorized: false };
|
|
176
175
|
} else {
|
|
177
|
-
|
|
176
|
+
poolConfig.ssl = true;
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
});
|
|
181
180
|
if (connectionTimeoutSeconds !== void 0) {
|
|
182
|
-
|
|
181
|
+
poolConfig.connectionTimeoutMillis = connectionTimeoutSeconds * 1e3;
|
|
183
182
|
}
|
|
184
|
-
|
|
183
|
+
if (queryTimeoutSeconds !== void 0) {
|
|
184
|
+
poolConfig.query_timeout = queryTimeoutSeconds * 1e3;
|
|
185
|
+
}
|
|
186
|
+
return poolConfig;
|
|
185
187
|
} catch (error) {
|
|
186
188
|
throw new Error(
|
|
187
189
|
`Failed to parse PostgreSQL DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -475,22 +477,26 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
475
477
|
const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
476
478
|
if (statements.length === 1) {
|
|
477
479
|
const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
|
|
480
|
+
let result;
|
|
478
481
|
if (parameters && parameters.length > 0) {
|
|
479
482
|
try {
|
|
480
|
-
|
|
483
|
+
result = await client.query(processedStatement, parameters);
|
|
481
484
|
} catch (error) {
|
|
482
485
|
console.error(`[PostgreSQL executeSQL] ERROR: ${error.message}`);
|
|
483
486
|
console.error(`[PostgreSQL executeSQL] SQL: ${processedStatement}`);
|
|
484
487
|
console.error(`[PostgreSQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
485
488
|
throw error;
|
|
486
489
|
}
|
|
490
|
+
} else {
|
|
491
|
+
result = await client.query(processedStatement);
|
|
487
492
|
}
|
|
488
|
-
return
|
|
493
|
+
return { rows: result.rows, rowCount: result.rowCount ?? result.rows.length };
|
|
489
494
|
} else {
|
|
490
495
|
if (parameters && parameters.length > 0) {
|
|
491
496
|
throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
|
|
492
497
|
}
|
|
493
498
|
let allRows = [];
|
|
499
|
+
let totalRowCount = 0;
|
|
494
500
|
await client.query("BEGIN");
|
|
495
501
|
try {
|
|
496
502
|
for (let statement of statements) {
|
|
@@ -499,13 +505,16 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
499
505
|
if (result.rows && result.rows.length > 0) {
|
|
500
506
|
allRows.push(...result.rows);
|
|
501
507
|
}
|
|
508
|
+
if (result.rowCount) {
|
|
509
|
+
totalRowCount += result.rowCount;
|
|
510
|
+
}
|
|
502
511
|
}
|
|
503
512
|
await client.query("COMMIT");
|
|
504
513
|
} catch (error) {
|
|
505
514
|
await client.query("ROLLBACK");
|
|
506
515
|
throw error;
|
|
507
516
|
}
|
|
508
|
-
return { rows: allRows };
|
|
517
|
+
return { rows: allRows, rowCount: totalRowCount };
|
|
509
518
|
}
|
|
510
519
|
} finally {
|
|
511
520
|
client.release();
|
|
@@ -521,7 +530,7 @@ import { DefaultAzureCredential } from "@azure/identity";
|
|
|
521
530
|
var SQLServerDSNParser = class {
|
|
522
531
|
async parse(dsn, config) {
|
|
523
532
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
524
|
-
const
|
|
533
|
+
const queryTimeoutSeconds = config?.queryTimeoutSeconds;
|
|
525
534
|
if (!this.isValidDSN(dsn)) {
|
|
526
535
|
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
527
536
|
const expectedFormat = this.getSampleDSN();
|
|
@@ -541,8 +550,16 @@ Expected: ${expectedFormat}`
|
|
|
541
550
|
options.sslmode = value;
|
|
542
551
|
} else if (key === "instanceName") {
|
|
543
552
|
options.instanceName = value;
|
|
553
|
+
} else if (key === "domain") {
|
|
554
|
+
options.domain = value;
|
|
544
555
|
}
|
|
545
556
|
});
|
|
557
|
+
if (options.authentication === "ntlm" && !options.domain) {
|
|
558
|
+
throw new Error("NTLM authentication requires 'domain' parameter");
|
|
559
|
+
}
|
|
560
|
+
if (options.domain && options.authentication !== "ntlm") {
|
|
561
|
+
throw new Error("Parameter 'domain' requires 'authentication=ntlm'");
|
|
562
|
+
}
|
|
546
563
|
if (options.sslmode) {
|
|
547
564
|
if (options.sslmode === "disable") {
|
|
548
565
|
options.encrypt = false;
|
|
@@ -553,8 +570,6 @@ Expected: ${expectedFormat}`
|
|
|
553
570
|
}
|
|
554
571
|
}
|
|
555
572
|
const config2 = {
|
|
556
|
-
user: url.username,
|
|
557
|
-
password: url.password,
|
|
558
573
|
server: url.hostname,
|
|
559
574
|
port: url.port ? parseInt(url.port) : 1433,
|
|
560
575
|
// Default SQL Server port
|
|
@@ -567,27 +582,44 @@ Expected: ${expectedFormat}`
|
|
|
567
582
|
...connectionTimeoutSeconds !== void 0 && {
|
|
568
583
|
connectTimeout: connectionTimeoutSeconds * 1e3
|
|
569
584
|
},
|
|
570
|
-
...
|
|
571
|
-
requestTimeout:
|
|
585
|
+
...queryTimeoutSeconds !== void 0 && {
|
|
586
|
+
requestTimeout: queryTimeoutSeconds * 1e3
|
|
572
587
|
},
|
|
573
588
|
instanceName: options.instanceName
|
|
574
589
|
// Add named instance support
|
|
575
590
|
}
|
|
576
591
|
};
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
592
|
+
switch (options.authentication) {
|
|
593
|
+
case "azure-active-directory-access-token": {
|
|
594
|
+
try {
|
|
595
|
+
const credential = new DefaultAzureCredential();
|
|
596
|
+
const token = await credential.getToken("https://database.windows.net/");
|
|
597
|
+
config2.authentication = {
|
|
598
|
+
type: "azure-active-directory-access-token",
|
|
599
|
+
options: {
|
|
600
|
+
token: token.token
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
} catch (error) {
|
|
604
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
605
|
+
throw new Error(`Failed to get Azure AD token: ${errorMessage}`);
|
|
606
|
+
}
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case "ntlm":
|
|
581
610
|
config2.authentication = {
|
|
582
|
-
type: "
|
|
611
|
+
type: "ntlm",
|
|
583
612
|
options: {
|
|
584
|
-
|
|
613
|
+
domain: options.domain,
|
|
614
|
+
userName: url.username,
|
|
615
|
+
password: url.password
|
|
585
616
|
}
|
|
586
617
|
};
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
config2.user = url.username;
|
|
621
|
+
config2.password = url.password;
|
|
622
|
+
break;
|
|
591
623
|
}
|
|
592
624
|
return config2;
|
|
593
625
|
} catch (error) {
|
|
@@ -904,9 +936,6 @@ var SQLServerConnector = class _SQLServerConnector {
|
|
|
904
936
|
}
|
|
905
937
|
return {
|
|
906
938
|
rows: result.recordset || [],
|
|
907
|
-
fields: result.recordset && result.recordset.length > 0 ? Object.keys(result.recordset[0]).map((key) => ({
|
|
908
|
-
name: key
|
|
909
|
-
})) : [],
|
|
910
939
|
rowCount: result.rowsAffected[0] || 0
|
|
911
940
|
};
|
|
912
941
|
} catch (error) {
|
|
@@ -1190,7 +1219,7 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1190
1219
|
if (parameters && parameters.length > 0) {
|
|
1191
1220
|
try {
|
|
1192
1221
|
const rows = this.db.prepare(processedStatement).all(...parameters);
|
|
1193
|
-
return { rows };
|
|
1222
|
+
return { rows, rowCount: rows.length };
|
|
1194
1223
|
} catch (error) {
|
|
1195
1224
|
console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
|
|
1196
1225
|
console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
|
|
@@ -1199,12 +1228,13 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1199
1228
|
}
|
|
1200
1229
|
} else {
|
|
1201
1230
|
const rows = this.db.prepare(processedStatement).all();
|
|
1202
|
-
return { rows };
|
|
1231
|
+
return { rows, rowCount: rows.length };
|
|
1203
1232
|
}
|
|
1204
1233
|
} else {
|
|
1234
|
+
let result;
|
|
1205
1235
|
if (parameters && parameters.length > 0) {
|
|
1206
1236
|
try {
|
|
1207
|
-
this.db.prepare(processedStatement).run(...parameters);
|
|
1237
|
+
result = this.db.prepare(processedStatement).run(...parameters);
|
|
1208
1238
|
} catch (error) {
|
|
1209
1239
|
console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
|
|
1210
1240
|
console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
|
|
@@ -1212,9 +1242,9 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1212
1242
|
throw error;
|
|
1213
1243
|
}
|
|
1214
1244
|
} else {
|
|
1215
|
-
this.db.prepare(processedStatement).run();
|
|
1245
|
+
result = this.db.prepare(processedStatement).run();
|
|
1216
1246
|
}
|
|
1217
|
-
return { rows: [] };
|
|
1247
|
+
return { rows: [], rowCount: result.changes };
|
|
1218
1248
|
}
|
|
1219
1249
|
} else {
|
|
1220
1250
|
if (parameters && parameters.length > 0) {
|
|
@@ -1230,8 +1260,10 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1230
1260
|
writeStatements.push(statement);
|
|
1231
1261
|
}
|
|
1232
1262
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1263
|
+
let totalChanges = 0;
|
|
1264
|
+
for (const statement of writeStatements) {
|
|
1265
|
+
const result = this.db.prepare(statement).run();
|
|
1266
|
+
totalChanges += result.changes;
|
|
1235
1267
|
}
|
|
1236
1268
|
let allRows = [];
|
|
1237
1269
|
for (let statement of readStatements) {
|
|
@@ -1239,7 +1271,7 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1239
1271
|
const result = this.db.prepare(statement).all();
|
|
1240
1272
|
allRows.push(...result);
|
|
1241
1273
|
}
|
|
1242
|
-
return { rows: allRows };
|
|
1274
|
+
return { rows: allRows, rowCount: totalChanges + allRows.length };
|
|
1243
1275
|
}
|
|
1244
1276
|
} catch (error) {
|
|
1245
1277
|
throw error;
|
|
@@ -1278,6 +1310,26 @@ function extractRowsFromMultiStatement(results) {
|
|
|
1278
1310
|
}
|
|
1279
1311
|
return allRows;
|
|
1280
1312
|
}
|
|
1313
|
+
function extractAffectedRows(results) {
|
|
1314
|
+
if (isMetadataObject(results)) {
|
|
1315
|
+
return results.affectedRows || 0;
|
|
1316
|
+
}
|
|
1317
|
+
if (!Array.isArray(results)) {
|
|
1318
|
+
return 0;
|
|
1319
|
+
}
|
|
1320
|
+
if (isMultiStatementResult(results)) {
|
|
1321
|
+
let totalAffected = 0;
|
|
1322
|
+
for (const result of results) {
|
|
1323
|
+
if (isMetadataObject(result)) {
|
|
1324
|
+
totalAffected += result.affectedRows || 0;
|
|
1325
|
+
} else if (Array.isArray(result)) {
|
|
1326
|
+
totalAffected += result.length;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return totalAffected;
|
|
1330
|
+
}
|
|
1331
|
+
return results.length;
|
|
1332
|
+
}
|
|
1281
1333
|
function parseQueryResults(results) {
|
|
1282
1334
|
if (!Array.isArray(results)) {
|
|
1283
1335
|
return [];
|
|
@@ -1374,6 +1426,9 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1374
1426
|
try {
|
|
1375
1427
|
const connectionOptions = await this.dsnParser.parse(dsn, config);
|
|
1376
1428
|
this.pool = mysql.createPool(connectionOptions);
|
|
1429
|
+
if (config?.queryTimeoutSeconds !== void 0) {
|
|
1430
|
+
this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
|
|
1431
|
+
}
|
|
1377
1432
|
const [rows] = await this.pool.query("SELECT 1");
|
|
1378
1433
|
} catch (err) {
|
|
1379
1434
|
console.error("Failed to connect to MySQL database:", err);
|
|
@@ -1676,7 +1731,7 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1676
1731
|
let results;
|
|
1677
1732
|
if (parameters && parameters.length > 0) {
|
|
1678
1733
|
try {
|
|
1679
|
-
results = await conn.query(processedSQL, parameters);
|
|
1734
|
+
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
|
|
1680
1735
|
} catch (error) {
|
|
1681
1736
|
console.error(`[MySQL executeSQL] ERROR: ${error.message}`);
|
|
1682
1737
|
console.error(`[MySQL executeSQL] SQL: ${processedSQL}`);
|
|
@@ -1684,11 +1739,12 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1684
1739
|
throw error;
|
|
1685
1740
|
}
|
|
1686
1741
|
} else {
|
|
1687
|
-
results = await conn.query(processedSQL);
|
|
1742
|
+
results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
|
|
1688
1743
|
}
|
|
1689
1744
|
const [firstResult] = results;
|
|
1690
1745
|
const rows = parseQueryResults(firstResult);
|
|
1691
|
-
|
|
1746
|
+
const rowCount = extractAffectedRows(firstResult);
|
|
1747
|
+
return { rows, rowCount };
|
|
1692
1748
|
} catch (error) {
|
|
1693
1749
|
console.error("Error executing query:", error);
|
|
1694
1750
|
throw error;
|
|
@@ -1705,6 +1761,7 @@ import mariadb from "mariadb";
|
|
|
1705
1761
|
var MariadbDSNParser = class {
|
|
1706
1762
|
async parse(dsn, config) {
|
|
1707
1763
|
const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
|
|
1764
|
+
const queryTimeoutSeconds = config?.queryTimeoutSeconds;
|
|
1708
1765
|
if (!this.isValidDSN(dsn)) {
|
|
1709
1766
|
const obfuscatedDSN = obfuscateDSNPassword(dsn);
|
|
1710
1767
|
const expectedFormat = this.getSampleDSN();
|
|
@@ -1716,7 +1773,7 @@ Expected: ${expectedFormat}`
|
|
|
1716
1773
|
}
|
|
1717
1774
|
try {
|
|
1718
1775
|
const url = new SafeURL(dsn);
|
|
1719
|
-
const
|
|
1776
|
+
const connectionConfig = {
|
|
1720
1777
|
host: url.hostname,
|
|
1721
1778
|
port: url.port ? parseInt(url.port) : 3306,
|
|
1722
1779
|
database: url.pathname ? url.pathname.substring(1) : "",
|
|
@@ -1727,25 +1784,28 @@ Expected: ${expectedFormat}`
|
|
|
1727
1784
|
// Enable native multi-statement support
|
|
1728
1785
|
...connectionTimeoutSeconds !== void 0 && {
|
|
1729
1786
|
connectTimeout: connectionTimeoutSeconds * 1e3
|
|
1787
|
+
},
|
|
1788
|
+
...queryTimeoutSeconds !== void 0 && {
|
|
1789
|
+
queryTimeout: queryTimeoutSeconds * 1e3
|
|
1730
1790
|
}
|
|
1731
1791
|
};
|
|
1732
1792
|
url.forEachSearchParam((value, key) => {
|
|
1733
1793
|
if (key === "sslmode") {
|
|
1734
1794
|
if (value === "disable") {
|
|
1735
|
-
|
|
1795
|
+
connectionConfig.ssl = void 0;
|
|
1736
1796
|
} else if (value === "require") {
|
|
1737
|
-
|
|
1797
|
+
connectionConfig.ssl = { rejectUnauthorized: false };
|
|
1738
1798
|
} else {
|
|
1739
|
-
|
|
1799
|
+
connectionConfig.ssl = {};
|
|
1740
1800
|
}
|
|
1741
1801
|
}
|
|
1742
1802
|
});
|
|
1743
1803
|
if (url.password && url.password.includes("X-Amz-Credential")) {
|
|
1744
|
-
if (
|
|
1745
|
-
|
|
1804
|
+
if (connectionConfig.ssl === void 0) {
|
|
1805
|
+
connectionConfig.ssl = { rejectUnauthorized: false };
|
|
1746
1806
|
}
|
|
1747
1807
|
}
|
|
1748
|
-
return
|
|
1808
|
+
return connectionConfig;
|
|
1749
1809
|
} catch (error) {
|
|
1750
1810
|
throw new Error(
|
|
1751
1811
|
`Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2095,7 +2155,8 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
2095
2155
|
results = await conn.query(processedSQL);
|
|
2096
2156
|
}
|
|
2097
2157
|
const rows = parseQueryResults(results);
|
|
2098
|
-
|
|
2158
|
+
const rowCount = extractAffectedRows(results);
|
|
2159
|
+
return { rows, rowCount };
|
|
2099
2160
|
} catch (error) {
|
|
2100
2161
|
console.error("Error executing query:", error);
|
|
2101
2162
|
throw error;
|
|
@@ -2242,9 +2303,30 @@ function getClientIdentifier(extra) {
|
|
|
2242
2303
|
return "stdio";
|
|
2243
2304
|
}
|
|
2244
2305
|
|
|
2306
|
+
// src/utils/tool-handler-helpers.ts
|
|
2307
|
+
function getEffectiveSourceId(sourceId) {
|
|
2308
|
+
return sourceId || "default";
|
|
2309
|
+
}
|
|
2310
|
+
function createReadonlyViolationMessage(toolName, sourceId, connectorType) {
|
|
2311
|
+
return `Tool '${toolName}' cannot execute in readonly mode for source '${sourceId}'. Only read-only SQL operations are allowed: ${allowedKeywords[connectorType]?.join(", ") || "none"}`;
|
|
2312
|
+
}
|
|
2313
|
+
function trackToolRequest(metadata, startTime, extra, success, error) {
|
|
2314
|
+
requestStore.add({
|
|
2315
|
+
id: crypto.randomUUID(),
|
|
2316
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2317
|
+
sourceId: metadata.sourceId,
|
|
2318
|
+
toolName: metadata.toolName,
|
|
2319
|
+
sql: metadata.sql,
|
|
2320
|
+
durationMs: Date.now() - startTime,
|
|
2321
|
+
client: getClientIdentifier(extra),
|
|
2322
|
+
success,
|
|
2323
|
+
error
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2245
2327
|
// src/tools/execute-sql.ts
|
|
2246
2328
|
var executeSqlSchema = {
|
|
2247
|
-
sql: z.string().describe("SQL
|
|
2329
|
+
sql: z.string().describe("SQL to execute (multiple statements separated by ;)")
|
|
2248
2330
|
};
|
|
2249
2331
|
function splitSQLStatements(sql2) {
|
|
2250
2332
|
return sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
@@ -2257,7 +2339,7 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2257
2339
|
return async (args, extra) => {
|
|
2258
2340
|
const { sql: sql2 } = args;
|
|
2259
2341
|
const startTime = Date.now();
|
|
2260
|
-
const effectiveSourceId = sourceId
|
|
2342
|
+
const effectiveSourceId = getEffectiveSourceId(sourceId);
|
|
2261
2343
|
let success = true;
|
|
2262
2344
|
let errorMessage;
|
|
2263
2345
|
let result;
|
|
@@ -2268,18 +2350,18 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2268
2350
|
const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, actualSourceId);
|
|
2269
2351
|
const isReadonly = toolConfig?.readonly === true;
|
|
2270
2352
|
if (isReadonly && !areAllStatementsReadOnly(sql2, connector.id)) {
|
|
2271
|
-
errorMessage = `Read-only mode is enabled
|
|
2353
|
+
errorMessage = `Read-only mode is enabled. Only the following SQL operations are allowed: ${allowedKeywords[connector.id]?.join(", ") || "none"}`;
|
|
2272
2354
|
success = false;
|
|
2273
2355
|
return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
|
|
2274
2356
|
}
|
|
2275
2357
|
const executeOptions = {
|
|
2276
|
-
readonly:
|
|
2277
|
-
|
|
2358
|
+
readonly: toolConfig?.readonly,
|
|
2359
|
+
maxRows: toolConfig?.max_rows
|
|
2278
2360
|
};
|
|
2279
2361
|
result = await connector.executeSQL(sql2, executeOptions);
|
|
2280
2362
|
const responseData = {
|
|
2281
2363
|
rows: result.rows,
|
|
2282
|
-
count: result.
|
|
2364
|
+
count: result.rowCount,
|
|
2283
2365
|
source_id: effectiveSourceId
|
|
2284
2366
|
};
|
|
2285
2367
|
return createToolSuccessResponse(responseData);
|
|
@@ -2288,17 +2370,17 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2288
2370
|
errorMessage = error.message;
|
|
2289
2371
|
return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
|
|
2290
2372
|
} finally {
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2373
|
+
trackToolRequest(
|
|
2374
|
+
{
|
|
2375
|
+
sourceId: effectiveSourceId,
|
|
2376
|
+
toolName: effectiveSourceId === "default" ? "execute_sql" : `execute_sql_${effectiveSourceId}`,
|
|
2377
|
+
sql: sql2
|
|
2378
|
+
},
|
|
2379
|
+
startTime,
|
|
2380
|
+
extra,
|
|
2299
2381
|
success,
|
|
2300
|
-
|
|
2301
|
-
|
|
2382
|
+
errorMessage
|
|
2383
|
+
);
|
|
2302
2384
|
}
|
|
2303
2385
|
};
|
|
2304
2386
|
}
|
|
@@ -2306,12 +2388,12 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2306
2388
|
// src/tools/search-objects.ts
|
|
2307
2389
|
import { z as z2 } from "zod";
|
|
2308
2390
|
var searchDatabaseObjectsSchema = {
|
|
2309
|
-
object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("
|
|
2310
|
-
pattern: z2.string().optional().default("%").describe("
|
|
2311
|
-
schema: z2.string().optional().describe("Filter
|
|
2312
|
-
table: z2.string().optional().describe("Filter to
|
|
2313
|
-
detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("
|
|
2314
|
-
limit: z2.number().int().positive().max(1e3).default(100).describe("
|
|
2391
|
+
object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Object type to search"),
|
|
2392
|
+
pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char). Default: %"),
|
|
2393
|
+
schema: z2.string().optional().describe("Filter to schema"),
|
|
2394
|
+
table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
|
|
2395
|
+
detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("Detail: names (minimal), summary (metadata), full (all)"),
|
|
2396
|
+
limit: z2.number().int().positive().max(1e3).default(100).describe("Max results (default: 100, max: 1000)")
|
|
2315
2397
|
};
|
|
2316
2398
|
function likePatternToRegex(pattern) {
|
|
2317
2399
|
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
|
|
@@ -2585,7 +2667,7 @@ async function searchIndexes(connector, pattern, schemaFilter, tableFilter, deta
|
|
|
2585
2667
|
return results;
|
|
2586
2668
|
}
|
|
2587
2669
|
function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
2588
|
-
return async (args,
|
|
2670
|
+
return async (args, extra) => {
|
|
2589
2671
|
const {
|
|
2590
2672
|
object_type,
|
|
2591
2673
|
pattern = "%",
|
|
@@ -2594,29 +2676,30 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2594
2676
|
detail_level = "names",
|
|
2595
2677
|
limit = 100
|
|
2596
2678
|
} = args;
|
|
2679
|
+
const startTime = Date.now();
|
|
2680
|
+
const effectiveSourceId = getEffectiveSourceId(sourceId);
|
|
2681
|
+
let success = true;
|
|
2682
|
+
let errorMessage;
|
|
2597
2683
|
try {
|
|
2598
2684
|
const connector = ConnectorManager.getCurrentConnector(sourceId);
|
|
2599
2685
|
if (table) {
|
|
2600
2686
|
if (!schema) {
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
);
|
|
2687
|
+
success = false;
|
|
2688
|
+
errorMessage = "The 'table' parameter requires 'schema' to be specified";
|
|
2689
|
+
return createToolErrorResponse(errorMessage, "SCHEMA_REQUIRED");
|
|
2605
2690
|
}
|
|
2606
2691
|
if (!["column", "index"].includes(object_type)) {
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
);
|
|
2692
|
+
success = false;
|
|
2693
|
+
errorMessage = `The 'table' parameter only applies to object_type 'column' or 'index', not '${object_type}'`;
|
|
2694
|
+
return createToolErrorResponse(errorMessage, "INVALID_TABLE_FILTER");
|
|
2611
2695
|
}
|
|
2612
2696
|
}
|
|
2613
2697
|
if (schema) {
|
|
2614
2698
|
const schemas = await connector.getSchemas();
|
|
2615
2699
|
if (!schemas.includes(schema)) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
);
|
|
2700
|
+
success = false;
|
|
2701
|
+
errorMessage = `Schema '${schema}' does not exist. Available schemas: ${schemas.join(", ")}`;
|
|
2702
|
+
return createToolErrorResponse(errorMessage, "SCHEMA_NOT_FOUND");
|
|
2620
2703
|
}
|
|
2621
2704
|
}
|
|
2622
2705
|
let results = [];
|
|
@@ -2637,10 +2720,9 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2637
2720
|
results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
|
|
2638
2721
|
break;
|
|
2639
2722
|
default:
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
);
|
|
2723
|
+
success = false;
|
|
2724
|
+
errorMessage = `Unsupported object_type: ${object_type}`;
|
|
2725
|
+
return createToolErrorResponse(errorMessage, "INVALID_OBJECT_TYPE");
|
|
2644
2726
|
}
|
|
2645
2727
|
return createToolSuccessResponse({
|
|
2646
2728
|
object_type,
|
|
@@ -2653,10 +2735,24 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2653
2735
|
truncated: results.length === limit
|
|
2654
2736
|
});
|
|
2655
2737
|
} catch (error) {
|
|
2738
|
+
success = false;
|
|
2739
|
+
errorMessage = error.message;
|
|
2656
2740
|
return createToolErrorResponse(
|
|
2657
|
-
`Error searching database objects: ${
|
|
2741
|
+
`Error searching database objects: ${errorMessage}`,
|
|
2658
2742
|
"SEARCH_ERROR"
|
|
2659
2743
|
);
|
|
2744
|
+
} finally {
|
|
2745
|
+
trackToolRequest(
|
|
2746
|
+
{
|
|
2747
|
+
sourceId: effectiveSourceId,
|
|
2748
|
+
toolName: effectiveSourceId === "default" ? "search_objects" : `search_objects_${effectiveSourceId}`,
|
|
2749
|
+
sql: `search_objects(object_type=${object_type}, pattern=${pattern}, schema=${schema || "all"}, table=${table || "all"}, detail_level=${detail_level})`
|
|
2750
|
+
},
|
|
2751
|
+
startTime,
|
|
2752
|
+
extra,
|
|
2753
|
+
success,
|
|
2754
|
+
errorMessage
|
|
2755
|
+
);
|
|
2660
2756
|
}
|
|
2661
2757
|
};
|
|
2662
2758
|
}
|
|
@@ -2699,14 +2795,19 @@ function zodToParameters(schema) {
|
|
|
2699
2795
|
function getExecuteSqlMetadata(sourceId) {
|
|
2700
2796
|
const sourceIds = ConnectorManager.getAvailableSourceIds();
|
|
2701
2797
|
const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
|
|
2702
|
-
const executeOptions = ConnectorManager.getCurrentExecuteOptions(sourceId);
|
|
2703
2798
|
const dbType = sourceConfig.type;
|
|
2704
|
-
const
|
|
2705
|
-
const
|
|
2706
|
-
const
|
|
2799
|
+
const isSingleSource = sourceIds.length === 1;
|
|
2800
|
+
const registry = getToolRegistry();
|
|
2801
|
+
const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, sourceId);
|
|
2802
|
+
const executeOptions = {
|
|
2803
|
+
readonly: toolConfig?.readonly,
|
|
2804
|
+
maxRows: toolConfig?.max_rows
|
|
2805
|
+
};
|
|
2806
|
+
const toolName = isSingleSource ? "execute_sql" : `execute_sql_${normalizeSourceId(sourceId)}`;
|
|
2807
|
+
const title = isSingleSource ? `Execute SQL (${dbType})` : `Execute SQL on ${sourceId} (${dbType})`;
|
|
2707
2808
|
const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : "";
|
|
2708
2809
|
const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : "";
|
|
2709
|
-
const description = `Execute SQL queries on the
|
|
2810
|
+
const description = isSingleSource ? `Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`;
|
|
2710
2811
|
const isReadonly = executeOptions.readonly === true;
|
|
2711
2812
|
const annotations = {
|
|
2712
2813
|
title,
|
|
@@ -2726,10 +2827,14 @@ function getExecuteSqlMetadata(sourceId) {
|
|
|
2726
2827
|
annotations
|
|
2727
2828
|
};
|
|
2728
2829
|
}
|
|
2729
|
-
function getSearchObjectsMetadata(sourceId
|
|
2730
|
-
const
|
|
2731
|
-
const
|
|
2732
|
-
const
|
|
2830
|
+
function getSearchObjectsMetadata(sourceId) {
|
|
2831
|
+
const sourceIds = ConnectorManager.getAvailableSourceIds();
|
|
2832
|
+
const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
|
|
2833
|
+
const dbType = sourceConfig.type;
|
|
2834
|
+
const isSingleSource = sourceIds.length === 1;
|
|
2835
|
+
const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
|
|
2836
|
+
const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`;
|
|
2837
|
+
const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, indexes) on the '${sourceId}' ${dbType} database`;
|
|
2733
2838
|
return {
|
|
2734
2839
|
name: toolName,
|
|
2735
2840
|
description,
|
|
@@ -2747,21 +2852,21 @@ function customParamsToToolParams(params) {
|
|
|
2747
2852
|
description: param.description
|
|
2748
2853
|
}));
|
|
2749
2854
|
}
|
|
2750
|
-
function buildExecuteSqlTool(sourceId) {
|
|
2855
|
+
function buildExecuteSqlTool(sourceId, toolConfig) {
|
|
2751
2856
|
const executeSqlMetadata = getExecuteSqlMetadata(sourceId);
|
|
2752
2857
|
const executeSqlParameters = zodToParameters(executeSqlMetadata.schema);
|
|
2858
|
+
const readonly = toolConfig && "readonly" in toolConfig ? toolConfig.readonly : void 0;
|
|
2859
|
+
const max_rows = toolConfig && "max_rows" in toolConfig ? toolConfig.max_rows : void 0;
|
|
2753
2860
|
return {
|
|
2754
2861
|
name: executeSqlMetadata.name,
|
|
2755
2862
|
description: executeSqlMetadata.description,
|
|
2756
|
-
parameters: executeSqlParameters
|
|
2863
|
+
parameters: executeSqlParameters,
|
|
2864
|
+
readonly,
|
|
2865
|
+
max_rows
|
|
2757
2866
|
};
|
|
2758
2867
|
}
|
|
2759
2868
|
function buildSearchObjectsTool(sourceId) {
|
|
2760
|
-
const
|
|
2761
|
-
const dbType = sourceConfig.type;
|
|
2762
|
-
const sourceIds = ConnectorManager.getAvailableSourceIds();
|
|
2763
|
-
const isDefault = sourceIds[0] === sourceId;
|
|
2764
|
-
const searchMetadata = getSearchObjectsMetadata(sourceId, dbType, isDefault);
|
|
2869
|
+
const searchMetadata = getSearchObjectsMetadata(sourceId);
|
|
2765
2870
|
return {
|
|
2766
2871
|
name: searchMetadata.name,
|
|
2767
2872
|
description: searchMetadata.description,
|
|
@@ -2770,40 +2875,51 @@ function buildSearchObjectsTool(sourceId) {
|
|
|
2770
2875
|
name: "object_type",
|
|
2771
2876
|
type: "string",
|
|
2772
2877
|
required: true,
|
|
2773
|
-
description: "
|
|
2878
|
+
description: "Object type to search"
|
|
2774
2879
|
},
|
|
2775
2880
|
{
|
|
2776
2881
|
name: "pattern",
|
|
2777
2882
|
type: "string",
|
|
2778
2883
|
required: false,
|
|
2779
|
-
description: "
|
|
2884
|
+
description: "LIKE pattern (% = any chars, _ = one char). Default: %"
|
|
2780
2885
|
},
|
|
2781
2886
|
{
|
|
2782
2887
|
name: "schema",
|
|
2783
2888
|
type: "string",
|
|
2784
2889
|
required: false,
|
|
2785
|
-
description: "Filter
|
|
2890
|
+
description: "Filter to schema"
|
|
2891
|
+
},
|
|
2892
|
+
{
|
|
2893
|
+
name: "table",
|
|
2894
|
+
type: "string",
|
|
2895
|
+
required: false,
|
|
2896
|
+
description: "Filter to table (requires schema; column/index only)"
|
|
2786
2897
|
},
|
|
2787
2898
|
{
|
|
2788
2899
|
name: "detail_level",
|
|
2789
2900
|
type: "string",
|
|
2790
2901
|
required: false,
|
|
2791
|
-
description: "
|
|
2902
|
+
description: "Detail: names (minimal), summary (metadata), full (all)"
|
|
2792
2903
|
},
|
|
2793
2904
|
{
|
|
2794
2905
|
name: "limit",
|
|
2795
2906
|
type: "integer",
|
|
2796
2907
|
required: false,
|
|
2797
|
-
description: "
|
|
2908
|
+
description: "Max results (default: 100, max: 1000)"
|
|
2798
2909
|
}
|
|
2799
|
-
]
|
|
2910
|
+
],
|
|
2911
|
+
readonly: true
|
|
2912
|
+
// search_objects is always readonly
|
|
2800
2913
|
};
|
|
2801
2914
|
}
|
|
2802
2915
|
function buildCustomTool(toolConfig) {
|
|
2803
2916
|
return {
|
|
2804
2917
|
name: toolConfig.name,
|
|
2805
2918
|
description: toolConfig.description,
|
|
2806
|
-
parameters: customParamsToToolParams(toolConfig.parameters)
|
|
2919
|
+
parameters: customParamsToToolParams(toolConfig.parameters),
|
|
2920
|
+
statement: toolConfig.statement,
|
|
2921
|
+
readonly: toolConfig.readonly,
|
|
2922
|
+
max_rows: toolConfig.max_rows
|
|
2807
2923
|
};
|
|
2808
2924
|
}
|
|
2809
2925
|
function getToolsForSource(sourceId) {
|
|
@@ -2811,7 +2927,7 @@ function getToolsForSource(sourceId) {
|
|
|
2811
2927
|
const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId);
|
|
2812
2928
|
return enabledToolConfigs.map((toolConfig) => {
|
|
2813
2929
|
if (toolConfig.name === "execute_sql") {
|
|
2814
|
-
return buildExecuteSqlTool(sourceId);
|
|
2930
|
+
return buildExecuteSqlTool(sourceId, toolConfig);
|
|
2815
2931
|
} else if (toolConfig.name === "search_objects") {
|
|
2816
2932
|
return buildSearchObjectsTool(sourceId);
|
|
2817
2933
|
} else {
|
|
@@ -2878,12 +2994,13 @@ function createCustomToolHandler(toolConfig) {
|
|
|
2878
2994
|
try {
|
|
2879
2995
|
const validatedArgs = zodSchema.parse(args);
|
|
2880
2996
|
const connector = ConnectorManager.getCurrentConnector(toolConfig.source);
|
|
2881
|
-
const executeOptions =
|
|
2882
|
-
toolConfig.
|
|
2883
|
-
|
|
2997
|
+
const executeOptions = {
|
|
2998
|
+
readonly: toolConfig.readonly,
|
|
2999
|
+
maxRows: toolConfig.max_rows
|
|
3000
|
+
};
|
|
2884
3001
|
const isReadonly = executeOptions.readonly === true;
|
|
2885
3002
|
if (isReadonly && !isReadOnlySQL(toolConfig.statement, connector.id)) {
|
|
2886
|
-
errorMessage =
|
|
3003
|
+
errorMessage = createReadonlyViolationMessage(toolConfig.name, toolConfig.source, connector.id);
|
|
2887
3004
|
success = false;
|
|
2888
3005
|
return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
|
|
2889
3006
|
}
|
|
@@ -2898,7 +3015,7 @@ function createCustomToolHandler(toolConfig) {
|
|
|
2898
3015
|
);
|
|
2899
3016
|
const responseData = {
|
|
2900
3017
|
rows: result.rows,
|
|
2901
|
-
count: result.
|
|
3018
|
+
count: result.rowCount,
|
|
2902
3019
|
source_id: toolConfig.source
|
|
2903
3020
|
};
|
|
2904
3021
|
return createToolSuccessResponse(responseData);
|
|
@@ -2916,17 +3033,17 @@ Parameters: ${JSON.stringify(paramValues)}`;
|
|
|
2916
3033
|
}
|
|
2917
3034
|
return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
|
|
2918
3035
|
} finally {
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3036
|
+
trackToolRequest(
|
|
3037
|
+
{
|
|
3038
|
+
sourceId: toolConfig.source,
|
|
3039
|
+
toolName: toolConfig.name,
|
|
3040
|
+
sql: toolConfig.statement
|
|
3041
|
+
},
|
|
3042
|
+
startTime,
|
|
3043
|
+
extra,
|
|
2927
3044
|
success,
|
|
2928
|
-
|
|
2929
|
-
|
|
3045
|
+
errorMessage
|
|
3046
|
+
);
|
|
2930
3047
|
}
|
|
2931
3048
|
};
|
|
2932
3049
|
}
|
|
@@ -2940,21 +3057,18 @@ function registerTools(server) {
|
|
|
2940
3057
|
const registry = getToolRegistry();
|
|
2941
3058
|
for (const sourceId of sourceIds) {
|
|
2942
3059
|
const enabledTools = registry.getEnabledToolConfigs(sourceId);
|
|
2943
|
-
const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
|
|
2944
|
-
const dbType = sourceConfig.type;
|
|
2945
|
-
const isDefault = sourceIds[0] === sourceId;
|
|
2946
3060
|
for (const toolConfig of enabledTools) {
|
|
2947
3061
|
if (toolConfig.name === BUILTIN_TOOL_EXECUTE_SQL) {
|
|
2948
|
-
registerExecuteSqlTool(server, sourceId
|
|
3062
|
+
registerExecuteSqlTool(server, sourceId);
|
|
2949
3063
|
} else if (toolConfig.name === BUILTIN_TOOL_SEARCH_OBJECTS) {
|
|
2950
|
-
registerSearchObjectsTool(server, sourceId
|
|
3064
|
+
registerSearchObjectsTool(server, sourceId);
|
|
2951
3065
|
} else {
|
|
2952
|
-
registerCustomTool(server,
|
|
3066
|
+
registerCustomTool(server, sourceId, toolConfig);
|
|
2953
3067
|
}
|
|
2954
3068
|
}
|
|
2955
3069
|
}
|
|
2956
3070
|
}
|
|
2957
|
-
function registerExecuteSqlTool(server, sourceId
|
|
3071
|
+
function registerExecuteSqlTool(server, sourceId) {
|
|
2958
3072
|
const metadata = getExecuteSqlMetadata(sourceId);
|
|
2959
3073
|
server.registerTool(
|
|
2960
3074
|
metadata.name,
|
|
@@ -2966,8 +3080,8 @@ function registerExecuteSqlTool(server, sourceId, dbType) {
|
|
|
2966
3080
|
createExecuteSqlToolHandler(sourceId)
|
|
2967
3081
|
);
|
|
2968
3082
|
}
|
|
2969
|
-
function registerSearchObjectsTool(server, sourceId
|
|
2970
|
-
const metadata = getSearchObjectsMetadata(sourceId
|
|
3083
|
+
function registerSearchObjectsTool(server, sourceId) {
|
|
3084
|
+
const metadata = getSearchObjectsMetadata(sourceId);
|
|
2971
3085
|
server.registerTool(
|
|
2972
3086
|
metadata.name,
|
|
2973
3087
|
{
|
|
@@ -2984,7 +3098,9 @@ function registerSearchObjectsTool(server, sourceId, dbType, isDefault) {
|
|
|
2984
3098
|
createSearchDatabaseObjectsToolHandler(sourceId)
|
|
2985
3099
|
);
|
|
2986
3100
|
}
|
|
2987
|
-
function registerCustomTool(server,
|
|
3101
|
+
function registerCustomTool(server, sourceId, toolConfig) {
|
|
3102
|
+
const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
|
|
3103
|
+
const dbType = sourceConfig.type;
|
|
2988
3104
|
const isReadOnly = isReadOnlySQL(toolConfig.statement, dbType);
|
|
2989
3105
|
const zodSchema = buildZodSchemaFromParameters(toolConfig.parameters);
|
|
2990
3106
|
server.registerTool(
|
|
@@ -3002,11 +3118,10 @@ function registerCustomTool(server, toolConfig, dbType) {
|
|
|
3002
3118
|
},
|
|
3003
3119
|
createCustomToolHandler(toolConfig)
|
|
3004
3120
|
);
|
|
3005
|
-
console.error(` - ${toolConfig.name} \u2192 ${toolConfig.source} (${dbType})`);
|
|
3006
3121
|
}
|
|
3007
3122
|
|
|
3008
3123
|
// src/api/sources.ts
|
|
3009
|
-
function transformSourceConfig(source
|
|
3124
|
+
function transformSourceConfig(source) {
|
|
3010
3125
|
if (!source.type && source.dsn) {
|
|
3011
3126
|
const inferredType = getDatabaseTypeFromDSN(source.dsn);
|
|
3012
3127
|
if (inferredType) {
|
|
@@ -3018,8 +3133,7 @@ function transformSourceConfig(source, isDefault) {
|
|
|
3018
3133
|
}
|
|
3019
3134
|
const dataSource = {
|
|
3020
3135
|
id: source.id,
|
|
3021
|
-
type: source.type
|
|
3022
|
-
is_default: isDefault
|
|
3136
|
+
type: source.type
|
|
3023
3137
|
};
|
|
3024
3138
|
if (source.host) {
|
|
3025
3139
|
dataSource.host = source.host;
|
|
@@ -3033,12 +3147,6 @@ function transformSourceConfig(source, isDefault) {
|
|
|
3033
3147
|
if (source.user) {
|
|
3034
3148
|
dataSource.user = source.user;
|
|
3035
3149
|
}
|
|
3036
|
-
if (source.readonly !== void 0) {
|
|
3037
|
-
dataSource.readonly = source.readonly;
|
|
3038
|
-
}
|
|
3039
|
-
if (source.max_rows !== void 0) {
|
|
3040
|
-
dataSource.max_rows = source.max_rows;
|
|
3041
|
-
}
|
|
3042
3150
|
if (source.ssh_host) {
|
|
3043
3151
|
const sshTunnel = {
|
|
3044
3152
|
enabled: true,
|
|
@@ -3058,9 +3166,8 @@ function transformSourceConfig(source, isDefault) {
|
|
|
3058
3166
|
function listSources(req, res) {
|
|
3059
3167
|
try {
|
|
3060
3168
|
const sourceConfigs = ConnectorManager.getAllSourceConfigs();
|
|
3061
|
-
const sources = sourceConfigs.map((config
|
|
3062
|
-
|
|
3063
|
-
return transformSourceConfig(config, isDefault);
|
|
3169
|
+
const sources = sourceConfigs.map((config) => {
|
|
3170
|
+
return transformSourceConfig(config);
|
|
3064
3171
|
});
|
|
3065
3172
|
res.json(sources);
|
|
3066
3173
|
} catch (error) {
|
|
@@ -3074,7 +3181,6 @@ function listSources(req, res) {
|
|
|
3074
3181
|
function getSource(req, res) {
|
|
3075
3182
|
try {
|
|
3076
3183
|
const sourceId = req.params.sourceId;
|
|
3077
|
-
const sourceIds = ConnectorManager.getAvailableSourceIds();
|
|
3078
3184
|
const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
|
|
3079
3185
|
if (!sourceConfig) {
|
|
3080
3186
|
const errorResponse = {
|
|
@@ -3084,8 +3190,7 @@ function getSource(req, res) {
|
|
|
3084
3190
|
res.status(404).json(errorResponse);
|
|
3085
3191
|
return;
|
|
3086
3192
|
}
|
|
3087
|
-
const
|
|
3088
|
-
const dataSource = transformSourceConfig(sourceConfig, isDefault);
|
|
3193
|
+
const dataSource = transformSourceConfig(sourceConfig);
|
|
3089
3194
|
res.json(dataSource);
|
|
3090
3195
|
} catch (error) {
|
|
3091
3196
|
console.error(`Error getting source ${req.params.sourceId}:`, error);
|
|
@@ -3280,13 +3385,8 @@ See documentation for more details on configuring database connections.
|
|
|
3280
3385
|
const connectorManager = new ConnectorManager();
|
|
3281
3386
|
const sources = sourceConfigsData.sources;
|
|
3282
3387
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
3283
|
-
console.error(`Connecting to ${sources.length} database source(s)...`);
|
|
3284
|
-
for (const source of sources) {
|
|
3285
|
-
const dsn = source.dsn || buildDSNFromSource(source);
|
|
3286
|
-
console.error(` - ${source.id}: ${redactDSN(dsn)}`);
|
|
3287
|
-
}
|
|
3288
3388
|
await connectorManager.connectWithSources(sources);
|
|
3289
|
-
const { initializeToolRegistry } = await import("./registry-
|
|
3389
|
+
const { initializeToolRegistry } = await import("./registry-XXEL5IXH.js");
|
|
3290
3390
|
initializeToolRegistry({
|
|
3291
3391
|
sources: sourceConfigsData.sources,
|
|
3292
3392
|
tools: sourceConfigsData.tools
|
|
@@ -3315,7 +3415,7 @@ See documentation for more details on configuring database connections.
|
|
|
3315
3415
|
console.error(generateBanner(SERVER_VERSION, activeModes));
|
|
3316
3416
|
const sourceDisplayInfos = buildSourceDisplayInfo(
|
|
3317
3417
|
sources,
|
|
3318
|
-
(sourceId) => getToolsForSource(sourceId).map((t) => t.name),
|
|
3418
|
+
(sourceId) => getToolsForSource(sourceId).map((t) => t.readonly ? `\u{1F512} ${t.name}` : t.name),
|
|
3319
3419
|
isDemo
|
|
3320
3420
|
);
|
|
3321
3421
|
console.error(generateStartupTable(sourceDisplayInfos));
|
|
@@ -3376,13 +3476,13 @@ See documentation for more details on configuring database connections.
|
|
|
3376
3476
|
app.listen(port, "0.0.0.0", () => {
|
|
3377
3477
|
if (process.env.NODE_ENV === "development") {
|
|
3378
3478
|
console.error("Development mode detected!");
|
|
3379
|
-
console.error("
|
|
3479
|
+
console.error(" Workbench dev server (with HMR): http://localhost:5173");
|
|
3380
3480
|
console.error(" Backend API: http://localhost:8080");
|
|
3381
3481
|
console.error("");
|
|
3382
3482
|
} else {
|
|
3383
|
-
console.error(`
|
|
3483
|
+
console.error(`Workbench at http://localhost:${port}/`);
|
|
3384
3484
|
}
|
|
3385
|
-
console.error(`MCP server endpoint at http://
|
|
3485
|
+
console.error(`MCP server endpoint at http://localhost:${port}/mcp`);
|
|
3386
3486
|
});
|
|
3387
3487
|
} else {
|
|
3388
3488
|
const server = createServer();
|