@zz1996/dbhub-dameng 0.1.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 ADDED
@@ -0,0 +1,1841 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BUILTIN_TOOL_EXECUTE_SQL,
4
+ BUILTIN_TOOL_SEARCH_OBJECTS,
5
+ ConnectorManager,
6
+ classifyConnectionError,
7
+ getToolRegistry,
8
+ initializeToolRegistry,
9
+ isDemoMode,
10
+ loadTomlConfig,
11
+ mapArgumentsToArray,
12
+ resolveAllowedHosts,
13
+ resolveHost,
14
+ resolvePort,
15
+ resolveSourceConfigs,
16
+ resolveTomlConfigPath,
17
+ resolveTransport
18
+ } from "./chunk-2A2QF3CS.js";
19
+ import {
20
+ loadConnectors
21
+ } from "./chunk-WVVMH6FJ.js";
22
+ import {
23
+ quoteQualifiedIdentifier
24
+ } from "./chunk-IPK7BYBL.js";
25
+ import {
26
+ ConnectorRegistry,
27
+ getDatabaseTypeFromDSN,
28
+ getDefaultPortForType,
29
+ parseConnectionInfoFromDSN,
30
+ splitSQLStatements,
31
+ stripCommentsAndStrings
32
+ } from "./chunk-SQA2ISDE.js";
33
+
34
+ // src/server.ts
35
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
36
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
37
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
38
+ import express from "express";
39
+ import http from "http";
40
+ import path from "path";
41
+ import { readFileSync } from "fs";
42
+ import { fileURLToPath } from "url";
43
+
44
+ // src/tools/execute-sql.ts
45
+ import { z } from "zod";
46
+
47
+ // src/utils/response-formatter.ts
48
+ var MIN_SAFE_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
49
+ var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
50
+ function bigIntReplacer(_key, value) {
51
+ if (typeof value === "bigint") {
52
+ if (value >= MIN_SAFE_BIGINT && value <= MAX_SAFE_BIGINT) {
53
+ return Number(value);
54
+ }
55
+ return value.toString();
56
+ }
57
+ return value;
58
+ }
59
+ function formatSuccessResponse(data, meta = {}) {
60
+ return {
61
+ success: true,
62
+ data,
63
+ ...Object.keys(meta).length > 0 ? { meta } : {}
64
+ };
65
+ }
66
+ function formatErrorResponse(error, code = "ERROR", details) {
67
+ return {
68
+ success: false,
69
+ error,
70
+ code,
71
+ ...details ? { details } : {}
72
+ };
73
+ }
74
+ function createToolErrorResponse(error, code = "ERROR", details) {
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: JSON.stringify(formatErrorResponse(error, code, details), bigIntReplacer, 2),
80
+ mimeType: "application/json"
81
+ }
82
+ ],
83
+ isError: true
84
+ };
85
+ }
86
+ function createToolSuccessResponse(data, meta = {}) {
87
+ return {
88
+ content: [
89
+ {
90
+ type: "text",
91
+ text: JSON.stringify(formatSuccessResponse(data, meta), bigIntReplacer, 2),
92
+ mimeType: "application/json"
93
+ }
94
+ ]
95
+ };
96
+ }
97
+
98
+ // src/utils/allowed-keywords.ts
99
+ var allowedKeywords = {
100
+ postgres: ["select", "with", "explain", "show"],
101
+ mysql: ["select", "with", "explain", "show", "describe", "desc"],
102
+ mariadb: ["select", "with", "explain", "show", "describe", "desc"],
103
+ sqlite: ["select", "with", "explain", "pragma"],
104
+ // SQL Server has no native EXPLAIN statement; the connector translates a
105
+ // leading `EXPLAIN` into a SET SHOWPLAN_XML request (see SQLServerConnector).
106
+ sqlserver: ["select", "with", "explain"],
107
+ dameng: ["select", "with", "explain", "describe", "desc"]
108
+ };
109
+ var mutatingKeywords = [
110
+ "insert",
111
+ "update",
112
+ "delete",
113
+ "drop",
114
+ "alter",
115
+ "create",
116
+ "truncate",
117
+ "merge",
118
+ "grant",
119
+ "revoke",
120
+ "rename"
121
+ ];
122
+ var mutatingPattern = new RegExp(
123
+ `\\b(?:${mutatingKeywords.join("|")})\\b`,
124
+ "i"
125
+ );
126
+ var mutatingPatternWithReplace = new RegExp(
127
+ `\\b(?:${mutatingKeywords.join("|")}|replace\\s+(?:(?:low_priority|delayed)\\s+)?into)\\b`,
128
+ "i"
129
+ );
130
+ var mutatingPatterns = {
131
+ postgres: mutatingPattern,
132
+ mysql: mutatingPatternWithReplace,
133
+ mariadb: mutatingPatternWithReplace,
134
+ sqlite: mutatingPatternWithReplace,
135
+ sqlserver: mutatingPattern,
136
+ dameng: mutatingPattern
137
+ };
138
+ var selectIntoPattern = /\bselect\b[\s\S]+\binto\b/i;
139
+ var sqliteReadOnlyArgPragmas = /* @__PURE__ */ new Set([
140
+ "table_info",
141
+ "index_info",
142
+ "index_list",
143
+ "foreign_key_list"
144
+ ]);
145
+ var sqlitePragmaParenPattern = /^pragma\s+(?:[a-z0-9_]+\.)?([a-z0-9_]+)\s*\(/;
146
+ var explainAnalyzePattern = /^explain\s+(?:\([^)]*\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)[^)]*\)|\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)(?:\s+verbose\b)?)/i;
147
+ function isReadOnlySQL(sql, connectorType) {
148
+ return checkReadOnly(
149
+ stripCommentsAndStrings(sql, connectorType).trim().toLowerCase(),
150
+ connectorType
151
+ );
152
+ }
153
+ function checkReadOnly(cleanedSQL, connectorType) {
154
+ if (!cleanedSQL) {
155
+ return false;
156
+ }
157
+ const firstWord = cleanedSQL.match(/\S+/)?.[0] ?? "";
158
+ const keywordList = allowedKeywords[connectorType] || [];
159
+ if (!keywordList.includes(firstWord)) {
160
+ return false;
161
+ }
162
+ if (firstWord === "with") {
163
+ const pattern = mutatingPatterns[connectorType] ?? mutatingPattern;
164
+ if (pattern.test(cleanedSQL)) {
165
+ return false;
166
+ }
167
+ }
168
+ if (firstWord === "pragma" && connectorType === "sqlite") {
169
+ if (cleanedSQL.includes("=")) {
170
+ return false;
171
+ }
172
+ const parenMatch = cleanedSQL.match(sqlitePragmaParenPattern);
173
+ if (parenMatch && !sqliteReadOnlyArgPragmas.has(parenMatch[1])) {
174
+ return false;
175
+ }
176
+ }
177
+ if ((firstWord === "select" || firstWord === "with") && selectIntoPattern.test(cleanedSQL)) {
178
+ return false;
179
+ }
180
+ if (firstWord === "explain") {
181
+ const m = explainAnalyzePattern.exec(cleanedSQL);
182
+ if (m) {
183
+ const afterExplain = cleanedSQL.slice(m[0].length).trim();
184
+ if (afterExplain && !checkReadOnly(afterExplain, connectorType)) {
185
+ return false;
186
+ }
187
+ }
188
+ }
189
+ return true;
190
+ }
191
+
192
+ // src/requests/store.ts
193
+ var RequestStore = class {
194
+ constructor() {
195
+ this.store = /* @__PURE__ */ new Map();
196
+ this.maxPerSource = 100;
197
+ }
198
+ /**
199
+ * Add a request to the store
200
+ * Evicts oldest entry if at capacity
201
+ */
202
+ add(request) {
203
+ const requests = this.store.get(request.sourceId) ?? [];
204
+ requests.push(request);
205
+ if (requests.length > this.maxPerSource) {
206
+ requests.shift();
207
+ }
208
+ this.store.set(request.sourceId, requests);
209
+ }
210
+ /**
211
+ * Get requests, optionally filtered by source
212
+ * Returns newest first
213
+ */
214
+ getAll(sourceId) {
215
+ let requests;
216
+ if (sourceId) {
217
+ requests = [...this.store.get(sourceId) ?? []];
218
+ } else {
219
+ requests = Array.from(this.store.values()).flat();
220
+ }
221
+ return requests.sort(
222
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
223
+ );
224
+ }
225
+ /**
226
+ * Get total count of requests across all sources
227
+ */
228
+ getTotal() {
229
+ return Array.from(this.store.values()).reduce((sum, arr) => sum + arr.length, 0);
230
+ }
231
+ /**
232
+ * Clear all requests (useful for testing)
233
+ */
234
+ clear() {
235
+ this.store.clear();
236
+ }
237
+ };
238
+
239
+ // src/requests/index.ts
240
+ var requestStore = new RequestStore();
241
+
242
+ // src/utils/client-identifier.ts
243
+ function getClientIdentifier(extra) {
244
+ const userAgent = extra?.requestInfo?.headers?.["user-agent"];
245
+ if (userAgent) {
246
+ return userAgent;
247
+ }
248
+ return "stdio";
249
+ }
250
+
251
+ // src/utils/tool-handler-helpers.ts
252
+ function getEffectiveSourceId(sourceId) {
253
+ return sourceId || "default";
254
+ }
255
+ function createReadonlyViolationMessage(toolName, sourceId, connectorType) {
256
+ return `Tool '${toolName}' cannot execute in readonly mode for source '${sourceId}'. Only read-only SQL operations are allowed: ${allowedKeywords[connectorType]?.join(", ") || "none"}`;
257
+ }
258
+ function trackToolRequest(metadata, startTime, extra, success, error) {
259
+ requestStore.add({
260
+ id: crypto.randomUUID(),
261
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
262
+ sourceId: metadata.sourceId,
263
+ toolName: metadata.toolName,
264
+ sql: metadata.sql,
265
+ durationMs: Date.now() - startTime,
266
+ client: getClientIdentifier(extra),
267
+ success,
268
+ error
269
+ });
270
+ }
271
+ function tryClassifyConnectionError(error, rawSourceId, displaySourceId) {
272
+ let connectorType;
273
+ try {
274
+ connectorType = ConnectorManager.getSourceConfig(rawSourceId)?.type;
275
+ } catch {
276
+ return null;
277
+ }
278
+ if (!connectorType) return null;
279
+ const classified = classifyConnectionError(error, connectorType, displaySourceId);
280
+ if (!classified) return null;
281
+ return createToolErrorResponse(classified.message, classified.code, {
282
+ source_id: displaySourceId
283
+ });
284
+ }
285
+
286
+ // src/tools/execute-sql.ts
287
+ var executeSqlSchema = {
288
+ sql: z.string().describe("SQL to execute (multiple statements separated by ;)")
289
+ };
290
+ function areAllStatementsReadOnly(sql, connectorType) {
291
+ const statements = splitSQLStatements(sql, connectorType);
292
+ return statements.every((statement) => isReadOnlySQL(statement, connectorType));
293
+ }
294
+ function createExecuteSqlToolHandler(sourceId) {
295
+ return async (args, extra) => {
296
+ const { sql } = args;
297
+ const startTime = Date.now();
298
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
299
+ let success = true;
300
+ let errorMessage;
301
+ let result;
302
+ try {
303
+ await ConnectorManager.ensureConnected(sourceId);
304
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
305
+ const actualSourceId = connector.getId();
306
+ const registry = getToolRegistry();
307
+ const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, actualSourceId);
308
+ const isReadonly = toolConfig?.readonly === true;
309
+ if (isReadonly && !areAllStatementsReadOnly(sql, connector.id)) {
310
+ errorMessage = `Read-only mode is enabled. Only the following SQL operations are allowed: ${allowedKeywords[connector.id]?.join(", ") || "none"}`;
311
+ success = false;
312
+ return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
313
+ }
314
+ const executeOptions = {
315
+ readonly: toolConfig?.readonly,
316
+ maxRows: toolConfig?.max_rows
317
+ };
318
+ result = await connector.executeSQL(sql, executeOptions);
319
+ const responseData = {
320
+ rows: result.rows,
321
+ count: result.rowCount,
322
+ source_id: effectiveSourceId,
323
+ ...result.messages && result.messages.length > 0 ? { messages: result.messages } : {}
324
+ };
325
+ return createToolSuccessResponse(responseData);
326
+ } catch (error) {
327
+ success = false;
328
+ errorMessage = error.message;
329
+ const classified = tryClassifyConnectionError(error, sourceId, effectiveSourceId);
330
+ if (classified) return classified;
331
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
332
+ } finally {
333
+ trackToolRequest(
334
+ {
335
+ sourceId: effectiveSourceId,
336
+ toolName: effectiveSourceId === "default" ? "execute_sql" : `execute_sql_${effectiveSourceId}`,
337
+ sql
338
+ },
339
+ startTime,
340
+ extra,
341
+ success,
342
+ errorMessage
343
+ );
344
+ }
345
+ };
346
+ }
347
+
348
+ // src/tools/search-objects.ts
349
+ import { z as z2 } from "zod";
350
+ var searchDatabaseObjectsSchema = {
351
+ object_type: z2.enum(["schema", "table", "view", "column", "procedure", "function", "index"]).describe("Object type to search"),
352
+ pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char)"),
353
+ schema: z2.string().optional().describe("Filter to schema"),
354
+ table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
355
+ detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("Detail: names (minimal), summary (metadata), full (all)"),
356
+ limit: z2.number().int().positive().max(1e3).default(100).describe("Max results")
357
+ };
358
+ function likePatternToRegex(pattern) {
359
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
360
+ return new RegExp(`^${escaped}$`, "i");
361
+ }
362
+ async function resolveDefaultSchemas(connector) {
363
+ if (connector.getDefaultSchema) {
364
+ const defaultSchema = await connector.getDefaultSchema();
365
+ if (defaultSchema) {
366
+ return [defaultSchema];
367
+ }
368
+ }
369
+ return connector.getSchemas();
370
+ }
371
+ async function getTableRowCount(connector, tableName, schemaName) {
372
+ try {
373
+ if (connector.getTableRowCount) {
374
+ return await connector.getTableRowCount(tableName, schemaName);
375
+ }
376
+ const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
377
+ const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
378
+ const result = await connector.executeSQL(countQuery, { maxRows: 1 });
379
+ if (result.rows && result.rows.length > 0) {
380
+ return Number(result.rows[0].count || result.rows[0].COUNT || 0);
381
+ }
382
+ } catch (error) {
383
+ return null;
384
+ }
385
+ return null;
386
+ }
387
+ async function getTableComment(connector, tableName, schemaName) {
388
+ try {
389
+ if (connector.getTableComment) {
390
+ return await connector.getTableComment(tableName, schemaName);
391
+ }
392
+ return null;
393
+ } catch (error) {
394
+ return null;
395
+ }
396
+ }
397
+ async function searchSchemas(connector, pattern, detailLevel, limit) {
398
+ const schemas = await resolveDefaultSchemas(connector);
399
+ const regex = likePatternToRegex(pattern);
400
+ const matched = schemas.filter((schema) => regex.test(schema)).slice(0, limit);
401
+ if (detailLevel === "names") {
402
+ return matched.map((name) => ({ name }));
403
+ }
404
+ const results = await Promise.all(
405
+ matched.map(async (schemaName) => {
406
+ try {
407
+ const tables = await connector.getTables(schemaName);
408
+ return {
409
+ name: schemaName,
410
+ table_count: tables.length
411
+ };
412
+ } catch (error) {
413
+ return {
414
+ name: schemaName,
415
+ table_count: 0
416
+ };
417
+ }
418
+ })
419
+ );
420
+ return results;
421
+ }
422
+ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit) {
423
+ const regex = likePatternToRegex(pattern);
424
+ const results = [];
425
+ let schemasToSearch;
426
+ if (schemaFilter) {
427
+ schemasToSearch = [schemaFilter];
428
+ } else {
429
+ schemasToSearch = await resolveDefaultSchemas(connector);
430
+ }
431
+ for (const schemaName of schemasToSearch) {
432
+ if (results.length >= limit) break;
433
+ try {
434
+ const tables = await connector.getTables(schemaName);
435
+ const matched = tables.filter((table) => regex.test(table));
436
+ for (const tableName of matched) {
437
+ if (results.length >= limit) break;
438
+ if (detailLevel === "names") {
439
+ results.push({
440
+ name: tableName,
441
+ schema: schemaName
442
+ });
443
+ } else if (detailLevel === "summary") {
444
+ try {
445
+ const columns = await connector.getTableSchema(tableName, schemaName);
446
+ const rowCount = await getTableRowCount(connector, tableName, schemaName);
447
+ const comment = await getTableComment(connector, tableName, schemaName);
448
+ results.push({
449
+ name: tableName,
450
+ schema: schemaName,
451
+ column_count: columns.length,
452
+ row_count: rowCount,
453
+ ...comment ? { comment } : {}
454
+ });
455
+ } catch (error) {
456
+ results.push({
457
+ name: tableName,
458
+ schema: schemaName,
459
+ column_count: null,
460
+ row_count: null
461
+ });
462
+ }
463
+ } else {
464
+ try {
465
+ const columns = await connector.getTableSchema(tableName, schemaName);
466
+ const indexes = await connector.getTableIndexes(tableName, schemaName);
467
+ const rowCount = await getTableRowCount(connector, tableName, schemaName);
468
+ const comment = await getTableComment(connector, tableName, schemaName);
469
+ results.push({
470
+ name: tableName,
471
+ schema: schemaName,
472
+ column_count: columns.length,
473
+ row_count: rowCount,
474
+ ...comment ? { comment } : {},
475
+ columns: columns.map((col) => ({
476
+ name: col.column_name,
477
+ type: col.data_type,
478
+ nullable: col.is_nullable === "YES",
479
+ default: col.column_default,
480
+ ...col.description ? { description: col.description } : {}
481
+ })),
482
+ indexes: indexes.map((idx) => ({
483
+ name: idx.index_name,
484
+ columns: idx.column_names,
485
+ unique: idx.is_unique,
486
+ primary: idx.is_primary
487
+ }))
488
+ });
489
+ } catch (error) {
490
+ results.push({
491
+ name: tableName,
492
+ schema: schemaName,
493
+ error: `Unable to fetch full details: ${error.message}`
494
+ });
495
+ }
496
+ }
497
+ }
498
+ } catch (error) {
499
+ continue;
500
+ }
501
+ }
502
+ return results;
503
+ }
504
+ async function searchViews(connector, pattern, schemaFilter, detailLevel, limit) {
505
+ const regex = likePatternToRegex(pattern);
506
+ const results = [];
507
+ let schemasToSearch;
508
+ if (schemaFilter) {
509
+ schemasToSearch = [schemaFilter];
510
+ } else {
511
+ schemasToSearch = await connector.getSchemas();
512
+ }
513
+ for (const schemaName of schemasToSearch) {
514
+ if (results.length >= limit) break;
515
+ try {
516
+ const views = await connector.getViews(schemaName);
517
+ const matched = views.filter((view) => regex.test(view));
518
+ for (const viewName of matched) {
519
+ if (results.length >= limit) break;
520
+ if (detailLevel === "names") {
521
+ results.push({
522
+ name: viewName,
523
+ schema: schemaName
524
+ });
525
+ } else if (detailLevel === "summary") {
526
+ try {
527
+ const columns = await connector.getTableSchema(viewName, schemaName);
528
+ const comment = await getTableComment(connector, viewName, schemaName);
529
+ results.push({
530
+ name: viewName,
531
+ schema: schemaName,
532
+ column_count: columns.length,
533
+ ...comment ? { comment } : {}
534
+ });
535
+ } catch (error) {
536
+ results.push({
537
+ name: viewName,
538
+ schema: schemaName,
539
+ column_count: null
540
+ });
541
+ }
542
+ } else {
543
+ try {
544
+ const columns = await connector.getTableSchema(viewName, schemaName);
545
+ const comment = await getTableComment(connector, viewName, schemaName);
546
+ results.push({
547
+ name: viewName,
548
+ schema: schemaName,
549
+ column_count: columns.length,
550
+ ...comment ? { comment } : {},
551
+ columns: columns.map((col) => ({
552
+ name: col.column_name,
553
+ type: col.data_type,
554
+ nullable: col.is_nullable === "YES",
555
+ default: col.column_default,
556
+ ...col.description ? { description: col.description } : {}
557
+ })),
558
+ indexes: []
559
+ });
560
+ } catch (error) {
561
+ results.push({
562
+ name: viewName,
563
+ schema: schemaName,
564
+ error: `Unable to fetch full details: ${error.message}`
565
+ });
566
+ }
567
+ }
568
+ }
569
+ } catch (error) {
570
+ continue;
571
+ }
572
+ }
573
+ return results;
574
+ }
575
+ async function searchColumns(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
576
+ const regex = likePatternToRegex(pattern);
577
+ const results = [];
578
+ let schemasToSearch;
579
+ if (schemaFilter) {
580
+ schemasToSearch = [schemaFilter];
581
+ } else {
582
+ schemasToSearch = await resolveDefaultSchemas(connector);
583
+ }
584
+ for (const schemaName of schemasToSearch) {
585
+ if (results.length >= limit) break;
586
+ try {
587
+ let tablesToSearch;
588
+ if (tableFilter) {
589
+ tablesToSearch = [tableFilter];
590
+ } else {
591
+ const [tables, views] = await Promise.all([
592
+ connector.getTables(schemaName),
593
+ Promise.resolve(connector.getViews(schemaName)).catch(() => [])
594
+ ]);
595
+ tablesToSearch = [...tables, ...views ?? []];
596
+ }
597
+ for (const tableName of tablesToSearch) {
598
+ if (results.length >= limit) break;
599
+ try {
600
+ const columns = await connector.getTableSchema(tableName, schemaName);
601
+ const matchedColumns = columns.filter((col) => regex.test(col.column_name));
602
+ for (const column of matchedColumns) {
603
+ if (results.length >= limit) break;
604
+ if (detailLevel === "names") {
605
+ results.push({
606
+ name: column.column_name,
607
+ table: tableName,
608
+ schema: schemaName
609
+ });
610
+ } else {
611
+ results.push({
612
+ name: column.column_name,
613
+ table: tableName,
614
+ schema: schemaName,
615
+ type: column.data_type,
616
+ nullable: column.is_nullable === "YES",
617
+ default: column.column_default,
618
+ ...column.description ? { description: column.description } : {}
619
+ });
620
+ }
621
+ }
622
+ } catch (error) {
623
+ continue;
624
+ }
625
+ }
626
+ } catch (error) {
627
+ continue;
628
+ }
629
+ }
630
+ return results;
631
+ }
632
+ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit, routineType) {
633
+ const regex = likePatternToRegex(pattern);
634
+ const results = [];
635
+ let schemasToSearch;
636
+ if (schemaFilter) {
637
+ schemasToSearch = [schemaFilter];
638
+ } else {
639
+ schemasToSearch = await resolveDefaultSchemas(connector);
640
+ }
641
+ for (const schemaName of schemasToSearch) {
642
+ if (results.length >= limit) break;
643
+ try {
644
+ const procedures = await connector.getStoredProcedures(schemaName, routineType);
645
+ const matched = procedures.filter((proc) => regex.test(proc));
646
+ for (const procName of matched) {
647
+ if (results.length >= limit) break;
648
+ if (detailLevel === "names") {
649
+ results.push({
650
+ name: procName,
651
+ schema: schemaName
652
+ });
653
+ } else {
654
+ try {
655
+ const details = await connector.getStoredProcedureDetail(procName, schemaName);
656
+ results.push({
657
+ name: procName,
658
+ schema: schemaName,
659
+ type: details.procedure_type,
660
+ language: details.language,
661
+ parameters: detailLevel === "full" ? details.parameter_list : void 0,
662
+ return_type: details.return_type,
663
+ definition: detailLevel === "full" ? details.definition : void 0
664
+ });
665
+ } catch (error) {
666
+ results.push({
667
+ name: procName,
668
+ schema: schemaName,
669
+ error: `Unable to fetch details: ${error.message}`
670
+ });
671
+ }
672
+ }
673
+ }
674
+ } catch (error) {
675
+ continue;
676
+ }
677
+ }
678
+ return results;
679
+ }
680
+ async function searchIndexes(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
681
+ const regex = likePatternToRegex(pattern);
682
+ const results = [];
683
+ let schemasToSearch;
684
+ if (schemaFilter) {
685
+ schemasToSearch = [schemaFilter];
686
+ } else {
687
+ schemasToSearch = await resolveDefaultSchemas(connector);
688
+ }
689
+ for (const schemaName of schemasToSearch) {
690
+ if (results.length >= limit) break;
691
+ try {
692
+ let tablesToSearch;
693
+ if (tableFilter) {
694
+ tablesToSearch = [tableFilter];
695
+ } else {
696
+ tablesToSearch = await connector.getTables(schemaName);
697
+ }
698
+ for (const tableName of tablesToSearch) {
699
+ if (results.length >= limit) break;
700
+ try {
701
+ const indexes = await connector.getTableIndexes(tableName, schemaName);
702
+ const matchedIndexes = indexes.filter((idx) => regex.test(idx.index_name));
703
+ for (const index of matchedIndexes) {
704
+ if (results.length >= limit) break;
705
+ if (detailLevel === "names") {
706
+ results.push({
707
+ name: index.index_name,
708
+ table: tableName,
709
+ schema: schemaName
710
+ });
711
+ } else {
712
+ results.push({
713
+ name: index.index_name,
714
+ table: tableName,
715
+ schema: schemaName,
716
+ columns: index.column_names,
717
+ unique: index.is_unique,
718
+ primary: index.is_primary
719
+ });
720
+ }
721
+ }
722
+ } catch (error) {
723
+ continue;
724
+ }
725
+ }
726
+ } catch (error) {
727
+ continue;
728
+ }
729
+ }
730
+ return results;
731
+ }
732
+ function createSearchDatabaseObjectsToolHandler(sourceId) {
733
+ return async (args, extra) => {
734
+ const {
735
+ object_type,
736
+ pattern = "%",
737
+ schema,
738
+ table,
739
+ detail_level = "names",
740
+ limit = 100
741
+ } = args;
742
+ const startTime = Date.now();
743
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
744
+ let success = true;
745
+ let errorMessage;
746
+ try {
747
+ await ConnectorManager.ensureConnected(sourceId);
748
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
749
+ if (table) {
750
+ if (!schema) {
751
+ success = false;
752
+ errorMessage = "The 'table' parameter requires 'schema' to be specified";
753
+ return createToolErrorResponse(errorMessage, "SCHEMA_REQUIRED");
754
+ }
755
+ if (!["column", "index"].includes(object_type)) {
756
+ success = false;
757
+ errorMessage = `The 'table' parameter only applies to object_type 'column' or 'index', not '${object_type}'`;
758
+ return createToolErrorResponse(errorMessage, "INVALID_TABLE_FILTER");
759
+ }
760
+ }
761
+ if (schema) {
762
+ const schemas = await connector.getSchemas();
763
+ if (!schemas.includes(schema)) {
764
+ success = false;
765
+ errorMessage = `Schema '${schema}' does not exist. Available schemas: ${schemas.join(", ")}`;
766
+ return createToolErrorResponse(errorMessage, "SCHEMA_NOT_FOUND");
767
+ }
768
+ }
769
+ let results = [];
770
+ switch (object_type) {
771
+ case "schema":
772
+ results = await searchSchemas(connector, pattern, detail_level, limit);
773
+ break;
774
+ case "table":
775
+ results = await searchTables(connector, pattern, schema, detail_level, limit);
776
+ break;
777
+ case "view":
778
+ results = await searchViews(connector, pattern, schema, detail_level, limit);
779
+ break;
780
+ case "column":
781
+ results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
782
+ break;
783
+ case "procedure":
784
+ results = await searchProcedures(connector, pattern, schema, detail_level, limit, "procedure");
785
+ break;
786
+ case "function":
787
+ results = await searchProcedures(connector, pattern, schema, detail_level, limit, "function");
788
+ break;
789
+ case "index":
790
+ results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
791
+ break;
792
+ default:
793
+ success = false;
794
+ errorMessage = `Unsupported object_type: ${object_type}`;
795
+ return createToolErrorResponse(errorMessage, "INVALID_OBJECT_TYPE");
796
+ }
797
+ return createToolSuccessResponse({
798
+ object_type,
799
+ pattern,
800
+ schema,
801
+ table,
802
+ detail_level,
803
+ count: results.length,
804
+ results,
805
+ truncated: results.length === limit
806
+ });
807
+ } catch (error) {
808
+ success = false;
809
+ errorMessage = error.message;
810
+ const classified = tryClassifyConnectionError(error, sourceId, effectiveSourceId);
811
+ if (classified) return classified;
812
+ return createToolErrorResponse(
813
+ `Error searching database objects: ${errorMessage}`,
814
+ "SEARCH_ERROR"
815
+ );
816
+ } finally {
817
+ trackToolRequest(
818
+ {
819
+ sourceId: effectiveSourceId,
820
+ toolName: effectiveSourceId === "default" ? "search_objects" : `search_objects_${effectiveSourceId}`,
821
+ sql: `search_objects(object_type=${object_type}, pattern=${pattern}, schema=${schema || "all"}, table=${table || "all"}, detail_level=${detail_level})`
822
+ },
823
+ startTime,
824
+ extra,
825
+ success,
826
+ errorMessage
827
+ );
828
+ }
829
+ };
830
+ }
831
+
832
+ // src/utils/tool-metadata.ts
833
+ import { z as z3 } from "zod";
834
+
835
+ // src/utils/normalize-id.ts
836
+ function normalizeSourceId(id) {
837
+ return id.replace(/[^a-zA-Z0-9]/g, "_");
838
+ }
839
+
840
+ // src/utils/tool-metadata.ts
841
+ function buildSourceDescriptionPrefix(description) {
842
+ const trimmed = description?.trim() ?? "";
843
+ if (!trimmed) return "";
844
+ return /[.!?:]$/.test(trimmed) ? `${trimmed} ` : `${trimmed}. `;
845
+ }
846
+ function zodToParameters(schema) {
847
+ const parameters = [];
848
+ for (const [key, zodType] of Object.entries(schema)) {
849
+ const description = zodType.description || "";
850
+ const required = !(zodType instanceof z3.ZodOptional);
851
+ let type = "string";
852
+ if (zodType instanceof z3.ZodString) {
853
+ type = "string";
854
+ } else if (zodType instanceof z3.ZodNumber) {
855
+ type = "number";
856
+ } else if (zodType instanceof z3.ZodBoolean) {
857
+ type = "boolean";
858
+ } else if (zodType instanceof z3.ZodArray) {
859
+ type = "array";
860
+ } else if (zodType instanceof z3.ZodObject) {
861
+ type = "object";
862
+ }
863
+ parameters.push({
864
+ name: key,
865
+ type,
866
+ required,
867
+ description
868
+ });
869
+ }
870
+ return parameters;
871
+ }
872
+ function getExecuteSqlMetadata(sourceId) {
873
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
874
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
875
+ const dbType = sourceConfig.type;
876
+ const isSingleSource = sourceIds.length === 1;
877
+ const registry = getToolRegistry();
878
+ const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, sourceId);
879
+ const executeOptions = {
880
+ readonly: toolConfig?.readonly,
881
+ maxRows: toolConfig?.max_rows
882
+ };
883
+ const toolName = isSingleSource ? "execute_sql" : `execute_sql_${normalizeSourceId(sourceId)}`;
884
+ const title = isSingleSource ? `Execute SQL (${dbType})` : `Execute SQL on ${sourceId} (${dbType})`;
885
+ const userDescPrefix = buildSourceDescriptionPrefix(sourceConfig.description);
886
+ const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : "";
887
+ const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : "";
888
+ const description = isSingleSource ? `${userDescPrefix}Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `${userDescPrefix}Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`;
889
+ const isReadonly = executeOptions.readonly === true;
890
+ const annotations = {
891
+ title,
892
+ readOnlyHint: isReadonly,
893
+ destructiveHint: !isReadonly,
894
+ // Can be destructive if not readonly
895
+ // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes)
896
+ // In write mode, queries are definitely not idempotent
897
+ idempotentHint: false,
898
+ // Database operations are always against internal/closed systems, not open-world
899
+ openWorldHint: false
900
+ };
901
+ return {
902
+ name: toolName,
903
+ description,
904
+ schema: executeSqlSchema,
905
+ annotations
906
+ };
907
+ }
908
+ function getSearchObjectsMetadata(sourceId) {
909
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
910
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
911
+ const dbType = sourceConfig.type;
912
+ const isSingleSource = sourceIds.length === 1;
913
+ const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
914
+ const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`;
915
+ const userDescPrefix = buildSourceDescriptionPrefix(sourceConfig.description);
916
+ const description = isSingleSource ? `${userDescPrefix}Search and list database objects on the ${dbType} database` : `${userDescPrefix}Search and list database objects on the '${sourceId}' ${dbType} database`;
917
+ return {
918
+ name: toolName,
919
+ description,
920
+ title
921
+ };
922
+ }
923
+ function customParamsToToolParams(params) {
924
+ if (!params || params.length === 0) {
925
+ return [];
926
+ }
927
+ return params.map((param) => ({
928
+ name: param.name,
929
+ type: param.type,
930
+ required: param.required !== false && param.default === void 0,
931
+ description: param.description
932
+ }));
933
+ }
934
+ function buildExecuteSqlTool(sourceId, toolConfig) {
935
+ const executeSqlMetadata = getExecuteSqlMetadata(sourceId);
936
+ const executeSqlParameters = zodToParameters(executeSqlMetadata.schema);
937
+ const readonly = toolConfig && "readonly" in toolConfig ? toolConfig.readonly : void 0;
938
+ const max_rows = toolConfig && "max_rows" in toolConfig ? toolConfig.max_rows : void 0;
939
+ return {
940
+ name: executeSqlMetadata.name,
941
+ description: executeSqlMetadata.description,
942
+ parameters: executeSqlParameters,
943
+ readonly,
944
+ max_rows
945
+ };
946
+ }
947
+ function buildSearchObjectsTool(sourceId) {
948
+ const searchMetadata = getSearchObjectsMetadata(sourceId);
949
+ return {
950
+ name: searchMetadata.name,
951
+ description: searchMetadata.description,
952
+ parameters: [
953
+ {
954
+ name: "object_type",
955
+ type: "string",
956
+ required: true,
957
+ description: "Object type to search: schema, table, view, column, procedure, function, index"
958
+ },
959
+ {
960
+ name: "pattern",
961
+ type: "string",
962
+ required: false,
963
+ description: "LIKE pattern (% = any chars, _ = one char). Default: %"
964
+ },
965
+ {
966
+ name: "schema",
967
+ type: "string",
968
+ required: false,
969
+ description: "Filter to schema"
970
+ },
971
+ {
972
+ name: "table",
973
+ type: "string",
974
+ required: false,
975
+ description: "Filter to table (requires schema; column/index only)"
976
+ },
977
+ {
978
+ name: "detail_level",
979
+ type: "string",
980
+ required: false,
981
+ description: "Detail: names (minimal), summary (metadata), full (all)"
982
+ },
983
+ {
984
+ name: "limit",
985
+ type: "integer",
986
+ required: false,
987
+ description: "Max results (default: 100, max: 1000)"
988
+ }
989
+ ],
990
+ readonly: true
991
+ // search_objects is always readonly
992
+ };
993
+ }
994
+ function buildCustomTool(toolConfig) {
995
+ return {
996
+ name: toolConfig.name,
997
+ description: toolConfig.description,
998
+ parameters: customParamsToToolParams(toolConfig.parameters),
999
+ statement: toolConfig.statement,
1000
+ readonly: toolConfig.readonly,
1001
+ max_rows: toolConfig.max_rows
1002
+ };
1003
+ }
1004
+ function getToolsForSource(sourceId) {
1005
+ const registry = getToolRegistry();
1006
+ const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId);
1007
+ return enabledToolConfigs.map((toolConfig) => {
1008
+ if (toolConfig.name === "execute_sql") {
1009
+ return buildExecuteSqlTool(sourceId, toolConfig);
1010
+ } else if (toolConfig.name === "search_objects") {
1011
+ return buildSearchObjectsTool(sourceId);
1012
+ } else {
1013
+ return buildCustomTool(toolConfig);
1014
+ }
1015
+ });
1016
+ }
1017
+
1018
+ // src/tools/custom-tool-handler.ts
1019
+ import { z as z4 } from "zod";
1020
+ function buildZodSchemaFromParameters(parameters) {
1021
+ if (!parameters || parameters.length === 0) {
1022
+ return {};
1023
+ }
1024
+ const schemaShape = {};
1025
+ for (const param of parameters) {
1026
+ let fieldSchema;
1027
+ switch (param.type) {
1028
+ case "string":
1029
+ fieldSchema = z4.string().describe(param.description);
1030
+ break;
1031
+ case "integer":
1032
+ fieldSchema = z4.number().int().describe(param.description);
1033
+ break;
1034
+ case "float":
1035
+ fieldSchema = z4.number().describe(param.description);
1036
+ break;
1037
+ case "boolean":
1038
+ fieldSchema = z4.boolean().describe(param.description);
1039
+ break;
1040
+ case "array":
1041
+ fieldSchema = z4.array(z4.unknown()).describe(param.description);
1042
+ break;
1043
+ default:
1044
+ throw new Error(`Unsupported parameter type: ${param.type}`);
1045
+ }
1046
+ if (param.allowed_values && param.allowed_values.length > 0) {
1047
+ if (param.type === "string") {
1048
+ fieldSchema = z4.enum(param.allowed_values).describe(param.description);
1049
+ } else {
1050
+ fieldSchema = fieldSchema.refine(
1051
+ (val) => param.allowed_values.includes(val),
1052
+ {
1053
+ message: `Value must be one of: ${param.allowed_values.join(", ")}`
1054
+ }
1055
+ );
1056
+ }
1057
+ }
1058
+ if (param.default !== void 0 || param.required === false) {
1059
+ fieldSchema = fieldSchema.optional();
1060
+ }
1061
+ schemaShape[param.name] = fieldSchema;
1062
+ }
1063
+ return schemaShape;
1064
+ }
1065
+ function createCustomToolHandler(toolConfig) {
1066
+ const zodSchemaShape = buildZodSchemaFromParameters(toolConfig.parameters);
1067
+ const zodSchema = z4.object(zodSchemaShape);
1068
+ return async (args, extra) => {
1069
+ const startTime = Date.now();
1070
+ let success = true;
1071
+ let errorMessage;
1072
+ let paramValues = [];
1073
+ try {
1074
+ const validatedArgs = zodSchema.parse(args);
1075
+ await ConnectorManager.ensureConnected(toolConfig.source);
1076
+ const connector = ConnectorManager.getCurrentConnector(toolConfig.source);
1077
+ const executeOptions = {
1078
+ readonly: toolConfig.readonly,
1079
+ maxRows: toolConfig.max_rows
1080
+ };
1081
+ const isReadonly = executeOptions.readonly === true;
1082
+ if (isReadonly && !isReadOnlySQL(toolConfig.statement, connector.id)) {
1083
+ errorMessage = createReadonlyViolationMessage(toolConfig.name, toolConfig.source, connector.id);
1084
+ success = false;
1085
+ return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
1086
+ }
1087
+ paramValues = mapArgumentsToArray(
1088
+ toolConfig.parameters,
1089
+ validatedArgs
1090
+ );
1091
+ const result = await connector.executeSQL(
1092
+ toolConfig.statement,
1093
+ executeOptions,
1094
+ paramValues
1095
+ );
1096
+ const responseData = {
1097
+ rows: result.rows,
1098
+ count: result.rowCount,
1099
+ source_id: toolConfig.source
1100
+ };
1101
+ return createToolSuccessResponse(responseData);
1102
+ } catch (error) {
1103
+ success = false;
1104
+ errorMessage = error.message;
1105
+ const classified = tryClassifyConnectionError(error, toolConfig.source, toolConfig.source);
1106
+ if (classified) return classified;
1107
+ if (error instanceof z4.ZodError) {
1108
+ const issues = error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
1109
+ errorMessage = `Parameter validation failed: ${issues}`;
1110
+ } else {
1111
+ errorMessage = `${errorMessage}
1112
+
1113
+ SQL: ${toolConfig.statement}
1114
+ Parameters: ${JSON.stringify(paramValues)}`;
1115
+ }
1116
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
1117
+ } finally {
1118
+ trackToolRequest(
1119
+ {
1120
+ sourceId: toolConfig.source,
1121
+ toolName: toolConfig.name,
1122
+ sql: toolConfig.statement
1123
+ },
1124
+ startTime,
1125
+ extra,
1126
+ success,
1127
+ errorMessage
1128
+ );
1129
+ }
1130
+ };
1131
+ }
1132
+
1133
+ // src/tools/index.ts
1134
+ function registerTools(server) {
1135
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
1136
+ if (sourceIds.length === 0) {
1137
+ throw new Error("No database sources configured");
1138
+ }
1139
+ const registry = getToolRegistry();
1140
+ for (const sourceId of sourceIds) {
1141
+ const enabledTools = registry.getEnabledToolConfigs(sourceId);
1142
+ for (const toolConfig of enabledTools) {
1143
+ if (toolConfig.name === BUILTIN_TOOL_EXECUTE_SQL) {
1144
+ registerExecuteSqlTool(server, sourceId);
1145
+ } else if (toolConfig.name === BUILTIN_TOOL_SEARCH_OBJECTS) {
1146
+ registerSearchObjectsTool(server, sourceId);
1147
+ } else {
1148
+ registerCustomTool(server, sourceId, toolConfig);
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ function registerExecuteSqlTool(server, sourceId) {
1154
+ const metadata = getExecuteSqlMetadata(sourceId);
1155
+ server.registerTool(
1156
+ metadata.name,
1157
+ {
1158
+ description: metadata.description,
1159
+ inputSchema: metadata.schema,
1160
+ annotations: metadata.annotations
1161
+ },
1162
+ createExecuteSqlToolHandler(sourceId)
1163
+ );
1164
+ }
1165
+ function registerSearchObjectsTool(server, sourceId) {
1166
+ const metadata = getSearchObjectsMetadata(sourceId);
1167
+ server.registerTool(
1168
+ metadata.name,
1169
+ {
1170
+ description: metadata.description,
1171
+ inputSchema: searchDatabaseObjectsSchema,
1172
+ annotations: {
1173
+ title: metadata.title,
1174
+ readOnlyHint: true,
1175
+ destructiveHint: false,
1176
+ idempotentHint: true,
1177
+ openWorldHint: false
1178
+ }
1179
+ },
1180
+ createSearchDatabaseObjectsToolHandler(sourceId)
1181
+ );
1182
+ }
1183
+ function registerCustomTool(server, sourceId, toolConfig) {
1184
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
1185
+ const dbType = sourceConfig.type;
1186
+ const isReadOnly = isReadOnlySQL(toolConfig.statement, dbType);
1187
+ const zodSchema = buildZodSchemaFromParameters(toolConfig.parameters);
1188
+ server.registerTool(
1189
+ toolConfig.name,
1190
+ {
1191
+ description: toolConfig.description,
1192
+ inputSchema: zodSchema,
1193
+ annotations: {
1194
+ title: `${toolConfig.name} (${dbType})`,
1195
+ readOnlyHint: isReadOnly,
1196
+ destructiveHint: !isReadOnly,
1197
+ idempotentHint: isReadOnly,
1198
+ openWorldHint: false
1199
+ }
1200
+ },
1201
+ createCustomToolHandler(toolConfig)
1202
+ );
1203
+ }
1204
+
1205
+ // src/api/sources.ts
1206
+ function transformSourceConfig(source) {
1207
+ if (!source.type && source.dsn) {
1208
+ const inferredType = getDatabaseTypeFromDSN(source.dsn);
1209
+ if (inferredType) {
1210
+ source.type = inferredType;
1211
+ }
1212
+ }
1213
+ if (!source.type) {
1214
+ throw new Error(`Source ${source.id} is missing required type field`);
1215
+ }
1216
+ const dataSource = {
1217
+ id: source.id,
1218
+ type: source.type
1219
+ };
1220
+ if (source.description) {
1221
+ dataSource.description = source.description;
1222
+ }
1223
+ if (source.host) {
1224
+ dataSource.host = source.host;
1225
+ }
1226
+ if (source.port !== void 0) {
1227
+ dataSource.port = source.port;
1228
+ }
1229
+ if (source.database) {
1230
+ dataSource.database = source.database;
1231
+ }
1232
+ if (source.user) {
1233
+ dataSource.user = source.user;
1234
+ }
1235
+ if (source.ssh_host) {
1236
+ const sshTunnel = {
1237
+ enabled: true,
1238
+ ssh_host: source.ssh_host
1239
+ };
1240
+ if (source.ssh_port !== void 0) {
1241
+ sshTunnel.ssh_port = source.ssh_port;
1242
+ }
1243
+ if (source.ssh_user) {
1244
+ sshTunnel.ssh_user = source.ssh_user;
1245
+ }
1246
+ dataSource.ssh_tunnel = sshTunnel;
1247
+ }
1248
+ dataSource.tools = getToolsForSource(source.id);
1249
+ return dataSource;
1250
+ }
1251
+ function listSources(req, res) {
1252
+ try {
1253
+ const sourceConfigs = ConnectorManager.getAllSourceConfigs();
1254
+ const sources = sourceConfigs.map((config) => {
1255
+ return transformSourceConfig(config);
1256
+ });
1257
+ res.json(sources);
1258
+ } catch (error) {
1259
+ console.error("Error listing sources:", error);
1260
+ const errorResponse = {
1261
+ error: error instanceof Error ? error.message : "Internal server error"
1262
+ };
1263
+ res.status(500).json(errorResponse);
1264
+ }
1265
+ }
1266
+ function getSource(req, res) {
1267
+ try {
1268
+ const sourceId = req.params.sourceId;
1269
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
1270
+ if (!sourceConfig) {
1271
+ const errorResponse = {
1272
+ error: "Source not found",
1273
+ source_id: sourceId
1274
+ };
1275
+ res.status(404).json(errorResponse);
1276
+ return;
1277
+ }
1278
+ const dataSource = transformSourceConfig(sourceConfig);
1279
+ res.json(dataSource);
1280
+ } catch (error) {
1281
+ console.error(`Error getting source ${req.params.sourceId}:`, error);
1282
+ const errorResponse = {
1283
+ error: error instanceof Error ? error.message : "Internal server error"
1284
+ };
1285
+ res.status(500).json(errorResponse);
1286
+ }
1287
+ }
1288
+
1289
+ // src/api/requests.ts
1290
+ function listRequests(req, res) {
1291
+ try {
1292
+ const sourceId = req.query.source_id;
1293
+ const requests = requestStore.getAll(sourceId);
1294
+ res.json({
1295
+ requests,
1296
+ total: requests.length
1297
+ });
1298
+ } catch (error) {
1299
+ console.error("Error listing requests:", error);
1300
+ res.status(500).json({
1301
+ error: error instanceof Error ? error.message : "Internal server error"
1302
+ });
1303
+ }
1304
+ }
1305
+
1306
+ // src/utils/startup-table.ts
1307
+ var BOX = {
1308
+ topLeft: "\u250C",
1309
+ topRight: "\u2510",
1310
+ bottomLeft: "\u2514",
1311
+ bottomRight: "\u2518",
1312
+ horizontal: "\u2500",
1313
+ vertical: "\u2502",
1314
+ leftT: "\u251C",
1315
+ rightT: "\u2524",
1316
+ bullet: "\u2022"
1317
+ };
1318
+ function parseHostAndDatabase(source) {
1319
+ if (source.dsn) {
1320
+ const parsed = parseConnectionInfoFromDSN(source.dsn);
1321
+ if (parsed) {
1322
+ if (parsed.type === "sqlite") {
1323
+ return { host: "", database: parsed.database || ":memory:" };
1324
+ }
1325
+ if (!parsed.host) {
1326
+ return { host: "", database: parsed.database || "" };
1327
+ }
1328
+ const port = parsed.port ?? getDefaultPortForType(parsed.type);
1329
+ const host2 = port ? `${parsed.host}:${port}` : parsed.host;
1330
+ return { host: host2, database: parsed.database || "" };
1331
+ }
1332
+ return { host: "unknown", database: "" };
1333
+ }
1334
+ const host = source.host ? source.port ? `${source.host}:${source.port}` : source.host : "";
1335
+ const database = source.database || "";
1336
+ return { host, database };
1337
+ }
1338
+ function horizontalLine(width, left, right) {
1339
+ return left + BOX.horizontal.repeat(width - 2) + right;
1340
+ }
1341
+ function fitString(str, width) {
1342
+ if (str.length > width) {
1343
+ return str.slice(0, width - 1) + "\u2026";
1344
+ }
1345
+ return str.padEnd(width);
1346
+ }
1347
+ function formatHostDatabase(host, database) {
1348
+ return host ? database ? `${host}/${database}` : host : database || "";
1349
+ }
1350
+ function generateStartupTable(sources) {
1351
+ if (sources.length === 0) {
1352
+ return "";
1353
+ }
1354
+ const idTypeWidth = Math.max(
1355
+ 20,
1356
+ ...sources.map((s) => `${s.id} (${s.type})`.length)
1357
+ );
1358
+ const hostDbWidth = Math.max(
1359
+ 24,
1360
+ ...sources.map((s) => formatHostDatabase(s.host, s.database).length)
1361
+ );
1362
+ const modeWidth = Math.max(
1363
+ 10,
1364
+ ...sources.map((s) => {
1365
+ const modes = [];
1366
+ if (s.isDemo) modes.push("DEMO");
1367
+ if (s.readonly) modes.push("READ-ONLY");
1368
+ return modes.join(" ").length;
1369
+ })
1370
+ );
1371
+ const totalWidth = 2 + idTypeWidth + 3 + hostDbWidth + 3 + modeWidth + 2;
1372
+ const lines = [];
1373
+ for (let i = 0; i < sources.length; i++) {
1374
+ const source = sources[i];
1375
+ const isFirst = i === 0;
1376
+ const isLast = i === sources.length - 1;
1377
+ if (isFirst) {
1378
+ lines.push(horizontalLine(totalWidth, BOX.topLeft, BOX.topRight));
1379
+ }
1380
+ const idType = fitString(`${source.id} (${source.type})`, idTypeWidth);
1381
+ const hostDb = fitString(
1382
+ formatHostDatabase(source.host, source.database),
1383
+ hostDbWidth
1384
+ );
1385
+ const modes = [];
1386
+ if (source.isDemo) modes.push("DEMO");
1387
+ if (source.readonly) modes.push("READ-ONLY");
1388
+ const modeStr = fitString(modes.join(" "), modeWidth);
1389
+ lines.push(
1390
+ `${BOX.vertical} ${idType} ${BOX.vertical} ${hostDb} ${BOX.vertical} ${modeStr} ${BOX.vertical}`
1391
+ );
1392
+ lines.push(horizontalLine(totalWidth, BOX.leftT, BOX.rightT));
1393
+ for (const tool of source.tools) {
1394
+ const toolLine = ` ${BOX.bullet} ${tool}`;
1395
+ lines.push(
1396
+ `${BOX.vertical} ${fitString(toolLine, totalWidth - 4)} ${BOX.vertical}`
1397
+ );
1398
+ }
1399
+ if (isLast) {
1400
+ lines.push(horizontalLine(totalWidth, BOX.bottomLeft, BOX.bottomRight));
1401
+ } else {
1402
+ lines.push(horizontalLine(totalWidth, BOX.leftT, BOX.rightT));
1403
+ }
1404
+ }
1405
+ return lines.join("\n");
1406
+ }
1407
+ function buildSourceDisplayInfo(sourceConfigs, getToolsForSource2, isDemo) {
1408
+ return sourceConfigs.map((source) => {
1409
+ const { host, database } = parseHostAndDatabase(source);
1410
+ return {
1411
+ id: source.id,
1412
+ type: source.type || "sqlite",
1413
+ host,
1414
+ database,
1415
+ readonly: source.readonly || false,
1416
+ isDemo,
1417
+ tools: getToolsForSource2(source.id)
1418
+ };
1419
+ });
1420
+ }
1421
+
1422
+ // src/utils/config-watcher.ts
1423
+ import fs from "fs";
1424
+ var DEBOUNCE_MS = 500;
1425
+ function startConfigWatcher(options) {
1426
+ const { connectorManager, initialTools } = options;
1427
+ const configPath = resolveTomlConfigPath();
1428
+ if (!configPath) {
1429
+ return null;
1430
+ }
1431
+ let debounceTimer = null;
1432
+ let isReloading = false;
1433
+ let reloadPending = false;
1434
+ let lastGoodSources = connectorManager.getAllSourceConfigs();
1435
+ let lastGoodTools = initialTools;
1436
+ const scheduleReload = () => {
1437
+ if (debounceTimer) {
1438
+ clearTimeout(debounceTimer);
1439
+ }
1440
+ debounceTimer = setTimeout(reload, DEBOUNCE_MS);
1441
+ };
1442
+ const reload = async () => {
1443
+ if (isReloading) {
1444
+ reloadPending = true;
1445
+ return;
1446
+ }
1447
+ isReloading = true;
1448
+ reloadPending = false;
1449
+ try {
1450
+ console.error(`
1451
+ Detected change in ${configPath}, reloading configuration...`);
1452
+ const newConfig = loadTomlConfig();
1453
+ if (!newConfig) {
1454
+ console.error("Config reload: failed to load TOML config, keeping existing connections.");
1455
+ return;
1456
+ }
1457
+ const oldSources = lastGoodSources;
1458
+ const oldTools = lastGoodTools;
1459
+ await connectorManager.disconnect();
1460
+ try {
1461
+ await connectorManager.connectWithSources(newConfig.sources);
1462
+ initializeToolRegistry({
1463
+ sources: newConfig.sources,
1464
+ tools: newConfig.tools
1465
+ });
1466
+ lastGoodSources = newConfig.sources;
1467
+ lastGoodTools = newConfig.tools;
1468
+ console.error("Configuration reloaded successfully.");
1469
+ } catch (connectError) {
1470
+ console.error("Failed to connect with new config, rolling back:", connectError);
1471
+ try {
1472
+ await connectorManager.disconnect();
1473
+ } catch {
1474
+ }
1475
+ try {
1476
+ await connectorManager.connectWithSources(oldSources);
1477
+ initializeToolRegistry({ sources: oldSources, tools: oldTools });
1478
+ console.error("Rolled back to previous configuration.");
1479
+ } catch (rollbackError) {
1480
+ console.error("Rollback also failed, server has no active connections:", rollbackError);
1481
+ }
1482
+ }
1483
+ } catch (error) {
1484
+ console.error("Config reload failed, keeping existing connections:", error);
1485
+ } finally {
1486
+ isReloading = false;
1487
+ if (reloadPending) {
1488
+ reloadPending = false;
1489
+ scheduleReload();
1490
+ }
1491
+ }
1492
+ };
1493
+ const watcher = fs.watch(configPath, (eventType) => {
1494
+ if (eventType === "change") {
1495
+ scheduleReload();
1496
+ }
1497
+ });
1498
+ watcher.unref?.();
1499
+ watcher.on("error", (err) => {
1500
+ console.error("Config file watcher error:", err);
1501
+ });
1502
+ console.error(`Watching ${configPath} for changes (hot reload enabled)`);
1503
+ return () => {
1504
+ if (debounceTimer) {
1505
+ clearTimeout(debounceTimer);
1506
+ }
1507
+ watcher.close();
1508
+ };
1509
+ }
1510
+
1511
+ // src/utils/cross-origin.ts
1512
+ import os from "node:os";
1513
+ var INVALID_HOST_CHARS = /[\s/\\@?#]/;
1514
+ var LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
1515
+ var ALLOW_ANY_HOST = "*";
1516
+ function normalizeHost(raw) {
1517
+ const trimmed = raw.trim();
1518
+ if (!trimmed) return null;
1519
+ if (trimmed === ALLOW_ANY_HOST) return ALLOW_ANY_HOST;
1520
+ if (INVALID_HOST_CHARS.test(trimmed)) return null;
1521
+ try {
1522
+ const hostname = new URL(`http://${trimmed}`).hostname.toLowerCase();
1523
+ return hostname || null;
1524
+ } catch {
1525
+ return null;
1526
+ }
1527
+ }
1528
+ function getSelfHosts() {
1529
+ const hosts = [];
1530
+ try {
1531
+ const hostname = os.hostname().trim();
1532
+ if (hostname) hosts.push(hostname);
1533
+ } catch {
1534
+ }
1535
+ let interfaces = {};
1536
+ try {
1537
+ interfaces = os.networkInterfaces();
1538
+ } catch {
1539
+ }
1540
+ for (const addrs of Object.values(interfaces)) {
1541
+ for (const addr of addrs ?? []) {
1542
+ if (!addr.address || addr.internal) continue;
1543
+ const isIPv6 = addr.family === "IPv6" || addr.family === 6;
1544
+ if (isIPv6) {
1545
+ const bare = addr.address.split("%")[0];
1546
+ if (bare.toLowerCase().startsWith("fe80")) continue;
1547
+ hosts.push(`[${bare}]`);
1548
+ } else {
1549
+ hosts.push(addr.address);
1550
+ }
1551
+ }
1552
+ }
1553
+ return hosts;
1554
+ }
1555
+ function buildAllowedHosts(configured = [], bindHost, selfHosts = []) {
1556
+ const normalizedConfigured = configured.map(normalizeHost).filter((h) => h !== null);
1557
+ if (normalizedConfigured.includes(ALLOW_ANY_HOST)) {
1558
+ return /* @__PURE__ */ new Set([ALLOW_ANY_HOST]);
1559
+ }
1560
+ const hosts = /* @__PURE__ */ new Set();
1561
+ for (const h of LOOPBACK_HOSTS) {
1562
+ const normalized = normalizeHost(h);
1563
+ if (normalized) hosts.add(normalized);
1564
+ }
1565
+ const normalizedBind = bindHost ? normalizeHost(bindHost) : null;
1566
+ const bindIsWildcard = !normalizedBind || normalizedBind === "0.0.0.0" || normalizedBind === "[::]";
1567
+ if (normalizedBind && !bindIsWildcard) {
1568
+ hosts.add(normalizedBind);
1569
+ }
1570
+ if (bindIsWildcard) {
1571
+ for (const h of selfHosts) {
1572
+ const normalized = normalizeHost(h);
1573
+ if (normalized) hosts.add(normalized);
1574
+ }
1575
+ }
1576
+ for (const h of normalizedConfigured) hosts.add(h);
1577
+ return hosts;
1578
+ }
1579
+ function validateOrigin(originHeader, hostHeader, allowedHosts) {
1580
+ const allowAny = allowedHosts.has(ALLOW_ANY_HOST);
1581
+ const trimmedHost = (hostHeader ?? "").trim();
1582
+ if (!trimmedHost || INVALID_HOST_CHARS.test(trimmedHost)) {
1583
+ return { ok: false, status: 400, message: "Malformed Host header" };
1584
+ }
1585
+ let hostname;
1586
+ try {
1587
+ hostname = new URL(`http://${trimmedHost}`).hostname.toLowerCase();
1588
+ } catch {
1589
+ return { ok: false, status: 400, message: "Malformed Host header" };
1590
+ }
1591
+ if (!hostname) {
1592
+ return { ok: false, status: 400, message: "Malformed Host header" };
1593
+ }
1594
+ if (!allowAny && !allowedHosts.has(hostname)) {
1595
+ return {
1596
+ ok: false,
1597
+ status: 403,
1598
+ message: `Host '${hostname}' is not allowed. Only loopback is permitted by default; set --allowed-hosts (or DBHUB_ALLOWED_HOSTS) to serve other hostnames. This protects against DNS rebinding.`
1599
+ };
1600
+ }
1601
+ if (originHeader === void 0) return { ok: true };
1602
+ const trimmedOrigin = originHeader.trim();
1603
+ if (!trimmedOrigin) {
1604
+ return { ok: false, status: 400, message: "Malformed Origin header" };
1605
+ }
1606
+ let originHostname;
1607
+ try {
1608
+ originHostname = new URL(trimmedOrigin).hostname.toLowerCase();
1609
+ } catch {
1610
+ return { ok: false, status: 400, message: "Malformed Origin header" };
1611
+ }
1612
+ if (!originHostname) {
1613
+ return { ok: false, status: 400, message: "Malformed Origin header" };
1614
+ }
1615
+ if (!allowAny && !allowedHosts.has(originHostname)) {
1616
+ return {
1617
+ ok: false,
1618
+ status: 403,
1619
+ message: `Origin '${originHostname}' is not allowed`
1620
+ };
1621
+ }
1622
+ return { ok: true };
1623
+ }
1624
+
1625
+ // src/server.ts
1626
+ var __filename = fileURLToPath(import.meta.url);
1627
+ var __dirname = path.dirname(__filename);
1628
+ var packageJsonPath = path.join(__dirname, "..", "package.json");
1629
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1630
+ var SERVER_NAME = "DBHub MCP Server";
1631
+ var SERVER_VERSION = packageJson.version;
1632
+ function generateBanner(version, modes = []) {
1633
+ const modeText = modes.length > 0 ? ` [${modes.join(" | ")}]` : "";
1634
+ return `
1635
+ _____ ____ _ _ _
1636
+ | __ \\| _ \\| | | | | |
1637
+ | | | | |_) | |_| |_ _| |__
1638
+ | | | | _ <| _ | | | | '_ \\
1639
+ | |__| | |_) | | | | |_| | |_) |
1640
+ |_____/|____/|_| |_|\\__,_|_.__/
1641
+
1642
+ v${version}${modeText} - Minimal Database MCP Server
1643
+ `;
1644
+ }
1645
+ async function main() {
1646
+ try {
1647
+ const sourceConfigsData = await resolveSourceConfigs();
1648
+ if (!sourceConfigsData) {
1649
+ const samples = ConnectorRegistry.getAllSampleDSNs();
1650
+ const sampleFormats = Object.entries(samples).map(([id, dsn]) => ` - ${id}: ${dsn}`).join("\n");
1651
+ console.error(`
1652
+ ERROR: Database connection configuration is required.
1653
+ Please provide configuration in one of these ways (in order of priority):
1654
+
1655
+ 1. Use demo mode: --demo (uses in-memory SQLite with sample employee database)
1656
+ 2. TOML config file: --config=path/to/dbhub.toml or ./dbhub.toml
1657
+ 3. Command line argument: --dsn="your-connection-string"
1658
+ 4. Environment variable: export DSN="your-connection-string"
1659
+ 5. .env file: DSN=your-connection-string
1660
+
1661
+ Example DSN formats:
1662
+ ${sampleFormats}
1663
+
1664
+ Example TOML config (dbhub.toml):
1665
+ [[sources]]
1666
+ id = "my_db"
1667
+ dsn = "postgres://user:pass@localhost:5432/dbname"
1668
+
1669
+ See documentation for more details on configuring database connections.
1670
+ `);
1671
+ process.exit(1);
1672
+ }
1673
+ const connectorManager = new ConnectorManager();
1674
+ const sources = sourceConfigsData.sources;
1675
+ console.error(`Configuration source: ${sourceConfigsData.source}`);
1676
+ await connectorManager.connectWithSources(sources);
1677
+ const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-XSX2VZTD.js");
1678
+ initializeToolRegistry2({
1679
+ sources: sourceConfigsData.sources,
1680
+ tools: sourceConfigsData.tools
1681
+ });
1682
+ console.error("Tool registry initialized");
1683
+ const stopConfigWatcher = startConfigWatcher({
1684
+ connectorManager,
1685
+ initialTools: sourceConfigsData.tools
1686
+ });
1687
+ const createServer = () => {
1688
+ const server = new McpServer({
1689
+ name: SERVER_NAME,
1690
+ version: SERVER_VERSION
1691
+ });
1692
+ registerTools(server);
1693
+ return server;
1694
+ };
1695
+ const transportData = resolveTransport();
1696
+ const port = transportData.type === "http" ? resolvePort().port : null;
1697
+ const host = transportData.type === "http" ? resolveHost().host : null;
1698
+ const allowedHosts = transportData.type === "http" ? buildAllowedHosts(resolveAllowedHosts().hosts, host ?? void 0, getSelfHosts()) : /* @__PURE__ */ new Set();
1699
+ const activeModes = [];
1700
+ const modeDescriptions = [];
1701
+ const isDemo = isDemoMode();
1702
+ if (isDemo) {
1703
+ activeModes.push("DEMO");
1704
+ modeDescriptions.push("using sample employee database");
1705
+ }
1706
+ if (activeModes.length > 0) {
1707
+ console.error(`Running in ${activeModes.join(" and ")} mode - ${modeDescriptions.join(", ")}`);
1708
+ }
1709
+ console.error(generateBanner(SERVER_VERSION, activeModes));
1710
+ const sourceDisplayInfos = buildSourceDisplayInfo(
1711
+ sources,
1712
+ (sourceId) => getToolsForSource(sourceId).map((t) => t.readonly ? `\u{1F512} ${t.name}` : t.name),
1713
+ isDemo
1714
+ );
1715
+ console.error(generateStartupTable(sourceDisplayInfos));
1716
+ process.on("exit", () => {
1717
+ stopConfigWatcher?.();
1718
+ });
1719
+ if (transportData.type === "http") {
1720
+ const app = express();
1721
+ app.use(express.json());
1722
+ app.use((req, res, next) => {
1723
+ const origin = req.headers.origin;
1724
+ const result = validateOrigin(origin, req.headers.host, allowedHosts);
1725
+ if (!result.ok) {
1726
+ return res.status(result.status).json({
1727
+ error: result.status === 400 ? "Bad Request" : "Forbidden",
1728
+ message: result.message
1729
+ });
1730
+ }
1731
+ res.header("Access-Control-Allow-Origin", origin || "http://localhost");
1732
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1733
+ res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
1734
+ res.header("Access-Control-Allow-Credentials", "true");
1735
+ if (req.method === "OPTIONS") {
1736
+ return res.sendStatus(200);
1737
+ }
1738
+ next();
1739
+ });
1740
+ const frontendPath = path.join(__dirname, "public");
1741
+ app.use(express.static(frontendPath));
1742
+ app.get("/healthz", (req, res) => {
1743
+ res.status(200).send("OK");
1744
+ });
1745
+ app.get("/api/sources", listSources);
1746
+ app.get("/api/sources/:sourceId", getSource);
1747
+ app.get("/api/requests", listRequests);
1748
+ app.get("/mcp", (req, res) => {
1749
+ res.status(405).json({
1750
+ error: "Method Not Allowed",
1751
+ message: "SSE streaming is not supported in stateless mode. Use POST requests with JSON responses."
1752
+ });
1753
+ });
1754
+ app.post("/mcp", async (req, res) => {
1755
+ try {
1756
+ const transport = new StreamableHTTPServerTransport({
1757
+ sessionIdGenerator: void 0,
1758
+ // Disable session management for stateless mode
1759
+ enableJsonResponse: true
1760
+ // Use JSON responses (SSE not supported in stateless mode)
1761
+ });
1762
+ const server = createServer();
1763
+ await server.connect(transport);
1764
+ await transport.handleRequest(req, res, req.body);
1765
+ } catch (error) {
1766
+ console.error("Error handling request:", error);
1767
+ if (!res.headersSent) {
1768
+ res.status(500).json({ error: "Internal server error" });
1769
+ }
1770
+ }
1771
+ });
1772
+ if (process.env.NODE_ENV !== "development") {
1773
+ app.get("*", (req, res) => {
1774
+ res.sendFile(path.join(frontendPath, "index.html"));
1775
+ });
1776
+ }
1777
+ const httpServer = http.createServer(app);
1778
+ httpServer.on("error", (err) => {
1779
+ const displayHost = host.includes(":") ? `[${host}]` : host;
1780
+ console.error(`Failed to bind HTTP server to ${displayHost}:${port}: ${err.message}`);
1781
+ process.exit(1);
1782
+ });
1783
+ httpServer.listen(port, host, () => {
1784
+ const address = httpServer.address();
1785
+ const boundHost = typeof address === "object" && address ? address.address : host;
1786
+ const boundPort = typeof address === "object" && address ? address.port : port;
1787
+ const displayHost = boundHost.includes(":") ? `[${boundHost}]` : boundHost;
1788
+ const userHost = boundHost === "0.0.0.0" || boundHost === "::" ? "localhost" : displayHost;
1789
+ console.error(`HTTP server listening on ${displayHost}:${boundPort}`);
1790
+ if (allowedHosts.has(ALLOW_ANY_HOST)) {
1791
+ console.error("Allowed hosts: * (DNS-rebinding protection DISABLED \u2014 ensure DBHub is fronted by your own auth/proxy)");
1792
+ } else {
1793
+ console.error(`Allowed hosts: ${[...allowedHosts].join(", ")} (set --allowed-hosts to serve other hostnames)`);
1794
+ }
1795
+ if (process.env.NODE_ENV === "development") {
1796
+ console.error("Development mode detected!");
1797
+ console.error(" Workbench dev server (with HMR): http://localhost:5173");
1798
+ console.error(` Backend API: http://localhost:${boundPort}`);
1799
+ console.error("");
1800
+ } else {
1801
+ console.error(`Workbench at http://${userHost}:${boundPort}/`);
1802
+ }
1803
+ console.error(`MCP server endpoint at http://${userHost}:${boundPort}/mcp`);
1804
+ });
1805
+ } else {
1806
+ const server = createServer();
1807
+ const transport = new StdioServerTransport();
1808
+ await server.connect(transport);
1809
+ console.error("MCP server running on stdio");
1810
+ let isShuttingDown = false;
1811
+ const shutdown = async () => {
1812
+ if (isShuttingDown) return;
1813
+ isShuttingDown = true;
1814
+ console.error("Shutting down...");
1815
+ await transport.close();
1816
+ await connectorManager.disconnect();
1817
+ process.exit(0);
1818
+ };
1819
+ process.on("SIGINT", shutdown);
1820
+ process.on("SIGTERM", shutdown);
1821
+ process.stdin.on("end", shutdown);
1822
+ }
1823
+ } catch (err) {
1824
+ console.error("Fatal error:", err);
1825
+ process.exit(1);
1826
+ }
1827
+ }
1828
+
1829
+ // src/index.ts
1830
+ var connectorModules = [
1831
+ { load: () => import("./postgres-VFNLBWMF.js"), name: "PostgreSQL", driver: "pg" },
1832
+ { load: () => import("./sqlserver-OE4SFH4B.js"), name: "SQL Server", driver: "mssql" },
1833
+ { load: () => import("./sqlite-IOUAYHGE.js"), name: "SQLite", driver: "node:sqlite" },
1834
+ { load: () => import("./mysql-A43SL7UM.js"), name: "MySQL", driver: "mysql2" },
1835
+ { load: () => import("./mariadb-7F72IRB4.js"), name: "MariaDB", driver: "mariadb" },
1836
+ { load: () => import("./dameng-DC7OP4VV.js"), name: "Dameng", driver: "dmdb" }
1837
+ ];
1838
+ loadConnectors(connectorModules).then(() => main()).catch((error) => {
1839
+ console.error("Fatal error:", error);
1840
+ process.exit(1);
1841
+ });