@dbx-app/mcp-server 0.4.11 → 0.4.12

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.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { type Backend } from "@dbx-app/node-core";
4
4
  export declare const DBX_MCP_PACKAGE_VERSION: string;
5
- export declare const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
5
+ export declare const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
6
6
  export declare function createDbxMcpServer(backend: Backend, options?: {
7
7
  isWebMode?: boolean;
8
8
  }): McpServer;
package/dist/index.js CHANGED
@@ -23,59 +23,100 @@ function formatQueryToolResult(result, title) {
23
23
  const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
24
24
  return text(`${prefix}${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
25
25
  }
26
- export const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
26
+ export const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, jdbc, access, mq";
27
27
  const FILE_CAPABLE_CONNECTION_TYPES = new Set(["sqlite", "duckdb", "access", "h2"]);
28
+ function scopedValue(value) {
29
+ const trimmed = value?.trim();
30
+ return trimmed ? trimmed : undefined;
31
+ }
32
+ function mcpScopeFromEnv() {
33
+ return {
34
+ connectionId: scopedValue(process.env.DBX_MCP_SCOPE_CONNECTION_ID),
35
+ connectionName: scopedValue(process.env.DBX_MCP_SCOPE_CONNECTION_NAME),
36
+ database: scopedValue(process.env.DBX_MCP_SCOPE_DATABASE),
37
+ };
38
+ }
39
+ function scopeEnabled(scope) {
40
+ return !!(scope.connectionId || scope.connectionName);
41
+ }
42
+ function connectionMatchesScope(config, scope) {
43
+ return (!!scope.connectionId && config.id === scope.connectionId) || (!!scope.connectionName && config.name === scope.connectionName);
44
+ }
45
+ async function loadScopedConnections(backend, scope) {
46
+ const connections = await backend.loadConnections();
47
+ if (!scopeEnabled(scope))
48
+ return connections;
49
+ return connections.filter((config) => connectionMatchesScope(config, scope));
50
+ }
51
+ async function resolveConnection(backend, scope, requestedName) {
52
+ if (!scopeEnabled(scope)) {
53
+ if (!requestedName?.trim())
54
+ return { error: toolError("CONNECTION_NOT_FOUND", "Connection name is required.") };
55
+ const config = await backend.findConnection(requestedName);
56
+ return config ? { config } : { error: toolError("CONNECTION_NOT_FOUND", `Connection "${requestedName}" not found.`) };
57
+ }
58
+ const [scopedConfig] = await loadScopedConnections(backend, scope);
59
+ if (!scopedConfig)
60
+ return { error: toolError("CONNECTION_NOT_FOUND", "Scoped DBX connection was not found.") };
61
+ if (requestedName?.trim() && requestedName !== scopedConfig.name && requestedName !== scopedConfig.id) {
62
+ return { error: toolError("CONNECTION_OUT_OF_SCOPE", `Connection "${requestedName}" is outside this DBX AI session scope.`) };
63
+ }
64
+ return { config: scopedConfig };
65
+ }
28
66
  export function createDbxMcpServer(backend, options = {}) {
29
67
  const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
68
+ const scope = mcpScopeFromEnv();
69
+ const scoped = scopeEnabled(scope);
30
70
  const server = new McpServer({
31
71
  name: "dbx",
32
72
  version: DBX_MCP_PACKAGE_VERSION,
33
73
  });
34
74
  server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
35
- const connections = await backend.loadConnections();
75
+ const connections = await loadScopedConnections(backend, scope);
36
76
  if (connections.length === 0)
37
77
  return text("No connections configured in DBX.");
38
78
  const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
39
79
  return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
40
80
  });
41
81
  server.tool("dbx_list_tables", "List tables and views for a database connection", {
42
- connection_name: z.string().describe("Name of the DBX connection"),
82
+ connection_name: z.string().optional().describe("Name of the DBX connection"),
43
83
  database: z.string().optional().describe("Database name"),
44
84
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
45
85
  }, async ({ connection_name, database, schema }) => {
46
- const config = await backend.findConnection(connection_name);
47
- if (!config)
48
- return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
49
- const tables = await backend.listTables(withDatabase(config, database), schema);
86
+ const { config, error } = await resolveConnection(backend, scope, connection_name);
87
+ if (error)
88
+ return error;
89
+ const tables = await backend.listTables(withDatabase(config, database ?? scope.database), schema);
50
90
  if (tables.length === 0)
51
91
  return text("No tables found.");
52
92
  const rows = tables.map((t) => [t.name, t.type]);
53
93
  return text(mdTable(["Table", "Type"], rows));
54
94
  });
55
95
  server.tool("dbx_describe_table", "Get column definitions for a table", {
56
- connection_name: z.string().describe("Name of the DBX connection"),
96
+ connection_name: z.string().optional().describe("Name of the DBX connection"),
57
97
  table: z.string().describe("Table name"),
58
98
  database: z.string().optional().describe("Database name"),
59
99
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
60
100
  }, async ({ connection_name, table, database, schema }) => {
61
- const config = await backend.findConnection(connection_name);
62
- if (!config)
63
- return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
64
- const columns = await backend.describeTable(withDatabase(config, database), table, schema);
101
+ const { config, error } = await resolveConnection(backend, scope, connection_name);
102
+ if (error)
103
+ return error;
104
+ const columns = await backend.describeTable(withDatabase(config, database ?? scope.database), table, schema);
65
105
  if (columns.length === 0)
66
106
  return text("No columns found.");
67
107
  const rows = columns.map((c) => [c.is_primary_key ? `${c.name} (PK)` : c.name, c.data_type, c.is_nullable ? "YES" : "NO", c.column_default ?? "", c.comment ?? ""]);
68
108
  return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
69
109
  });
70
110
  server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
71
- connection_name: z.string().describe("Name of the DBX connection"),
111
+ connection_name: z.string().optional().describe("Name of the DBX connection"),
72
112
  database: z.string().optional().describe("Database name"),
73
113
  sql: z.string().describe("SQL query to execute"),
74
114
  }, async ({ connection_name, database, sql }) => {
75
- const config = await backend.findConnection(connection_name);
76
- if (!config)
77
- return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
78
- if (config.db_type !== "mongodb") {
115
+ const { config, error } = await resolveConnection(backend, scope, connection_name);
116
+ if (error)
117
+ return error;
118
+ const scopedConfig = config;
119
+ if (scopedConfig.db_type !== "mongodb") {
79
120
  const safety = evaluateSqlSafety(sql, { ...sqlSafetyFromEnv(), allowMultipleStatements: true });
80
121
  if (!safety.allowed)
81
122
  return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
@@ -83,10 +124,10 @@ export function createDbxMcpServer(backend, options = {}) {
83
124
  // MongoDB shell commands don't fit the SQL safety evaluator; the backend
84
125
  // (node-core executeQuery) applies command-aware read/write gating.
85
126
  try {
86
- const statements = config.db_type === "mongodb" ? [sql] : splitSqlStatements(sql);
127
+ const statements = scopedConfig.db_type === "mongodb" ? [sql] : splitSqlStatements(sql);
87
128
  const results = [];
88
129
  for (const statement of statements) {
89
- results.push(await backend.executeQuery(withDatabase(config, database), statement));
130
+ results.push(await backend.executeQuery(withDatabase(scopedConfig, database ?? scope.database), statement));
90
131
  }
91
132
  if (results.length === 1)
92
133
  return formatQueryToolResult(results[0]);
@@ -98,16 +139,16 @@ export function createDbxMcpServer(backend, options = {}) {
98
139
  }
99
140
  });
100
141
  server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
101
- connection_name: z.string().describe("Name of the DBX connection"),
142
+ connection_name: z.string().optional().describe("Name of the DBX connection"),
102
143
  database: z.string().optional().describe("Database name"),
103
144
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
104
145
  tables: z.array(z.string()).optional().describe("Specific table names to include"),
105
146
  max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
106
147
  }, async ({ connection_name, database, schema, tables, max_tables }) => {
107
- const config = await backend.findConnection(connection_name);
108
- if (!config)
109
- return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
110
- const context = await buildSchemaContext(backend, withDatabase(config, database), {
148
+ const { config, error } = await resolveConnection(backend, scope, connection_name);
149
+ if (error)
150
+ return error;
151
+ const context = await buildSchemaContext(backend, withDatabase(config, database ?? scope.database), {
111
152
  schema,
112
153
  tables,
113
154
  maxTables: max_tables,
@@ -116,56 +157,58 @@ export function createDbxMcpServer(backend, options = {}) {
116
157
  return text("No matching tables found.");
117
158
  return text(formatSchemaContext(context));
118
159
  });
119
- server.tool("dbx_add_connection", "Add a new database connection to DBX", {
120
- name: z.string().describe("Connection name"),
121
- db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
122
- host: z.string().describe("Database host"),
123
- port: z.number().optional().describe("Database port (TDengine defaults to 6041, IoTDB defaults to 6667, XuguDB defaults to 5138)"),
124
- username: z.string().default("").describe("Username"),
125
- password: z.string().default("").describe("Password"),
126
- database: z.string().optional().describe("Default database name"),
127
- ssl: z.boolean().default(false).describe("Enable SSL"),
128
- driver_profile: z.string().optional().describe("Driver profile (e.g. 'gbase8a', 'gbase8s')"),
129
- }, async ({ name, db_type, host, port, username, password, database, ssl, driver_profile }) => {
130
- const existing = await backend.findConnection(name);
131
- if (existing)
132
- return text(`Connection "${name}" already exists.`);
133
- const DEFAULT_PORTS = {
134
- kwdb: 26257,
135
- rqlite: 4001,
136
- tdengine: 6041,
137
- iotdb: 6667,
138
- xugu: 5138,
139
- };
140
- const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_CAPABLE_CONNECTION_TYPES.has(db_type) ? 0 : undefined);
141
- if (resolvedPort === undefined)
142
- return text("Port is required for this database type.");
143
- const config = await backend.addConnection({
144
- name,
145
- db_type,
146
- host,
147
- port: resolvedPort,
148
- username,
149
- password,
150
- database,
151
- ssl,
152
- driver_profile,
153
- ssh_enabled: false,
160
+ if (!scoped) {
161
+ server.tool("dbx_add_connection", "Add a new database connection to DBX", {
162
+ name: z.string().describe("Connection name"),
163
+ db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
164
+ host: z.string().describe("Database host"),
165
+ port: z.number().optional().describe("Database port (TDengine defaults to 6041, IoTDB defaults to 6667, XuguDB defaults to 5138)"),
166
+ username: z.string().default("").describe("Username"),
167
+ password: z.string().default("").describe("Password"),
168
+ database: z.string().optional().describe("Default database name"),
169
+ ssl: z.boolean().default(false).describe("Enable SSL"),
170
+ driver_profile: z.string().optional().describe("Driver profile (e.g. 'gbase8a', 'gbase8s')"),
171
+ }, async ({ name, db_type, host, port, username, password, database, ssl, driver_profile }) => {
172
+ const existing = await backend.findConnection(name);
173
+ if (existing)
174
+ return text(`Connection "${name}" already exists.`);
175
+ const DEFAULT_PORTS = {
176
+ kwdb: 26257,
177
+ rqlite: 4001,
178
+ tdengine: 6041,
179
+ iotdb: 6667,
180
+ xugu: 5138,
181
+ };
182
+ const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_CAPABLE_CONNECTION_TYPES.has(db_type) ? 0 : undefined);
183
+ if (resolvedPort === undefined)
184
+ return text("Port is required for this database type.");
185
+ const config = await backend.addConnection({
186
+ name,
187
+ db_type,
188
+ host,
189
+ port: resolvedPort,
190
+ username,
191
+ password,
192
+ database,
193
+ ssl,
194
+ driver_profile,
195
+ ssh_enabled: false,
196
+ });
197
+ await notifyReload();
198
+ return text(`Connection "${config.name}" added (id: ${config.id}).`);
154
199
  });
155
- await notifyReload();
156
- return text(`Connection "${config.name}" added (id: ${config.id}).`);
157
- });
158
- server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
159
- connection_name: z.string().describe("Name of the connection to remove"),
160
- }, async ({ connection_name }) => {
161
- const removed = await backend.removeConnection(connection_name);
162
- if (!removed)
163
- return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
164
- await notifyReload();
165
- return text(`Connection "${connection_name}" removed.`);
166
- });
200
+ server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
201
+ connection_name: z.string().describe("Name of the connection to remove"),
202
+ }, async ({ connection_name }) => {
203
+ const removed = await backend.removeConnection(connection_name);
204
+ if (!removed)
205
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
206
+ await notifyReload();
207
+ return text(`Connection "${connection_name}" removed.`);
208
+ });
209
+ }
167
210
  // Desktop-only tools: open table and execute-and-show require the Tauri bridge
168
- if (!isWebMode) {
211
+ if (!isWebMode && !scoped) {
169
212
  server.tool("dbx_open_table", "Open a table in DBX desktop app UI. Requires DBX to be running.", {
170
213
  connection_name: z.string().describe("Name of the DBX connection"),
171
214
  table: z.string().describe("Table name to open"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbx-app/mcp-server",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "description": "MCP server for DBX — query databases from Claude Code, Cursor, and other AI agents",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.12.1",
32
32
  "zod": "^3.25.20",
33
- "@dbx-app/node-core": "^0.4.11"
33
+ "@dbx-app/node-core": "^0.4.12"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.15.21",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/t8y2/dbx",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.4.11",
9
+ "version": "0.4.12",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@dbx-app/mcp-server",
14
- "version": "0.4.11",
14
+ "version": "0.4.12",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }