@bytebase/dbhub 0.16.1 → 0.18.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/dist/index.js
CHANGED
|
@@ -8,15 +8,19 @@ import {
|
|
|
8
8
|
getDatabaseTypeFromDSN,
|
|
9
9
|
getDefaultPortForType,
|
|
10
10
|
getToolRegistry,
|
|
11
|
+
initializeToolRegistry,
|
|
11
12
|
isDemoMode,
|
|
13
|
+
loadTomlConfig,
|
|
12
14
|
mapArgumentsToArray,
|
|
13
15
|
obfuscateDSNPassword,
|
|
14
16
|
parseConnectionInfoFromDSN,
|
|
15
17
|
resolvePort,
|
|
16
18
|
resolveSourceConfigs,
|
|
19
|
+
resolveTomlConfigPath,
|
|
17
20
|
resolveTransport,
|
|
21
|
+
splitSQLStatements,
|
|
18
22
|
stripCommentsAndStrings
|
|
19
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WCXOWHL3.js";
|
|
20
24
|
|
|
21
25
|
// src/connectors/postgres/index.ts
|
|
22
26
|
import pg from "pg";
|
|
@@ -141,6 +145,36 @@ var SQLRowLimiter = class {
|
|
|
141
145
|
}
|
|
142
146
|
};
|
|
143
147
|
|
|
148
|
+
// src/utils/identifier-quoter.ts
|
|
149
|
+
function quoteIdentifier(identifier, dbType) {
|
|
150
|
+
if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
|
|
151
|
+
throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
|
|
152
|
+
}
|
|
153
|
+
if (!identifier) {
|
|
154
|
+
throw new Error("Identifier cannot be empty");
|
|
155
|
+
}
|
|
156
|
+
switch (dbType) {
|
|
157
|
+
case "postgres":
|
|
158
|
+
case "sqlite":
|
|
159
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
160
|
+
case "mysql":
|
|
161
|
+
case "mariadb":
|
|
162
|
+
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
163
|
+
case "sqlserver":
|
|
164
|
+
return `[${identifier.replace(/]/g, "]]")}]`;
|
|
165
|
+
default:
|
|
166
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
|
|
170
|
+
const quotedTable = quoteIdentifier(tableName, dbType);
|
|
171
|
+
if (schemaName) {
|
|
172
|
+
const quotedSchema = quoteIdentifier(schemaName, dbType);
|
|
173
|
+
return `${quotedSchema}.${quotedTable}`;
|
|
174
|
+
}
|
|
175
|
+
return quotedTable;
|
|
176
|
+
}
|
|
177
|
+
|
|
144
178
|
// src/connectors/postgres/index.ts
|
|
145
179
|
var { Pool } = pg;
|
|
146
180
|
var PostgresDSNParser = class {
|
|
@@ -209,6 +243,8 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
209
243
|
this.pool = null;
|
|
210
244
|
// Source ID is set by ConnectorManager after cloning
|
|
211
245
|
this.sourceId = "default";
|
|
246
|
+
// Default schema for discovery methods (first entry from search_path, or "public")
|
|
247
|
+
this.defaultSchema = "public";
|
|
212
248
|
}
|
|
213
249
|
getId() {
|
|
214
250
|
return this.sourceId;
|
|
@@ -217,11 +253,21 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
217
253
|
return new _PostgresConnector();
|
|
218
254
|
}
|
|
219
255
|
async connect(dsn, initScript, config) {
|
|
256
|
+
this.defaultSchema = "public";
|
|
220
257
|
try {
|
|
221
258
|
const poolConfig = await this.dsnParser.parse(dsn, config);
|
|
222
259
|
if (config?.readonly) {
|
|
223
260
|
poolConfig.options = (poolConfig.options || "") + " -c default_transaction_read_only=on";
|
|
224
261
|
}
|
|
262
|
+
if (config?.searchPath) {
|
|
263
|
+
const schemas = config.searchPath.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
264
|
+
if (schemas.length > 0) {
|
|
265
|
+
this.defaultSchema = schemas[0];
|
|
266
|
+
const quotedSchemas = schemas.map((s) => quoteIdentifier(s, "postgres"));
|
|
267
|
+
const optionsValue = quotedSchemas.join(",").replace(/\\/g, "\\\\").replace(/ /g, "\\ ");
|
|
268
|
+
poolConfig.options = (poolConfig.options || "") + ` -c search_path=${optionsValue}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
225
271
|
this.pool = new Pool(poolConfig);
|
|
226
272
|
const client = await this.pool.connect();
|
|
227
273
|
client.release();
|
|
@@ -259,7 +305,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
259
305
|
}
|
|
260
306
|
const client = await this.pool.connect();
|
|
261
307
|
try {
|
|
262
|
-
const schemaToUse = schema ||
|
|
308
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
263
309
|
const result = await client.query(
|
|
264
310
|
`
|
|
265
311
|
SELECT table_name
|
|
@@ -280,7 +326,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
280
326
|
}
|
|
281
327
|
const client = await this.pool.connect();
|
|
282
328
|
try {
|
|
283
|
-
const schemaToUse = schema ||
|
|
329
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
284
330
|
const result = await client.query(
|
|
285
331
|
`
|
|
286
332
|
SELECT EXISTS (
|
|
@@ -302,7 +348,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
302
348
|
}
|
|
303
349
|
const client = await this.pool.connect();
|
|
304
350
|
try {
|
|
305
|
-
const schemaToUse = schema ||
|
|
351
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
306
352
|
const result = await client.query(
|
|
307
353
|
`
|
|
308
354
|
SELECT
|
|
@@ -350,18 +396,27 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
350
396
|
}
|
|
351
397
|
const client = await this.pool.connect();
|
|
352
398
|
try {
|
|
353
|
-
const schemaToUse = schema ||
|
|
399
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
354
400
|
const result = await client.query(
|
|
355
401
|
`
|
|
356
|
-
SELECT
|
|
357
|
-
column_name,
|
|
358
|
-
data_type,
|
|
359
|
-
is_nullable,
|
|
360
|
-
column_default
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
402
|
+
SELECT
|
|
403
|
+
c.column_name,
|
|
404
|
+
c.data_type,
|
|
405
|
+
c.is_nullable,
|
|
406
|
+
c.column_default,
|
|
407
|
+
pgd.description
|
|
408
|
+
FROM information_schema.columns c
|
|
409
|
+
LEFT JOIN pg_catalog.pg_namespace nsp
|
|
410
|
+
ON nsp.nspname = c.table_schema
|
|
411
|
+
LEFT JOIN pg_catalog.pg_class cls
|
|
412
|
+
ON cls.relnamespace = nsp.oid
|
|
413
|
+
AND cls.relname = c.table_name
|
|
414
|
+
LEFT JOIN pg_catalog.pg_description pgd
|
|
415
|
+
ON pgd.objoid = cls.oid
|
|
416
|
+
AND pgd.objsubid = c.ordinal_position
|
|
417
|
+
WHERE c.table_schema = $1
|
|
418
|
+
AND c.table_name = $2
|
|
419
|
+
ORDER BY c.ordinal_position
|
|
365
420
|
`,
|
|
366
421
|
[schemaToUse, tableName]
|
|
367
422
|
);
|
|
@@ -370,22 +425,82 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
370
425
|
client.release();
|
|
371
426
|
}
|
|
372
427
|
}
|
|
373
|
-
async
|
|
428
|
+
async getTableRowCount(tableName, schema) {
|
|
374
429
|
if (!this.pool) {
|
|
375
430
|
throw new Error("Not connected to database");
|
|
376
431
|
}
|
|
377
432
|
const client = await this.pool.connect();
|
|
378
433
|
try {
|
|
379
|
-
const schemaToUse = schema ||
|
|
434
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
380
435
|
const result = await client.query(
|
|
381
436
|
`
|
|
382
|
-
SELECT
|
|
437
|
+
SELECT c.reltuples::bigint as count
|
|
438
|
+
FROM pg_class c
|
|
439
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
440
|
+
WHERE c.relname = $1
|
|
441
|
+
AND n.nspname = $2
|
|
442
|
+
AND c.relkind IN ('r','p','m','f')
|
|
443
|
+
`,
|
|
444
|
+
[tableName, schemaToUse]
|
|
445
|
+
);
|
|
446
|
+
if (result.rows.length > 0) {
|
|
447
|
+
const count = Number(result.rows[0].count);
|
|
448
|
+
return count >= 0 ? count : null;
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
} finally {
|
|
452
|
+
client.release();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async getTableComment(tableName, schema) {
|
|
456
|
+
if (!this.pool) {
|
|
457
|
+
throw new Error("Not connected to database");
|
|
458
|
+
}
|
|
459
|
+
const client = await this.pool.connect();
|
|
460
|
+
try {
|
|
461
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
462
|
+
const result = await client.query(
|
|
463
|
+
`
|
|
464
|
+
SELECT obj_description(c.oid) as table_comment
|
|
465
|
+
FROM pg_catalog.pg_class c
|
|
466
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
467
|
+
WHERE c.relname = $1
|
|
468
|
+
AND n.nspname = $2
|
|
469
|
+
AND c.relkind IN ('r','p','m','f')
|
|
470
|
+
`,
|
|
471
|
+
[tableName, schemaToUse]
|
|
472
|
+
);
|
|
473
|
+
if (result.rows.length > 0) {
|
|
474
|
+
return result.rows[0].table_comment || null;
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
} finally {
|
|
478
|
+
client.release();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async getStoredProcedures(schema, routineType) {
|
|
482
|
+
if (!this.pool) {
|
|
483
|
+
throw new Error("Not connected to database");
|
|
484
|
+
}
|
|
485
|
+
const client = await this.pool.connect();
|
|
486
|
+
try {
|
|
487
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
488
|
+
const params = [schemaToUse];
|
|
489
|
+
let typeFilter = "";
|
|
490
|
+
if (routineType === "function") {
|
|
491
|
+
typeFilter = " AND routine_type = 'FUNCTION'";
|
|
492
|
+
} else if (routineType === "procedure") {
|
|
493
|
+
typeFilter = " AND routine_type = 'PROCEDURE'";
|
|
494
|
+
}
|
|
495
|
+
const result = await client.query(
|
|
496
|
+
`
|
|
497
|
+
SELECT
|
|
383
498
|
routine_name
|
|
384
499
|
FROM information_schema.routines
|
|
385
|
-
WHERE routine_schema = $1
|
|
500
|
+
WHERE routine_schema = $1${typeFilter}
|
|
386
501
|
ORDER BY routine_name
|
|
387
502
|
`,
|
|
388
|
-
|
|
503
|
+
params
|
|
389
504
|
);
|
|
390
505
|
return result.rows.map((row) => row.routine_name);
|
|
391
506
|
} finally {
|
|
@@ -398,7 +513,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
398
513
|
}
|
|
399
514
|
const client = await this.pool.connect();
|
|
400
515
|
try {
|
|
401
|
-
const schemaToUse = schema ||
|
|
516
|
+
const schemaToUse = schema || this.defaultSchema;
|
|
402
517
|
const result = await client.query(
|
|
403
518
|
`
|
|
404
519
|
SELECT
|
|
@@ -474,7 +589,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
474
589
|
}
|
|
475
590
|
const client = await this.pool.connect();
|
|
476
591
|
try {
|
|
477
|
-
const statements = sql2
|
|
592
|
+
const statements = splitSQLStatements(sql2, "postgres");
|
|
478
593
|
if (statements.length === 1) {
|
|
479
594
|
const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
|
|
480
595
|
let result;
|
|
@@ -789,33 +904,78 @@ var SQLServerConnector = class _SQLServerConnector {
|
|
|
789
904
|
const schemaToUse = schema || "dbo";
|
|
790
905
|
const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
|
|
791
906
|
const query = `
|
|
792
|
-
SELECT COLUMN_NAME as column_name,
|
|
793
|
-
DATA_TYPE as data_type,
|
|
794
|
-
IS_NULLABLE as is_nullable,
|
|
795
|
-
COLUMN_DEFAULT as column_default
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
907
|
+
SELECT c.COLUMN_NAME as column_name,
|
|
908
|
+
c.DATA_TYPE as data_type,
|
|
909
|
+
c.IS_NULLABLE as is_nullable,
|
|
910
|
+
c.COLUMN_DEFAULT as column_default,
|
|
911
|
+
ep.value as description
|
|
912
|
+
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
913
|
+
LEFT JOIN sys.columns sc
|
|
914
|
+
ON sc.name = c.COLUMN_NAME
|
|
915
|
+
AND sc.object_id = OBJECT_ID(QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME))
|
|
916
|
+
LEFT JOIN sys.extended_properties ep
|
|
917
|
+
ON ep.major_id = sc.object_id
|
|
918
|
+
AND ep.minor_id = sc.column_id
|
|
919
|
+
AND ep.name = 'MS_Description'
|
|
920
|
+
WHERE c.TABLE_NAME = @tableName
|
|
921
|
+
AND c.TABLE_SCHEMA = @schema
|
|
922
|
+
ORDER BY c.ORDINAL_POSITION
|
|
800
923
|
`;
|
|
801
924
|
const result = await request.query(query);
|
|
802
|
-
return result.recordset
|
|
925
|
+
return result.recordset.map((row) => ({
|
|
926
|
+
...row,
|
|
927
|
+
description: row.description || null
|
|
928
|
+
}));
|
|
803
929
|
} catch (error) {
|
|
804
930
|
throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
|
|
805
931
|
}
|
|
806
932
|
}
|
|
807
|
-
async
|
|
933
|
+
async getTableComment(tableName, schema) {
|
|
934
|
+
if (!this.connection) {
|
|
935
|
+
throw new Error("Not connected to SQL Server database");
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
const schemaToUse = schema || "dbo";
|
|
939
|
+
const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
|
|
940
|
+
const query = `
|
|
941
|
+
SELECT ep.value as table_comment
|
|
942
|
+
FROM sys.extended_properties ep
|
|
943
|
+
JOIN sys.tables t ON ep.major_id = t.object_id
|
|
944
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
945
|
+
WHERE ep.minor_id = 0
|
|
946
|
+
AND ep.name = 'MS_Description'
|
|
947
|
+
AND t.name = @tableName
|
|
948
|
+
AND s.name = @schema
|
|
949
|
+
`;
|
|
950
|
+
const result = await request.query(query);
|
|
951
|
+
if (result.recordset.length > 0) {
|
|
952
|
+
return result.recordset[0].table_comment || null;
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
} catch (error) {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async getStoredProcedures(schema, routineType) {
|
|
808
960
|
if (!this.connection) {
|
|
809
961
|
throw new Error("Not connected to SQL Server database");
|
|
810
962
|
}
|
|
811
963
|
try {
|
|
812
964
|
const schemaToUse = schema || "dbo";
|
|
813
965
|
const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
|
|
966
|
+
let typeFilter;
|
|
967
|
+
if (routineType === "function") {
|
|
968
|
+
typeFilter = "AND ROUTINE_TYPE = 'FUNCTION'";
|
|
969
|
+
} else if (routineType === "procedure") {
|
|
970
|
+
typeFilter = "AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
971
|
+
} else {
|
|
972
|
+
typeFilter = "AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')";
|
|
973
|
+
}
|
|
814
974
|
const query = `
|
|
815
975
|
SELECT ROUTINE_NAME
|
|
816
976
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
817
977
|
WHERE ROUTINE_SCHEMA = @schema
|
|
818
|
-
|
|
978
|
+
${typeFilter}
|
|
819
979
|
ORDER BY ROUTINE_NAME
|
|
820
980
|
`;
|
|
821
981
|
const result = await request.query(query);
|
|
@@ -948,38 +1108,6 @@ ConnectorRegistry.register(sqlServerConnector);
|
|
|
948
1108
|
|
|
949
1109
|
// src/connectors/sqlite/index.ts
|
|
950
1110
|
import Database from "better-sqlite3";
|
|
951
|
-
|
|
952
|
-
// src/utils/identifier-quoter.ts
|
|
953
|
-
function quoteIdentifier(identifier, dbType) {
|
|
954
|
-
if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
|
|
955
|
-
throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
|
|
956
|
-
}
|
|
957
|
-
if (!identifier) {
|
|
958
|
-
throw new Error("Identifier cannot be empty");
|
|
959
|
-
}
|
|
960
|
-
switch (dbType) {
|
|
961
|
-
case "postgres":
|
|
962
|
-
case "sqlite":
|
|
963
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
964
|
-
case "mysql":
|
|
965
|
-
case "mariadb":
|
|
966
|
-
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
967
|
-
case "sqlserver":
|
|
968
|
-
return `[${identifier.replace(/]/g, "]]")}]`;
|
|
969
|
-
default:
|
|
970
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
|
|
974
|
-
const quotedTable = quoteIdentifier(tableName, dbType);
|
|
975
|
-
if (schemaName) {
|
|
976
|
-
const quotedSchema = quoteIdentifier(schemaName, dbType);
|
|
977
|
-
return `${quotedSchema}.${quotedTable}`;
|
|
978
|
-
}
|
|
979
|
-
return quotedTable;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// src/connectors/sqlite/index.ts
|
|
983
1111
|
var SQLiteDSNParser = class {
|
|
984
1112
|
async parse(dsn, config) {
|
|
985
1113
|
if (!this.isValidDSN(dsn)) {
|
|
@@ -1181,14 +1309,15 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1181
1309
|
data_type: row.type,
|
|
1182
1310
|
// In SQLite, primary key columns are automatically NOT NULL even if notnull=0
|
|
1183
1311
|
is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
|
|
1184
|
-
column_default: row.dflt_value
|
|
1312
|
+
column_default: row.dflt_value,
|
|
1313
|
+
description: null
|
|
1185
1314
|
}));
|
|
1186
1315
|
return columns;
|
|
1187
1316
|
} catch (error) {
|
|
1188
1317
|
throw error;
|
|
1189
1318
|
}
|
|
1190
1319
|
}
|
|
1191
|
-
async getStoredProcedures(schema) {
|
|
1320
|
+
async getStoredProcedures(schema, routineType) {
|
|
1192
1321
|
if (!this.db) {
|
|
1193
1322
|
throw new Error("Not connected to SQLite database");
|
|
1194
1323
|
}
|
|
@@ -1207,7 +1336,7 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
1207
1336
|
throw new Error("Not connected to SQLite database");
|
|
1208
1337
|
}
|
|
1209
1338
|
try {
|
|
1210
|
-
const statements = sql2
|
|
1339
|
+
const statements = splitSQLStatements(sql2, "sqlite");
|
|
1211
1340
|
if (statements.length === 1) {
|
|
1212
1341
|
let processedStatement = statements[0];
|
|
1213
1342
|
const trimmedStatement = statements[0].toLowerCase().trim();
|
|
@@ -1566,11 +1695,12 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1566
1695
|
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1567
1696
|
const [rows] = await this.pool.query(
|
|
1568
1697
|
`
|
|
1569
|
-
SELECT
|
|
1570
|
-
COLUMN_NAME as column_name,
|
|
1571
|
-
DATA_TYPE as data_type,
|
|
1698
|
+
SELECT
|
|
1699
|
+
COLUMN_NAME as column_name,
|
|
1700
|
+
DATA_TYPE as data_type,
|
|
1572
1701
|
IS_NULLABLE as is_nullable,
|
|
1573
|
-
COLUMN_DEFAULT as column_default
|
|
1702
|
+
COLUMN_DEFAULT as column_default,
|
|
1703
|
+
COLUMN_COMMENT as description
|
|
1574
1704
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1575
1705
|
${schemaClause}
|
|
1576
1706
|
AND TABLE_NAME = ?
|
|
@@ -1578,24 +1708,57 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1578
1708
|
`,
|
|
1579
1709
|
queryParams
|
|
1580
1710
|
);
|
|
1581
|
-
return rows
|
|
1711
|
+
return rows.map((row) => ({
|
|
1712
|
+
...row,
|
|
1713
|
+
description: row.description || null
|
|
1714
|
+
}));
|
|
1582
1715
|
} catch (error) {
|
|
1583
1716
|
console.error("Error getting table schema:", error);
|
|
1584
1717
|
throw error;
|
|
1585
1718
|
}
|
|
1586
1719
|
}
|
|
1587
|
-
async
|
|
1720
|
+
async getTableComment(tableName, schema) {
|
|
1721
|
+
if (!this.pool) {
|
|
1722
|
+
throw new Error("Not connected to database");
|
|
1723
|
+
}
|
|
1724
|
+
try {
|
|
1725
|
+
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
1726
|
+
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1727
|
+
const [rows] = await this.pool.query(
|
|
1728
|
+
`
|
|
1729
|
+
SELECT TABLE_COMMENT
|
|
1730
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
1731
|
+
${schemaClause}
|
|
1732
|
+
AND TABLE_NAME = ?
|
|
1733
|
+
`,
|
|
1734
|
+
queryParams
|
|
1735
|
+
);
|
|
1736
|
+
if (rows.length > 0) {
|
|
1737
|
+
return rows[0].TABLE_COMMENT || null;
|
|
1738
|
+
}
|
|
1739
|
+
return null;
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async getStoredProcedures(schema, routineType) {
|
|
1588
1745
|
if (!this.pool) {
|
|
1589
1746
|
throw new Error("Not connected to database");
|
|
1590
1747
|
}
|
|
1591
1748
|
try {
|
|
1592
1749
|
const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
|
|
1593
1750
|
const queryParams = schema ? [schema] : [];
|
|
1751
|
+
let typeFilter = "";
|
|
1752
|
+
if (routineType === "function") {
|
|
1753
|
+
typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
|
|
1754
|
+
} else if (routineType === "procedure") {
|
|
1755
|
+
typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
1756
|
+
}
|
|
1594
1757
|
const [rows] = await this.pool.query(
|
|
1595
1758
|
`
|
|
1596
1759
|
SELECT ROUTINE_NAME
|
|
1597
1760
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
1598
|
-
${schemaClause}
|
|
1761
|
+
${schemaClause}${typeFilter}
|
|
1599
1762
|
ORDER BY ROUTINE_NAME
|
|
1600
1763
|
`,
|
|
1601
1764
|
queryParams
|
|
@@ -1719,7 +1882,7 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
1719
1882
|
try {
|
|
1720
1883
|
let processedSQL = sql2;
|
|
1721
1884
|
if (options.maxRows) {
|
|
1722
|
-
const statements = sql2
|
|
1885
|
+
const statements = splitSQLStatements(sql2, "mysql");
|
|
1723
1886
|
const processedStatements = statements.map(
|
|
1724
1887
|
(statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
|
|
1725
1888
|
);
|
|
@@ -1979,11 +2142,12 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
1979
2142
|
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
1980
2143
|
const rows = await this.pool.query(
|
|
1981
2144
|
`
|
|
1982
|
-
SELECT
|
|
1983
|
-
COLUMN_NAME as column_name,
|
|
1984
|
-
DATA_TYPE as data_type,
|
|
2145
|
+
SELECT
|
|
2146
|
+
COLUMN_NAME as column_name,
|
|
2147
|
+
DATA_TYPE as data_type,
|
|
1985
2148
|
IS_NULLABLE as is_nullable,
|
|
1986
|
-
COLUMN_DEFAULT as column_default
|
|
2149
|
+
COLUMN_DEFAULT as column_default,
|
|
2150
|
+
COLUMN_COMMENT as description
|
|
1987
2151
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1988
2152
|
${schemaClause}
|
|
1989
2153
|
AND TABLE_NAME = ?
|
|
@@ -1991,24 +2155,57 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
1991
2155
|
`,
|
|
1992
2156
|
queryParams
|
|
1993
2157
|
);
|
|
1994
|
-
return rows
|
|
2158
|
+
return rows.map((row) => ({
|
|
2159
|
+
...row,
|
|
2160
|
+
description: row.description || null
|
|
2161
|
+
}));
|
|
1995
2162
|
} catch (error) {
|
|
1996
2163
|
console.error("Error getting table schema:", error);
|
|
1997
2164
|
throw error;
|
|
1998
2165
|
}
|
|
1999
2166
|
}
|
|
2000
|
-
async
|
|
2167
|
+
async getTableComment(tableName, schema) {
|
|
2168
|
+
if (!this.pool) {
|
|
2169
|
+
throw new Error("Not connected to database");
|
|
2170
|
+
}
|
|
2171
|
+
try {
|
|
2172
|
+
const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
|
|
2173
|
+
const queryParams = schema ? [schema, tableName] : [tableName];
|
|
2174
|
+
const rows = await this.pool.query(
|
|
2175
|
+
`
|
|
2176
|
+
SELECT TABLE_COMMENT
|
|
2177
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
2178
|
+
${schemaClause}
|
|
2179
|
+
AND TABLE_NAME = ?
|
|
2180
|
+
`,
|
|
2181
|
+
queryParams
|
|
2182
|
+
);
|
|
2183
|
+
if (rows.length > 0) {
|
|
2184
|
+
return rows[0].TABLE_COMMENT || null;
|
|
2185
|
+
}
|
|
2186
|
+
return null;
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
return null;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
async getStoredProcedures(schema, routineType) {
|
|
2001
2192
|
if (!this.pool) {
|
|
2002
2193
|
throw new Error("Not connected to database");
|
|
2003
2194
|
}
|
|
2004
2195
|
try {
|
|
2005
2196
|
const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
|
|
2006
2197
|
const queryParams = schema ? [schema] : [];
|
|
2198
|
+
let typeFilter = "";
|
|
2199
|
+
if (routineType === "function") {
|
|
2200
|
+
typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
|
|
2201
|
+
} else if (routineType === "procedure") {
|
|
2202
|
+
typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
|
|
2203
|
+
}
|
|
2007
2204
|
const rows = await this.pool.query(
|
|
2008
2205
|
`
|
|
2009
2206
|
SELECT ROUTINE_NAME
|
|
2010
2207
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
2011
|
-
${schemaClause}
|
|
2208
|
+
${schemaClause}${typeFilter}
|
|
2012
2209
|
ORDER BY ROUTINE_NAME
|
|
2013
2210
|
`,
|
|
2014
2211
|
queryParams
|
|
@@ -2132,7 +2329,7 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
2132
2329
|
try {
|
|
2133
2330
|
let processedSQL = sql2;
|
|
2134
2331
|
if (options.maxRows) {
|
|
2135
|
-
const statements = sql2
|
|
2332
|
+
const statements = splitSQLStatements(sql2, "mariadb");
|
|
2136
2333
|
const processedStatements = statements.map(
|
|
2137
2334
|
(statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
|
|
2138
2335
|
);
|
|
@@ -2235,7 +2432,7 @@ var allowedKeywords = {
|
|
|
2235
2432
|
sqlserver: ["select", "with", "explain", "showplan"]
|
|
2236
2433
|
};
|
|
2237
2434
|
function isReadOnlySQL(sql2, connectorType) {
|
|
2238
|
-
const cleanedSQL = stripCommentsAndStrings(sql2).trim().toLowerCase();
|
|
2435
|
+
const cleanedSQL = stripCommentsAndStrings(sql2, connectorType).trim().toLowerCase();
|
|
2239
2436
|
if (!cleanedSQL) {
|
|
2240
2437
|
return true;
|
|
2241
2438
|
}
|
|
@@ -2328,11 +2525,8 @@ function trackToolRequest(metadata, startTime, extra, success, error) {
|
|
|
2328
2525
|
var executeSqlSchema = {
|
|
2329
2526
|
sql: z.string().describe("SQL to execute (multiple statements separated by ;)")
|
|
2330
2527
|
};
|
|
2331
|
-
function splitSQLStatements(sql2) {
|
|
2332
|
-
return sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
2333
|
-
}
|
|
2334
2528
|
function areAllStatementsReadOnly(sql2, connectorType) {
|
|
2335
|
-
const statements = splitSQLStatements(sql2);
|
|
2529
|
+
const statements = splitSQLStatements(sql2, connectorType);
|
|
2336
2530
|
return statements.every((statement) => isReadOnlySQL(statement, connectorType));
|
|
2337
2531
|
}
|
|
2338
2532
|
function createExecuteSqlToolHandler(sourceId) {
|
|
@@ -2389,7 +2583,7 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2389
2583
|
// src/tools/search-objects.ts
|
|
2390
2584
|
import { z as z2 } from "zod";
|
|
2391
2585
|
var searchDatabaseObjectsSchema = {
|
|
2392
|
-
object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Object type to search"),
|
|
2586
|
+
object_type: z2.enum(["schema", "table", "column", "procedure", "function", "index"]).describe("Object type to search"),
|
|
2393
2587
|
pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char). Default: %"),
|
|
2394
2588
|
schema: z2.string().optional().describe("Filter to schema"),
|
|
2395
2589
|
table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
|
|
@@ -2402,6 +2596,9 @@ function likePatternToRegex(pattern) {
|
|
|
2402
2596
|
}
|
|
2403
2597
|
async function getTableRowCount(connector, tableName, schemaName) {
|
|
2404
2598
|
try {
|
|
2599
|
+
if (connector.getTableRowCount) {
|
|
2600
|
+
return await connector.getTableRowCount(tableName, schemaName);
|
|
2601
|
+
}
|
|
2405
2602
|
const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
|
|
2406
2603
|
const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
|
|
2407
2604
|
const result = await connector.executeSQL(countQuery, { maxRows: 1 });
|
|
@@ -2413,6 +2610,16 @@ async function getTableRowCount(connector, tableName, schemaName) {
|
|
|
2413
2610
|
}
|
|
2414
2611
|
return null;
|
|
2415
2612
|
}
|
|
2613
|
+
async function getTableComment(connector, tableName, schemaName) {
|
|
2614
|
+
try {
|
|
2615
|
+
if (connector.getTableComment) {
|
|
2616
|
+
return await connector.getTableComment(tableName, schemaName);
|
|
2617
|
+
}
|
|
2618
|
+
return null;
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2416
2623
|
async function searchSchemas(connector, pattern, detailLevel, limit) {
|
|
2417
2624
|
const schemas = await connector.getSchemas();
|
|
2418
2625
|
const regex = likePatternToRegex(pattern);
|
|
@@ -2463,11 +2670,13 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
|
|
|
2463
2670
|
try {
|
|
2464
2671
|
const columns = await connector.getTableSchema(tableName, schemaName);
|
|
2465
2672
|
const rowCount = await getTableRowCount(connector, tableName, schemaName);
|
|
2673
|
+
const comment = await getTableComment(connector, tableName, schemaName);
|
|
2466
2674
|
results.push({
|
|
2467
2675
|
name: tableName,
|
|
2468
2676
|
schema: schemaName,
|
|
2469
2677
|
column_count: columns.length,
|
|
2470
|
-
row_count: rowCount
|
|
2678
|
+
row_count: rowCount,
|
|
2679
|
+
...comment ? { comment } : {}
|
|
2471
2680
|
});
|
|
2472
2681
|
} catch (error) {
|
|
2473
2682
|
results.push({
|
|
@@ -2482,16 +2691,19 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
|
|
|
2482
2691
|
const columns = await connector.getTableSchema(tableName, schemaName);
|
|
2483
2692
|
const indexes = await connector.getTableIndexes(tableName, schemaName);
|
|
2484
2693
|
const rowCount = await getTableRowCount(connector, tableName, schemaName);
|
|
2694
|
+
const comment = await getTableComment(connector, tableName, schemaName);
|
|
2485
2695
|
results.push({
|
|
2486
2696
|
name: tableName,
|
|
2487
2697
|
schema: schemaName,
|
|
2488
2698
|
column_count: columns.length,
|
|
2489
2699
|
row_count: rowCount,
|
|
2700
|
+
...comment ? { comment } : {},
|
|
2490
2701
|
columns: columns.map((col) => ({
|
|
2491
2702
|
name: col.column_name,
|
|
2492
2703
|
type: col.data_type,
|
|
2493
2704
|
nullable: col.is_nullable === "YES",
|
|
2494
|
-
default: col.column_default
|
|
2705
|
+
default: col.column_default,
|
|
2706
|
+
...col.description ? { description: col.description } : {}
|
|
2495
2707
|
})),
|
|
2496
2708
|
indexes: indexes.map((idx) => ({
|
|
2497
2709
|
name: idx.index_name,
|
|
@@ -2553,7 +2765,8 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
|
|
|
2553
2765
|
schema: schemaName,
|
|
2554
2766
|
type: column.data_type,
|
|
2555
2767
|
nullable: column.is_nullable === "YES",
|
|
2556
|
-
default: column.column_default
|
|
2768
|
+
default: column.column_default,
|
|
2769
|
+
...column.description ? { description: column.description } : {}
|
|
2557
2770
|
});
|
|
2558
2771
|
}
|
|
2559
2772
|
}
|
|
@@ -2567,7 +2780,7 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
|
|
|
2567
2780
|
}
|
|
2568
2781
|
return results;
|
|
2569
2782
|
}
|
|
2570
|
-
async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit) {
|
|
2783
|
+
async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit, routineType) {
|
|
2571
2784
|
const regex = likePatternToRegex(pattern);
|
|
2572
2785
|
const results = [];
|
|
2573
2786
|
let schemasToSearch;
|
|
@@ -2579,7 +2792,7 @@ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, l
|
|
|
2579
2792
|
for (const schemaName of schemasToSearch) {
|
|
2580
2793
|
if (results.length >= limit) break;
|
|
2581
2794
|
try {
|
|
2582
|
-
const procedures = await connector.getStoredProcedures(schemaName);
|
|
2795
|
+
const procedures = await connector.getStoredProcedures(schemaName, routineType);
|
|
2583
2796
|
const matched = procedures.filter((proc) => regex.test(proc));
|
|
2584
2797
|
for (const procName of matched) {
|
|
2585
2798
|
if (results.length >= limit) break;
|
|
@@ -2716,7 +2929,10 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2716
2929
|
results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
|
|
2717
2930
|
break;
|
|
2718
2931
|
case "procedure":
|
|
2719
|
-
results = await searchProcedures(connector, pattern, schema, detail_level, limit);
|
|
2932
|
+
results = await searchProcedures(connector, pattern, schema, detail_level, limit, "procedure");
|
|
2933
|
+
break;
|
|
2934
|
+
case "function":
|
|
2935
|
+
results = await searchProcedures(connector, pattern, schema, detail_level, limit, "function");
|
|
2720
2936
|
break;
|
|
2721
2937
|
case "index":
|
|
2722
2938
|
results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
|
|
@@ -2836,7 +3052,7 @@ function getSearchObjectsMetadata(sourceId) {
|
|
|
2836
3052
|
const isSingleSource = sourceIds.length === 1;
|
|
2837
3053
|
const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
|
|
2838
3054
|
const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`;
|
|
2839
|
-
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`;
|
|
3055
|
+
const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the '${sourceId}' ${dbType} database`;
|
|
2840
3056
|
return {
|
|
2841
3057
|
name: toolName,
|
|
2842
3058
|
description,
|
|
@@ -3340,6 +3556,95 @@ function buildSourceDisplayInfo(sourceConfigs, getToolsForSource2, isDemo) {
|
|
|
3340
3556
|
});
|
|
3341
3557
|
}
|
|
3342
3558
|
|
|
3559
|
+
// src/utils/config-watcher.ts
|
|
3560
|
+
import fs from "fs";
|
|
3561
|
+
var DEBOUNCE_MS = 500;
|
|
3562
|
+
function startConfigWatcher(options) {
|
|
3563
|
+
const { connectorManager, initialTools } = options;
|
|
3564
|
+
const configPath = resolveTomlConfigPath();
|
|
3565
|
+
if (!configPath) {
|
|
3566
|
+
return null;
|
|
3567
|
+
}
|
|
3568
|
+
let debounceTimer = null;
|
|
3569
|
+
let isReloading = false;
|
|
3570
|
+
let reloadPending = false;
|
|
3571
|
+
let lastGoodSources = connectorManager.getAllSourceConfigs();
|
|
3572
|
+
let lastGoodTools = initialTools;
|
|
3573
|
+
const scheduleReload = () => {
|
|
3574
|
+
if (debounceTimer) {
|
|
3575
|
+
clearTimeout(debounceTimer);
|
|
3576
|
+
}
|
|
3577
|
+
debounceTimer = setTimeout(reload, DEBOUNCE_MS);
|
|
3578
|
+
};
|
|
3579
|
+
const reload = async () => {
|
|
3580
|
+
if (isReloading) {
|
|
3581
|
+
reloadPending = true;
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
isReloading = true;
|
|
3585
|
+
reloadPending = false;
|
|
3586
|
+
try {
|
|
3587
|
+
console.error(`
|
|
3588
|
+
Detected change in ${configPath}, reloading configuration...`);
|
|
3589
|
+
const newConfig = loadTomlConfig();
|
|
3590
|
+
if (!newConfig) {
|
|
3591
|
+
console.error("Config reload: failed to load TOML config, keeping existing connections.");
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
const oldSources = lastGoodSources;
|
|
3595
|
+
const oldTools = lastGoodTools;
|
|
3596
|
+
await connectorManager.disconnect();
|
|
3597
|
+
try {
|
|
3598
|
+
await connectorManager.connectWithSources(newConfig.sources);
|
|
3599
|
+
initializeToolRegistry({
|
|
3600
|
+
sources: newConfig.sources,
|
|
3601
|
+
tools: newConfig.tools
|
|
3602
|
+
});
|
|
3603
|
+
lastGoodSources = newConfig.sources;
|
|
3604
|
+
lastGoodTools = newConfig.tools;
|
|
3605
|
+
console.error("Configuration reloaded successfully.");
|
|
3606
|
+
} catch (connectError) {
|
|
3607
|
+
console.error("Failed to connect with new config, rolling back:", connectError);
|
|
3608
|
+
try {
|
|
3609
|
+
await connectorManager.disconnect();
|
|
3610
|
+
} catch {
|
|
3611
|
+
}
|
|
3612
|
+
try {
|
|
3613
|
+
await connectorManager.connectWithSources(oldSources);
|
|
3614
|
+
initializeToolRegistry({ sources: oldSources, tools: oldTools });
|
|
3615
|
+
console.error("Rolled back to previous configuration.");
|
|
3616
|
+
} catch (rollbackError) {
|
|
3617
|
+
console.error("Rollback also failed, server has no active connections:", rollbackError);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
} catch (error) {
|
|
3621
|
+
console.error("Config reload failed, keeping existing connections:", error);
|
|
3622
|
+
} finally {
|
|
3623
|
+
isReloading = false;
|
|
3624
|
+
if (reloadPending) {
|
|
3625
|
+
reloadPending = false;
|
|
3626
|
+
scheduleReload();
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
};
|
|
3630
|
+
const watcher = fs.watch(configPath, (eventType) => {
|
|
3631
|
+
if (eventType === "change") {
|
|
3632
|
+
scheduleReload();
|
|
3633
|
+
}
|
|
3634
|
+
});
|
|
3635
|
+
watcher.unref?.();
|
|
3636
|
+
watcher.on("error", (err) => {
|
|
3637
|
+
console.error("Config file watcher error:", err);
|
|
3638
|
+
});
|
|
3639
|
+
console.error(`Watching ${configPath} for changes (hot reload enabled)`);
|
|
3640
|
+
return () => {
|
|
3641
|
+
if (debounceTimer) {
|
|
3642
|
+
clearTimeout(debounceTimer);
|
|
3643
|
+
}
|
|
3644
|
+
watcher.close();
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3343
3648
|
// src/server.ts
|
|
3344
3649
|
var __filename = fileURLToPath(import.meta.url);
|
|
3345
3650
|
var __dirname = path.dirname(__filename);
|
|
@@ -3392,12 +3697,16 @@ See documentation for more details on configuring database connections.
|
|
|
3392
3697
|
const sources = sourceConfigsData.sources;
|
|
3393
3698
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
3394
3699
|
await connectorManager.connectWithSources(sources);
|
|
3395
|
-
const { initializeToolRegistry } = await import("./registry-
|
|
3396
|
-
|
|
3700
|
+
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-NTKAVQCA.js");
|
|
3701
|
+
initializeToolRegistry2({
|
|
3397
3702
|
sources: sourceConfigsData.sources,
|
|
3398
3703
|
tools: sourceConfigsData.tools
|
|
3399
3704
|
});
|
|
3400
3705
|
console.error("Tool registry initialized");
|
|
3706
|
+
const stopConfigWatcher = startConfigWatcher({
|
|
3707
|
+
connectorManager,
|
|
3708
|
+
initialTools: sourceConfigsData.tools
|
|
3709
|
+
});
|
|
3401
3710
|
const createServer = () => {
|
|
3402
3711
|
const server = new McpServer({
|
|
3403
3712
|
name: SERVER_NAME,
|
|
@@ -3425,6 +3734,9 @@ See documentation for more details on configuring database connections.
|
|
|
3425
3734
|
isDemo
|
|
3426
3735
|
);
|
|
3427
3736
|
console.error(generateStartupTable(sourceDisplayInfos));
|
|
3737
|
+
process.on("exit", () => {
|
|
3738
|
+
stopConfigWatcher?.();
|
|
3739
|
+
});
|
|
3428
3740
|
if (transportData.type === "http") {
|
|
3429
3741
|
const app = express();
|
|
3430
3742
|
app.use(express.json());
|