@bytebase/dbhub 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  > [!NOTE]
2
2
  > Brought to you by [Bytebase](https://www.bytebase.com/), open-source database governance platform.
3
3
 
4
+ <p align="center">
5
+ <a href="https://www.star-history.com/bytebase/dbhub">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending&theme=dark" />
8
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending" />
9
+ <img alt="GitHub Trending Repository of the Day" src="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending" />
10
+ </picture>
11
+ </a>
12
+ </p>
13
+
4
14
  <p align="center">
5
15
  <a href="https://dbhub.ai/" target="_blank">
6
16
  <picture>
@@ -30,14 +40,33 @@
30
40
  MCP Clients MCP Server Databases
31
41
  ```
32
42
 
33
- DBHub is a token efficient, zero-dependency MCP server implementing the Model Context Protocol (MCP) server interface. This lightweight gateway allows MCP-compatible clients to connect to and explore different databases:
43
+ DBHub is a minimal MCP server: token-efficient, zero-dependency, and just two tools by default with opt-in extras. This lightweight gateway allows MCP-compatible clients to connect to and explore different databases:
34
44
 
35
- - **Local Development First**: Zero dependency, token efficient with a minimal set of MCP tools to maximize context window
45
+ - **Minimal**: Zero dependency, token efficient with a minimal set of MCP tools to maximize context window
36
46
  - **Multi-Database**: PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite through a single interface
37
47
  - **Multi-Connection**: Connect to multiple databases simultaneously with TOML configuration
38
48
  - **Guardrails**: Read-only mode, row limiting, and query timeout to prevent runaway operations
39
49
  - **Secure Access**: SSH tunneling and SSL/TLS encryption
40
50
 
51
+ > DBHub is the official example in the [Claude Code docs](https://code.claude.com/docs/en/mcp#example-query-your-postgresql-database) for connecting to PostgreSQL via MCP.
52
+
53
+ ## Token Efficiency
54
+
55
+ DBHub loads just 2 tools by default at **1.4k tokens** — 13-14x fewer than alternatives — keeping the context window open for your actual work.
56
+
57
+ | MCP Server | Default Config | Default Tools |
58
+ |------------|---------------|--------------|
59
+ | **DBHub** | **1.4k** | 2 (`execute_sql`, `search_objects`) |
60
+ | MCP Toolbox | 19.0k | 28 |
61
+ | Supabase MCP | 19.3k | all |
62
+
63
+ ## Use Cases
64
+
65
+ - **Local Development**: Schema exploration, query validation, and data debugging with Claude Code, VS Code, Cursor, etc.
66
+ - **Non-Technical Access**: Expose curated, read-only views to non-technical staff via Claude Desktop, VS Code, Cursor, etc.
67
+ - **Multi-Database Consolidation**: Replace separate MCP servers for each database with a single DBHub process
68
+ - **Production Troubleshooting**: Read-only diagnostics with guardrails against runaway queries
69
+
41
70
  ## Supported Databases
42
71
 
43
72
  PostgreSQL, MySQL, SQL Server, MariaDB, and SQLite.
@@ -8,7 +8,7 @@ import {
8
8
  getDefaultPortForType,
9
9
  parseConnectionInfoFromDSN,
10
10
  stripCommentsAndStrings
11
- } from "./chunk-WIQ4DWGY.js";
11
+ } from "./chunk-MF6LSVWK.js";
12
12
 
13
13
  // src/tools/builtin-tools.ts
14
14
  var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
@@ -1307,6 +1307,18 @@ function validateSourceConfig(source, configPath) {
1307
1307
  );
1308
1308
  }
1309
1309
  }
1310
+ if (source.aws_profile !== void 0) {
1311
+ if (typeof source.aws_profile !== "string" || source.aws_profile.trim().length === 0) {
1312
+ throw new Error(
1313
+ `Configuration file ${configPath}: source '${source.id}' has invalid aws_profile. Must be a non-empty string.`
1314
+ );
1315
+ }
1316
+ if (source.aws_iam_auth !== true) {
1317
+ throw new Error(
1318
+ `Configuration file ${configPath}: source '${source.id}' aws_profile requires aws_iam_auth = true.`
1319
+ );
1320
+ }
1321
+ }
1310
1322
  if (source.aws_iam_auth === true) {
1311
1323
  const validIamTypes = ["postgres", "mysql", "mariadb"];
1312
1324
  if (!source.type || !validIamTypes.includes(source.type)) {
@@ -1677,12 +1689,17 @@ async function generateRdsAuthToken(params) {
1677
1689
  }
1678
1690
  throw error;
1679
1691
  }
1680
- const signer = new Signer({
1692
+ const signerConfig = {
1681
1693
  hostname: params.hostname,
1682
1694
  port: params.port,
1683
1695
  username: params.username,
1684
1696
  region: params.region
1685
- });
1697
+ };
1698
+ if (params.profile) {
1699
+ const { fromIni } = await import("@aws-sdk/credential-providers");
1700
+ signerConfig.credentials = fromIni({ profile: params.profile });
1701
+ }
1702
+ const signer = new Signer(signerConfig);
1686
1703
  return signer.getAuthToken();
1687
1704
  }
1688
1705
 
@@ -2158,7 +2175,8 @@ var ConnectorManager = class {
2158
2175
  hostname,
2159
2176
  port,
2160
2177
  username,
2161
- region: source.aws_region
2178
+ region: source.aws_region,
2179
+ profile: source.aws_profile
2162
2180
  });
2163
2181
  const queryParams = new Map(parsed.searchParams);
2164
2182
  const currentSslMode = queryParams.get("sslmode");
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  blankCommentsAndStrings,
3
3
  stripCommentsAndStrings
4
- } from "./chunk-WIQ4DWGY.js";
4
+ } from "./chunk-MF6LSVWK.js";
5
5
 
6
6
  // src/utils/sql-row-limiter.ts
7
7
  var SQLRowLimiter = class {
@@ -183,6 +183,61 @@ LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
183
183
  }
184
184
  return this.applyTopToQuery(sql, maxRows);
185
185
  }
186
+ /**
187
+ * Like applyMaxRows, but requests one probe row (maxRows + 1) whenever the
188
+ * cap is the binding constraint, so callers can detect truncation exactly
189
+ * instead of guessing from `rowCount === maxRows` (which a table with
190
+ * exactly maxRows rows also produces).
191
+ *
192
+ * `probeApplied` is false when the cap cannot fire: no maxRows, not a
193
+ * SELECT, or the query's own literal LIMIT is already within the cap (the
194
+ * user asked for fewer rows than the cap — that is their limit, not
195
+ * truncation).
196
+ */
197
+ static applyMaxRowsWithTruncationProbe(sql, maxRows) {
198
+ if (!maxRows || !this.isSelectQuery(sql)) {
199
+ return { sql, probeApplied: false };
200
+ }
201
+ if (!this.hasParameterizedLimit(sql)) {
202
+ const existingLimit = this.extractLimitValue(sql);
203
+ if (existingLimit !== null && existingLimit <= maxRows) {
204
+ return { sql, probeApplied: false };
205
+ }
206
+ }
207
+ return { sql: this.applyMaxRows(sql, maxRows + 1), probeApplied: true };
208
+ }
209
+ /**
210
+ * SQL Server variant of applyMaxRowsWithTruncationProbe, using TOP syntax.
211
+ * A set-operator statement is always wrapped (any TOP on an individual
212
+ * branch caps only that branch, not the combined output), so the probe
213
+ * applies there regardless of branch-level TOPs.
214
+ */
215
+ static applyMaxRowsForSQLServerWithTruncationProbe(sql, maxRows) {
216
+ if (!maxRows || !this.isSelectQuery(sql)) {
217
+ return { sql, probeApplied: false };
218
+ }
219
+ if (!this.hasSetOperator(sql)) {
220
+ const existingTop = this.extractTopValue(sql);
221
+ if (existingTop !== null && existingTop <= maxRows) {
222
+ return { sql, probeApplied: false };
223
+ }
224
+ }
225
+ return { sql: this.applyMaxRowsForSQLServer(sql, maxRows + 1), probeApplied: true };
226
+ }
227
+ /**
228
+ * Post-process a result set produced by a truncation-probe rewrite: when
229
+ * the probe row came back (more than maxRows rows), drop it, clamp the
230
+ * count to maxRows, and mark the set truncated so consumers can tell a
231
+ * capped result from a complete one.
232
+ */
233
+ static flagTruncation(resultSet, maxRows, probeApplied) {
234
+ if (!probeApplied || !maxRows || resultSet.rows.length <= maxRows) {
235
+ return;
236
+ }
237
+ resultSet.rows = resultSet.rows.slice(0, maxRows);
238
+ resultSet.rowCount = maxRows;
239
+ resultSet.truncated = true;
240
+ }
186
241
  };
187
242
 
188
243
  // src/utils/resource-cleanup.ts
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-FU2ZJE4E.js";
5
5
  import {
6
6
  obfuscateDSNPassword
7
- } from "./chunk-WIQ4DWGY.js";
7
+ } from "./chunk-MF6LSVWK.js";
8
8
 
9
9
  // src/connectors/mysql-family-health-check.ts
10
10
  async function getMySQLFamilyHealthCheck(query) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  stripCommentsAndStrings
3
- } from "./chunk-WIQ4DWGY.js";
3
+ } from "./chunk-MF6LSVWK.js";
4
4
 
5
5
  // src/utils/allowed-keywords.ts
6
6
  var allowedKeywords = {
@@ -91,7 +91,9 @@ var SafeURL = class {
91
91
  }
92
92
  });
93
93
  }
94
- const atIndex = urlString.indexOf("@");
94
+ const pathStart = urlString.indexOf("/");
95
+ const authorityEnd = pathStart === -1 ? urlString.length : pathStart;
96
+ const atIndex = urlString.lastIndexOf("@", authorityEnd - 1);
95
97
  if (atIndex !== -1) {
96
98
  const auth = urlString.substring(0, atIndex);
97
99
  urlString = urlString.substring(atIndex + 1);
package/dist/index.js CHANGED
@@ -18,14 +18,14 @@ import {
18
18
  resolveSourceConfigs,
19
19
  resolveTomlConfigPath,
20
20
  resolveTransport
21
- } from "./chunk-CUBIR72I.js";
21
+ } from "./chunk-2DFLBX4D.js";
22
22
  import {
23
23
  allowedKeywords,
24
24
  getFirstKeyword,
25
25
  hasEscapeHatchFunction,
26
26
  isReadOnlySQL,
27
27
  sqlServerDynamicSqlPattern
28
- } from "./chunk-PBN2UXB5.js";
28
+ } from "./chunk-IQRBLAB4.js";
29
29
  import {
30
30
  loadConnectors
31
31
  } from "./chunk-WVVMH6FJ.js";
@@ -39,7 +39,7 @@ import {
39
39
  parseConnectionInfoFromDSN,
40
40
  splitSQLStatements,
41
41
  stripCommentsAndStrings
42
- } from "./chunk-WIQ4DWGY.js";
42
+ } from "./chunk-MF6LSVWK.js";
43
43
 
44
44
  // src/server.ts
45
45
  import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
@@ -224,7 +224,11 @@ function toStatementsPayload(resultSets) {
224
224
  return resultSets.map((set) => ({
225
225
  sql: set.sql,
226
226
  rows: set.rows,
227
- count: set.rowCount
227
+ count: set.rowCount,
228
+ // Only present when a max_rows cap actually cut off rows, so consumers
229
+ // can tell a capped result from a table with exactly max_rows rows
230
+ // (and complete results don't pay the extra tokens).
231
+ ...set.truncated ? { truncated: true } : {}
228
232
  }));
229
233
  }
230
234
  function resolveTrackedToolName(sourceId, baseName) {
@@ -1008,7 +1012,7 @@ function getExecuteSqlMetadata(sourceId) {
1008
1012
  maxRows: toolConfig?.max_rows
1009
1013
  };
1010
1014
  const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : "";
1011
- const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : "";
1015
+ const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows; capped results carry "truncated": true)` : "";
1012
1016
  const description = isSingleSource ? `${userDescPrefix}Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `${userDescPrefix}Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`;
1013
1017
  const isReadonly = isReadOnlyPolicy(policyFromReadonly(executeOptions.readonly));
1014
1018
  const annotations = {
@@ -1909,7 +1913,7 @@ See documentation for more details on configuring database connections.
1909
1913
  const sources = sourceConfigsData.sources;
1910
1914
  console.error(`Configuration source: ${sourceConfigsData.source}`);
1911
1915
  await connectorManager.connectWithSources(sources);
1912
- const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-XMEJF7WW.js");
1916
+ const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-D2QVU6AG.js");
1913
1917
  initializeToolRegistry2({
1914
1918
  sources: sourceConfigsData.sources,
1915
1919
  tools: sourceConfigsData.tools
@@ -2064,11 +2068,11 @@ See documentation for more details on configuring database connections.
2064
2068
 
2065
2069
  // src/index.ts
2066
2070
  var connectorModules = [
2067
- { load: () => import("./postgres-6XY2GPQV.js"), name: "PostgreSQL", driver: "pg" },
2068
- { load: () => import("./sqlserver-IIGRXGYK.js"), name: "SQL Server", driver: "mssql" },
2069
- { load: () => import("./sqlite-CZE6Z36Y.js"), name: "SQLite", driver: "node:sqlite" },
2070
- { load: () => import("./mysql-RLDQI3SJ.js"), name: "MySQL", driver: "mysql2" },
2071
- { load: () => import("./mariadb-E33EXGJZ.js"), name: "MariaDB", driver: "mariadb" }
2071
+ { load: () => import("./postgres-2FHS64EO.js"), name: "PostgreSQL", driver: "pg" },
2072
+ { load: () => import("./sqlserver-WNVSBFJ3.js"), name: "SQL Server", driver: "mssql" },
2073
+ { load: () => import("./sqlite-ASYMU5BI.js"), name: "SQLite", driver: "node:sqlite" },
2074
+ { load: () => import("./mysql-GXDUNLJ6.js"), name: "MySQL", driver: "mysql2" },
2075
+ { load: () => import("./mariadb-DQ6P57IX.js"), name: "MariaDB", driver: "mariadb" }
2072
2076
  ];
2073
2077
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
2074
2078
  console.error("Fatal error:", error);
@@ -5,7 +5,7 @@ import {
5
5
  parseQueryResultSets,
6
6
  requireDatabaseInDSN,
7
7
  withReadOnlyTransaction
8
- } from "./chunk-QEFMUQJ2.js";
8
+ } from "./chunk-A4TSDCPY.js";
9
9
  import "./chunk-FU2ZJE4E.js";
10
10
  import {
11
11
  quoteIdentifier
@@ -13,13 +13,13 @@ import {
13
13
  import {
14
14
  SQLRowLimiter,
15
15
  closeQuietly
16
- } from "./chunk-RSB7DPO2.js";
16
+ } from "./chunk-6QETXS44.js";
17
17
  import {
18
18
  ConnectorRegistry,
19
19
  SafeURL,
20
20
  obfuscateDSNPassword,
21
21
  splitSQLStatements
22
- } from "./chunk-WIQ4DWGY.js";
22
+ } from "./chunk-MF6LSVWK.js";
23
23
 
24
24
  // src/connectors/mariadb/index.ts
25
25
  import * as mariadb from "mariadb";
@@ -506,11 +506,13 @@ var MariaDBConnector = class _MariaDBConnector {
506
506
  async () => {
507
507
  const statements = splitSQLStatements(sql, "mariadb");
508
508
  let processedSQL = sql;
509
+ let probes = [];
509
510
  if (options.maxRows) {
510
- const processedStatements = statements.map(
511
- (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
511
+ const rewrites = statements.map(
512
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
512
513
  );
513
- processedSQL = processedStatements.join("; ");
514
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
515
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
514
516
  if (sql.trim().endsWith(";")) {
515
517
  processedSQL += ";";
516
518
  }
@@ -522,6 +524,11 @@ var MariaDBConnector = class _MariaDBConnector {
522
524
  results = await conn.query(processedSQL);
523
525
  }
524
526
  const resultSets = parseQueryResultSets(results, statements);
527
+ if (resultSets.length === probes.length) {
528
+ resultSets.forEach(
529
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
530
+ );
531
+ }
525
532
  return { resultSets };
526
533
  }
527
534
  );
@@ -6,7 +6,7 @@ import {
6
6
  parseQueryResultSets,
7
7
  requireDatabaseInDSN,
8
8
  withReadOnlyTransaction
9
- } from "./chunk-QEFMUQJ2.js";
9
+ } from "./chunk-A4TSDCPY.js";
10
10
  import "./chunk-FU2ZJE4E.js";
11
11
  import {
12
12
  quoteIdentifier
@@ -14,13 +14,13 @@ import {
14
14
  import {
15
15
  SQLRowLimiter,
16
16
  closeQuietly
17
- } from "./chunk-RSB7DPO2.js";
17
+ } from "./chunk-6QETXS44.js";
18
18
  import {
19
19
  ConnectorRegistry,
20
20
  SafeURL,
21
21
  obfuscateDSNPassword,
22
22
  splitSQLStatements
23
- } from "./chunk-WIQ4DWGY.js";
23
+ } from "./chunk-MF6LSVWK.js";
24
24
 
25
25
  // src/connectors/mysql/index.ts
26
26
  import mysql from "mysql2/promise";
@@ -517,11 +517,13 @@ var _MySQLConnector = class _MySQLConnector {
517
517
  async () => {
518
518
  const statements = splitSQLStatements(sql, "mysql");
519
519
  let processedSQL = sql;
520
+ let probes = [];
520
521
  if (options.maxRows) {
521
- const processedStatements = statements.map(
522
- (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
522
+ const rewrites = statements.map(
523
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
523
524
  );
524
- processedSQL = processedStatements.join("; ");
525
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
526
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
525
527
  if (sql.trim().endsWith(";")) {
526
528
  processedSQL += ";";
527
529
  }
@@ -534,6 +536,11 @@ var _MySQLConnector = class _MySQLConnector {
534
536
  }
535
537
  const [firstResult] = results;
536
538
  const resultSets = parseQueryResultSets(firstResult, statements);
539
+ if (resultSets.length === probes.length) {
540
+ resultSets.forEach(
541
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
542
+ );
543
+ }
537
544
  return { resultSets };
538
545
  }
539
546
  );
@@ -7,13 +7,13 @@ import {
7
7
  import {
8
8
  SQLRowLimiter,
9
9
  closeQuietly
10
- } from "./chunk-RSB7DPO2.js";
10
+ } from "./chunk-6QETXS44.js";
11
11
  import {
12
12
  ConnectorRegistry,
13
13
  SafeURL,
14
14
  obfuscateDSNPassword,
15
15
  splitSQLStatements
16
- } from "./chunk-WIQ4DWGY.js";
16
+ } from "./chunk-MF6LSVWK.js";
17
17
 
18
18
  // src/connectors/postgres/index.ts
19
19
  import fs from "fs";
@@ -557,17 +557,22 @@ var PostgresConnector = class _PostgresConnector {
557
557
  try {
558
558
  const statements = splitSQLStatements(sql, "postgres");
559
559
  if (statements.length === 1) {
560
- const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
560
+ const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
561
+ statements[0],
562
+ options.maxRows
563
+ );
561
564
  if (options.readonly) {
562
565
  await client.query("BEGIN READ ONLY");
563
566
  try {
564
567
  const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
565
568
  await client.query("COMMIT");
566
- return {
567
- resultSets: [
568
- { sql: statements[0], rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length }
569
- ]
569
+ const resultSet2 = {
570
+ sql: statements[0],
571
+ rows: result2.rows,
572
+ rowCount: result2.rowCount ?? result2.rows.length
570
573
  };
574
+ SQLRowLimiter.flagTruncation(resultSet2, options.maxRows, probeApplied);
575
+ return { resultSets: [resultSet2] };
571
576
  } catch (error) {
572
577
  try {
573
578
  await client.query("ROLLBACK");
@@ -582,11 +587,13 @@ var PostgresConnector = class _PostgresConnector {
582
587
  } else {
583
588
  result = await client.query(processedStatement);
584
589
  }
585
- return {
586
- resultSets: [
587
- { sql: statements[0], rows: result.rows, rowCount: result.rowCount ?? result.rows.length }
588
- ]
590
+ const resultSet = {
591
+ sql: statements[0],
592
+ rows: result.rows,
593
+ rowCount: result.rowCount ?? result.rows.length
589
594
  };
595
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
596
+ return { resultSets: [resultSet] };
590
597
  } else {
591
598
  if (parameters && parameters.length > 0) {
592
599
  throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
@@ -595,13 +602,18 @@ var PostgresConnector = class _PostgresConnector {
595
602
  await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
596
603
  try {
597
604
  for (let statement of statements) {
598
- const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
605
+ const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
606
+ statement,
607
+ options.maxRows
608
+ );
599
609
  const result = await client.query(processedStatement);
600
- resultSets.push({
610
+ const resultSet = {
601
611
  sql: statement,
602
612
  rows: result.rows ?? [],
603
613
  rowCount: result.rowCount ?? result.rows?.length ?? 0
604
- });
614
+ };
615
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
616
+ resultSets.push(resultSet);
605
617
  }
606
618
  await client.query("COMMIT");
607
619
  } catch (error) {
@@ -2,9 +2,9 @@ import {
2
2
  ToolRegistry,
3
3
  getToolRegistry,
4
4
  initializeToolRegistry
5
- } from "./chunk-CUBIR72I.js";
5
+ } from "./chunk-2DFLBX4D.js";
6
6
  import "./chunk-WVVMH6FJ.js";
7
- import "./chunk-WIQ4DWGY.js";
7
+ import "./chunk-MF6LSVWK.js";
8
8
  export {
9
9
  ToolRegistry,
10
10
  getToolRegistry,
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  SQLRowLimiter,
6
6
  closeQuietly
7
- } from "./chunk-RSB7DPO2.js";
7
+ } from "./chunk-6QETXS44.js";
8
8
  import {
9
9
  ConnectorRegistry,
10
10
  SafeURL,
11
11
  obfuscateDSNPassword,
12
12
  splitSQLStatements
13
- } from "./chunk-WIQ4DWGY.js";
13
+ } from "./chunk-MF6LSVWK.js";
14
14
 
15
15
  // src/connectors/sqlite/suppress-experimental-warning.ts
16
16
  var installed = false;
@@ -334,17 +334,20 @@ var SQLiteConnector = class _SQLiteConnector {
334
334
  let processedStatement = statements[0];
335
335
  const trimmedStatement = statements[0].toLowerCase().trim();
336
336
  const isReadStatement = trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma") && (trimmedStatement.includes("table_info") || trimmedStatement.includes("index_info") || trimmedStatement.includes("index_list") || trimmedStatement.includes("foreign_key_list"));
337
+ let probeApplied = false;
337
338
  if (options.maxRows) {
338
- processedStatement = SQLRowLimiter.applyMaxRows(processedStatement, options.maxRows);
339
+ const rewrite = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
340
+ processedStatement,
341
+ options.maxRows
342
+ );
343
+ processedStatement = rewrite.sql;
344
+ probeApplied = rewrite.probeApplied;
339
345
  }
340
346
  if (isReadStatement) {
341
- if (parameters && parameters.length > 0) {
342
- const rows = this.prepare(processedStatement).all(...parameters);
343
- return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
344
- } else {
345
- const rows = this.prepare(processedStatement).all();
346
- return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
347
- }
347
+ const rows = parameters && parameters.length > 0 ? this.prepare(processedStatement).all(...parameters) : this.prepare(processedStatement).all();
348
+ const resultSet = { sql: statements[0], rows, rowCount: rows.length };
349
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
350
+ return { resultSets: [resultSet] };
348
351
  } else {
349
352
  let result;
350
353
  if (parameters && parameters.length > 0) {
@@ -382,9 +385,14 @@ var SQLiteConnector = class _SQLiteConnector {
382
385
  if (options.readonly) {
383
386
  this.db.exec("PRAGMA query_only = ON");
384
387
  }
385
- const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
388
+ const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
389
+ statement,
390
+ options.maxRows
391
+ );
386
392
  const rows = this.prepare(processedStatement).all();
387
- resultSets.push({ sql: statement, rows, rowCount: rows.length });
393
+ const resultSet = { sql: statement, rows, rowCount: rows.length };
394
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
395
+ resultSets.push(resultSet);
388
396
  }
389
397
  return { resultSets };
390
398
  }
@@ -3,7 +3,7 @@ import {
3
3
  sqlServerDynamicSqlPattern,
4
4
  sqlServerPassThroughKeywords,
5
5
  sqlServerPassThroughPattern
6
- } from "./chunk-PBN2UXB5.js";
6
+ } from "./chunk-IQRBLAB4.js";
7
7
  import {
8
8
  isDriverNotInstalled
9
9
  } from "./chunk-WVVMH6FJ.js";
@@ -14,14 +14,14 @@ import {
14
14
  import {
15
15
  SQLRowLimiter,
16
16
  closeQuietly
17
- } from "./chunk-RSB7DPO2.js";
17
+ } from "./chunk-6QETXS44.js";
18
18
  import {
19
19
  ConnectorRegistry,
20
20
  SafeURL,
21
21
  obfuscateDSNPassword,
22
22
  splitSQLStatements,
23
23
  stripCommentsAndStrings
24
- } from "./chunk-WIQ4DWGY.js";
24
+ } from "./chunk-MF6LSVWK.js";
25
25
 
26
26
  // src/connectors/sqlserver/index.ts
27
27
  import sql from "mssql";
@@ -562,12 +562,24 @@ var _SQLServerConnector = class _SQLServerConnector {
562
562
  }
563
563
  try {
564
564
  let processedSQL = sqlQuery;
565
+ let probeApplied = false;
565
566
  if (options.maxRows) {
566
- processedSQL = SQLRowLimiter.applyMaxRowsForSQLServer(sqlQuery, options.maxRows);
567
+ const rewrite = SQLRowLimiter.applyMaxRowsForSQLServerWithTruncationProbe(
568
+ sqlQuery,
569
+ options.maxRows
570
+ );
571
+ processedSQL = rewrite.sql;
572
+ probeApplied = rewrite.probeApplied;
567
573
  }
568
574
  const isSingleStatement = splitSQLStatements(processedSQL, "sqlserver").length === 1;
569
575
  if (options.readonly) {
570
- return await this.executeReadOnly(processedSQL, parameters, isSingleStatement);
576
+ return await this.executeReadOnly(
577
+ processedSQL,
578
+ parameters,
579
+ isSingleStatement ? sqlQuery : void 0,
580
+ options.maxRows,
581
+ probeApplied
582
+ );
571
583
  }
572
584
  const request = this.connection.request();
573
585
  const messages = [];
@@ -585,12 +597,16 @@ var _SQLServerConnector = class _SQLServerConnector {
585
597
  );
586
598
  _SQLServerConnector.bindParameters(request, parameters);
587
599
  const result = await request.query(processedSQL);
600
+ const resultSets = _SQLServerConnector.buildResultSets(
601
+ result.recordsets,
602
+ result.rowsAffected,
603
+ isSingleStatement ? sqlQuery : void 0
604
+ );
605
+ if (resultSets.length > 0) {
606
+ SQLRowLimiter.flagTruncation(resultSets[0], options.maxRows, probeApplied);
607
+ }
588
608
  return {
589
- resultSets: _SQLServerConnector.buildResultSets(
590
- result.recordsets,
591
- result.rowsAffected,
592
- isSingleStatement ? processedSQL : void 0
593
- ),
609
+ resultSets,
594
610
  ...messages.length > 0 ? { messages } : {}
595
611
  };
596
612
  } catch (error) {
@@ -718,7 +734,7 @@ var _SQLServerConnector = class _SQLServerConnector {
718
734
  * Dangerous constructs are rejected before the transaction opens; see
719
735
  * assertNoReadOnlyEscapes.
720
736
  */
721
- async executeReadOnly(processedSQL, parameters, isSingleStatement) {
737
+ async executeReadOnly(processedSQL, parameters, sourceSql, maxRows, probeApplied) {
722
738
  this.assertNoReadOnlyEscapes(processedSQL, { transactionControl: true });
723
739
  const transaction = new sql.Transaction(this.connection);
724
740
  await transaction.begin();
@@ -754,12 +770,16 @@ var _SQLServerConnector = class _SQLServerConnector {
754
770
  }
755
771
  }
756
772
  }
773
+ const resultSets = _SQLServerConnector.buildResultSets(
774
+ result.recordsets,
775
+ result.rowsAffected,
776
+ sourceSql
777
+ );
778
+ if (resultSets.length > 0) {
779
+ SQLRowLimiter.flagTruncation(resultSets[0], maxRows, probeApplied);
780
+ }
757
781
  return {
758
- resultSets: _SQLServerConnector.buildResultSets(
759
- result.recordsets,
760
- result.rowsAffected,
761
- isSingleStatement ? processedSQL : void 0
762
- ),
782
+ resultSets,
763
783
  ...messages.length > 0 ? { messages } : {}
764
784
  };
765
785
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.bytebase/dbhub",
5
5
  "description": "Minimal, token-efficient Database MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB",
6
6
  "repository": {
@@ -55,6 +55,7 @@
55
55
  "zod": "^4.2.0"
56
56
  },
57
57
  "optionalDependencies": {
58
+ "@aws-sdk/credential-providers": "^3.1001.0",
58
59
  "@aws-sdk/rds-signer": "^3.1001.0",
59
60
  "@azure/identity": "^4.8.0",
60
61
  "mariadb": "^3.4.0",