@dbx-app/mcp-server 0.4.2 → 0.4.4

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
@@ -48,7 +48,7 @@ Or for development (from source):
48
48
  "mcpServers": {
49
49
  "dbx": {
50
50
  "command": "npx",
51
- "args": ["tsx", "mcp/src/index.ts"],
51
+ "args": ["tsx", "packages/mcp-server/src/index.ts"],
52
52
  "cwd": "/path/to/dbx"
53
53
  }
54
54
  }
@@ -65,6 +65,18 @@ In Claude Code, just ask:
65
65
  - "Query the average salary from employees"
66
66
  - "Open the orders table in DBX"
67
67
 
68
+ ## CLI
69
+
70
+ For terminal, script, and Codex workflows, install the dedicated CLI package:
71
+
72
+ ```bash
73
+ npm install -g @dbx-app/cli
74
+ dbx connections list --json
75
+ dbx query local "select 1" --json
76
+ ```
77
+
78
+ See the [DBX CLI README](../cli/README.md) for command details.
79
+
68
80
  ## Tools
69
81
 
70
82
  | Tool | Description |
@@ -115,7 +127,7 @@ PostgreSQL, MySQL, SQLite, Doris, StarRocks, and Redshift queries run directly f
115
127
  ## Requirements
116
128
 
117
129
  - [DBX](https://github.com/t8y2/dbx) installed with at least one connection configured
118
- - Node.js 18+
130
+ - Node.js 22.13.0 或更高版本
119
131
 
120
132
  ## License
121
133
 
@@ -174,6 +186,18 @@ npx @dbx-app/mcp-server
174
186
  - "查询最近 7 天的订单数量"
175
187
  - "打开 orders 表"
176
188
 
189
+ ### CLI
190
+
191
+ 终端、脚本和 Codex 工作流请安装独立 CLI 包:
192
+
193
+ ```bash
194
+ npm install -g @dbx-app/cli
195
+ dbx connections list --json
196
+ dbx query local "select 1" --json
197
+ ```
198
+
199
+ 命令详情见 [DBX CLI README](../cli/README.md)。
200
+
177
201
  ### 工具列表
178
202
 
179
203
  | 工具 | 说明 |
@@ -218,4 +242,4 @@ PostgreSQL、MySQL、SQLite、Doris、StarRocks、Redshift 查询可由 MCP Serv
218
242
  ### 系统要求
219
243
 
220
244
  - 已安装 [DBX](https://github.com/t8y2/dbx) 并配置了至少一个数据库连接
221
- - Node.js 18+
245
+ - Node.js 22.13.0 or newer
package/dist/index.d.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { type Backend } from "@dbx-app/node-core";
4
+ export declare const DBX_MCP_PACKAGE_VERSION: string;
5
+ export declare const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, xugu, jdbc, access";
6
+ export declare function createDbxMcpServer(backend: Backend, options?: {
7
+ isWebMode?: boolean;
8
+ }): McpServer;
package/dist/index.js CHANGED
@@ -1,235 +1,222 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createRequire } from "node:module";
5
+ import { pathToFileURL } from "node:url";
4
6
  import { z } from "zod";
5
- import { readFile } from "node:fs/promises";
6
- import { join } from "node:path";
7
- import { homedir, platform } from "node:os";
8
- import { loadConnections as desktopLoadConnections, findConnection as desktopFindConnection, addConnection as desktopAddConnection, removeConnection as desktopRemoveConnection, } from "./connections.js";
9
- import { listTables as desktopListTables, describeTable as desktopDescribeTable, executeQuery as desktopExecuteQuery, } from "./database.js";
10
- import { buildSchemaContext, formatSchemaContext } from "./schema-context.js";
11
- import { evaluateSqlSafety, sqlSafetyFromEnv } from "./sql-safety.js";
12
- const isWebMode = !!process.env.DBX_WEB_URL;
13
- let backend;
14
- if (isWebMode) {
15
- const web = await import("./web-backend.js");
16
- backend = web;
17
- }
18
- else {
19
- backend = {
20
- loadConnections: desktopLoadConnections,
21
- findConnection: desktopFindConnection,
22
- addConnection: desktopAddConnection,
23
- removeConnection: desktopRemoveConnection,
24
- listTables: desktopListTables,
25
- describeTable: desktopDescribeTable,
26
- executeQuery: desktopExecuteQuery,
27
- };
28
- }
7
+ import { buildSchemaContext, createBackend, evaluateMongoAggregateSafety, evaluateSqlSafety, formatCell, formatSchemaContext, mdTable, notifyReload, parseMongoAggregateCommand, postBridge, sqlSafetyFromEnv, } from "@dbx-app/node-core";
8
+ const require = createRequire(import.meta.url);
9
+ const packageJson = require("../package.json");
10
+ export const DBX_MCP_PACKAGE_VERSION = packageJson.version ?? "0.0.0";
29
11
  function text(s) {
30
12
  return { content: [{ type: "text", text: s }] };
31
13
  }
32
- function mdTable(headers, rows) {
33
- const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length), 3));
34
- const header = `| ${headers.map((h, i) => h.padEnd(widths[i])).join(" | ")} |`;
35
- const sep = `| ${widths.map((w) => "-".repeat(w)).join(" | ")} |`;
36
- const body = rows.map((r) => `| ${r.map((c, i) => (c || "").padEnd(widths[i])).join(" | ")} |`).join("\n");
37
- return `${header}\n${sep}\n${body}`;
38
- }
39
- const server = new McpServer({
40
- name: "dbx",
41
- version: "0.3.0",
42
- });
43
- server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
44
- const connections = await backend.loadConnections();
45
- if (connections.length === 0)
46
- return text("No connections configured in DBX.");
47
- const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
48
- return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
49
- });
50
- server.tool("dbx_list_tables", "List tables and views for a database connection", {
51
- connection_name: z.string().describe("Name of the DBX connection"),
52
- schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
53
- }, async ({ connection_name, schema }) => {
54
- const config = await backend.findConnection(connection_name);
55
- if (!config)
56
- return text(`Connection "${connection_name}" not found`);
57
- const tables = await backend.listTables(config, schema);
58
- if (tables.length === 0)
59
- return text("No tables found.");
60
- const rows = tables.map((t) => [t.name, t.type]);
61
- return text(mdTable(["Table", "Type"], rows));
62
- });
63
- server.tool("dbx_describe_table", "Get column definitions for a table", {
64
- connection_name: z.string().describe("Name of the DBX connection"),
65
- table: z.string().describe("Table name"),
66
- schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
67
- }, async ({ connection_name, table, schema }) => {
68
- const config = await backend.findConnection(connection_name);
69
- if (!config)
70
- return text(`Connection "${connection_name}" not found`);
71
- const columns = await backend.describeTable(config, table, schema);
72
- if (columns.length === 0)
73
- return text("No columns found.");
74
- const rows = columns.map((c) => [
75
- c.is_primary_key ? `${c.name} (PK)` : c.name,
76
- c.data_type,
77
- c.is_nullable ? "YES" : "NO",
78
- c.column_default ?? "",
79
- c.comment ?? "",
80
- ]);
81
- return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
82
- });
83
- server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
84
- connection_name: z.string().describe("Name of the DBX connection"),
85
- sql: z.string().describe("SQL query to execute"),
86
- }, async ({ connection_name, sql }) => {
87
- const config = await backend.findConnection(connection_name);
88
- if (!config)
89
- return text(`Connection "${connection_name}" not found`);
90
- const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
91
- if (!safety.allowed)
92
- return text(`Query blocked: ${safety.reason}`);
93
- try {
94
- const result = await backend.executeQuery(config, sql);
95
- if (result.columns.length === 0)
96
- return text(`Query executed. ${result.row_count} row(s) affected.`);
97
- const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
98
- return text(`${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
99
- }
100
- catch (e) {
101
- const msg = e instanceof Error ? e.message : String(e);
102
- return text(`Query error: ${msg}`);
103
- }
104
- });
105
- server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
106
- connection_name: z.string().describe("Name of the DBX connection"),
107
- schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
108
- tables: z.array(z.string()).optional().describe("Specific table names to include"),
109
- max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
110
- }, async ({ connection_name, schema, tables, max_tables }) => {
111
- const config = await backend.findConnection(connection_name);
112
- if (!config)
113
- return text(`Connection "${connection_name}" not found`);
114
- const context = await buildSchemaContext(backend, config, { schema, tables, maxTables: max_tables });
115
- if (context.tables.length === 0)
116
- return text("No matching tables found.");
117
- return text(formatSchemaContext(context));
118
- });
119
- server.tool("dbx_add_connection", "Add a new database connection to DBX", {
120
- name: z.string().describe("Connection name"),
121
- db_type: z
122
- .string()
123
- .describe("Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, jdbc, access"),
124
- host: z.string().describe("Database host"),
125
- port: z.number().optional().describe("Database port (TDengine defaults to 6041)"),
126
- username: z.string().default("").describe("Username"),
127
- password: z.string().default("").describe("Password"),
128
- database: z.string().optional().describe("Default database name"),
129
- ssl: z.boolean().default(false).describe("Enable SSL"),
130
- }, async ({ name, db_type, host, port, username, password, database, ssl }) => {
131
- const existing = await backend.findConnection(name);
132
- if (existing)
133
- return text(`Connection "${name}" already exists.`);
134
- const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
135
- const resolvedPort = port ?? (db_type === "tdengine" ? 6041 : FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
136
- if (resolvedPort === undefined)
137
- return text("Port is required for this database type.");
138
- const config = await backend.addConnection({
139
- name,
140
- db_type,
141
- host,
142
- port: resolvedPort,
143
- username,
144
- password,
145
- database,
146
- ssl,
147
- ssh_enabled: false,
148
- });
149
- await notifyReload();
150
- return text(`Connection "${config.name}" added (id: ${config.id}).`);
151
- });
152
- server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
153
- connection_name: z.string().describe("Name of the connection to remove"),
154
- }, async ({ connection_name }) => {
155
- const removed = await backend.removeConnection(connection_name);
156
- if (!removed)
157
- return text(`Connection "${connection_name}" not found.`);
158
- await notifyReload();
159
- return text(`Connection "${connection_name}" removed.`);
160
- });
161
- function formatCell(value) {
162
- if (value === null || value === undefined)
163
- return "NULL";
164
- if (typeof value === "object")
165
- return JSON.stringify(value);
166
- return String(value);
167
- }
168
- function appDataDir() {
169
- const home = homedir();
170
- switch (platform()) {
171
- case "darwin":
172
- return join(home, "Library", "Application Support", "com.dbx.app");
173
- case "win32":
174
- return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "com.dbx.app");
175
- default:
176
- return join(home, ".config", "com.dbx.app");
177
- }
14
+ function toolError(code, message) {
15
+ return { ...text(`${code}: ${message}`), isError: true };
178
16
  }
179
- async function getBridgeUrl() {
180
- const portFile = join(appDataDir(), "mcp-bridge-port");
181
- const port = (await readFile(portFile, "utf-8")).trim();
182
- return `http://127.0.0.1:${port}`;
17
+ function withDatabase(config, database) {
18
+ return database === undefined ? config : { ...config, database };
183
19
  }
184
- async function notifyReload() {
185
- try {
186
- const bridgeUrl = await getBridgeUrl();
187
- await fetch(`${bridgeUrl}/reload-connections`, { method: "POST" });
188
- }
189
- catch { }
190
- }
191
- // Desktop-only tools: open table and execute-and-show require the Tauri bridge
192
- if (!isWebMode) {
193
- server.tool("dbx_open_table", "Open a table in DBX desktop app UI. Requires DBX to be running.", {
20
+ export const DBX_CONNECTION_TYPE_DESCRIPTION = "Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, xugu, jdbc, access";
21
+ export function createDbxMcpServer(backend, options = {}) {
22
+ const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
23
+ const server = new McpServer({
24
+ name: "dbx",
25
+ version: DBX_MCP_PACKAGE_VERSION,
26
+ });
27
+ server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
28
+ const connections = await backend.loadConnections();
29
+ if (connections.length === 0)
30
+ return text("No connections configured in DBX.");
31
+ const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
32
+ return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
33
+ });
34
+ server.tool("dbx_list_tables", "List tables and views for a database connection", {
35
+ connection_name: z.string().describe("Name of the DBX connection"),
36
+ database: z.string().optional().describe("Database name"),
37
+ schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
38
+ }, async ({ connection_name, database, schema }) => {
39
+ const config = await backend.findConnection(connection_name);
40
+ if (!config)
41
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
42
+ const tables = await backend.listTables(withDatabase(config, database), schema);
43
+ if (tables.length === 0)
44
+ return text("No tables found.");
45
+ const rows = tables.map((t) => [t.name, t.type]);
46
+ return text(mdTable(["Table", "Type"], rows));
47
+ });
48
+ server.tool("dbx_describe_table", "Get column definitions for a table", {
194
49
  connection_name: z.string().describe("Name of the DBX connection"),
195
- table: z.string().describe("Table name to open"),
50
+ table: z.string().describe("Table name"),
196
51
  database: z.string().optional().describe("Database name"),
197
- schema: z.string().optional().describe("Schema name"),
52
+ schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
198
53
  }, async ({ connection_name, table, database, schema }) => {
199
- return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
54
+ const config = await backend.findConnection(connection_name);
55
+ if (!config)
56
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
57
+ const columns = await backend.describeTable(withDatabase(config, database), table, schema);
58
+ if (columns.length === 0)
59
+ return text("No columns found.");
60
+ const rows = columns.map((c) => [
61
+ c.is_primary_key ? `${c.name} (PK)` : c.name,
62
+ c.data_type,
63
+ c.is_nullable ? "YES" : "NO",
64
+ c.column_default ?? "",
65
+ c.comment ?? "",
66
+ ]);
67
+ return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
200
68
  });
201
- server.tool("dbx_execute_and_show", "Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.", {
69
+ server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
202
70
  connection_name: z.string().describe("Name of the DBX connection"),
71
+ database: z.string().optional().describe("Database name"),
203
72
  sql: z.string().describe("SQL query to execute"),
73
+ }, async ({ connection_name, database, sql }) => {
74
+ const config = await backend.findConnection(connection_name);
75
+ if (!config)
76
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
77
+ if (config.db_type !== "mongodb") {
78
+ const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
79
+ if (!safety.allowed)
80
+ return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
81
+ }
82
+ // MongoDB shell commands don't fit the SQL safety evaluator; the backend
83
+ // (node-core executeQuery) applies command-aware read/write gating.
84
+ try {
85
+ const result = await backend.executeQuery(withDatabase(config, database), sql);
86
+ if (result.columns.length === 0)
87
+ return text(`Query executed. ${result.row_count} row(s) affected.`);
88
+ const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
89
+ return text(`${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
90
+ }
91
+ catch (e) {
92
+ const msg = e instanceof Error ? e.message : String(e);
93
+ return toolError("QUERY_ERROR", msg);
94
+ }
95
+ });
96
+ server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
97
+ connection_name: z.string().describe("Name of the DBX connection"),
204
98
  database: z.string().optional().describe("Database name"),
205
- }, async ({ connection_name, sql, database }) => {
206
- const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
207
- if (!safety.allowed)
208
- return text(`Query blocked: ${safety.reason}`);
209
- return bridgeRequest("/execute-query", { connection_name, sql, database }, "Query sent to DBX");
99
+ schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
100
+ tables: z.array(z.string()).optional().describe("Specific table names to include"),
101
+ max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
102
+ }, async ({ connection_name, database, schema, tables, max_tables }) => {
103
+ const config = await backend.findConnection(connection_name);
104
+ if (!config)
105
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
106
+ const context = await buildSchemaContext(backend, withDatabase(config, database), {
107
+ schema,
108
+ tables,
109
+ maxTables: max_tables,
110
+ });
111
+ if (context.tables.length === 0)
112
+ return text("No matching tables found.");
113
+ return text(formatSchemaContext(context));
210
114
  });
211
- }
212
- async function bridgeRequest(path, body, successMsg) {
213
- try {
214
- const bridgeUrl = await getBridgeUrl();
215
- const res = await fetch(`${bridgeUrl}${path}`, {
216
- method: "POST",
217
- headers: { "Content-Type": "application/json" },
218
- body: JSON.stringify(body),
115
+ server.tool("dbx_add_connection", "Add a new database connection to DBX", {
116
+ name: z.string().describe("Connection name"),
117
+ db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
118
+ host: z.string().describe("Database host"),
119
+ port: z.number().optional().describe("Database port (TDengine defaults to 6041, XuguDB defaults to 5138)"),
120
+ username: z.string().default("").describe("Username"),
121
+ password: z.string().default("").describe("Password"),
122
+ database: z.string().optional().describe("Default database name"),
123
+ ssl: z.boolean().default(false).describe("Enable SSL"),
124
+ }, async ({ name, db_type, host, port, username, password, database, ssl }) => {
125
+ const existing = await backend.findConnection(name);
126
+ if (existing)
127
+ return text(`Connection "${name}" already exists.`);
128
+ const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
129
+ const DEFAULT_PORTS = { tdengine: 6041, xugu: 5138 };
130
+ const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
131
+ if (resolvedPort === undefined)
132
+ return text("Port is required for this database type.");
133
+ const config = await backend.addConnection({
134
+ name,
135
+ db_type,
136
+ host,
137
+ port: resolvedPort,
138
+ username,
139
+ password,
140
+ database,
141
+ ssl,
142
+ ssh_enabled: false,
143
+ });
144
+ await notifyReload();
145
+ return text(`Connection "${config.name}" added (id: ${config.id}).`);
146
+ });
147
+ server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
148
+ connection_name: z.string().describe("Name of the connection to remove"),
149
+ }, async ({ connection_name }) => {
150
+ const removed = await backend.removeConnection(connection_name);
151
+ if (!removed)
152
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
153
+ await notifyReload();
154
+ return text(`Connection "${connection_name}" removed.`);
155
+ });
156
+ // Desktop-only tools: open table and execute-and-show require the Tauri bridge
157
+ if (!isWebMode) {
158
+ server.tool("dbx_open_table", "Open a table in DBX desktop app UI. Requires DBX to be running.", {
159
+ connection_name: z.string().describe("Name of the DBX connection"),
160
+ table: z.string().describe("Table name to open"),
161
+ database: z.string().optional().describe("Database name"),
162
+ schema: z.string().optional().describe("Schema name"),
163
+ }, async ({ connection_name, table, database, schema }) => {
164
+ const config = await backend.findConnection(connection_name);
165
+ if (!config)
166
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
167
+ return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
168
+ });
169
+ server.tool("dbx_execute_and_show", "Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.", {
170
+ connection_name: z.string().describe("Name of the DBX connection"),
171
+ sql: z.string().describe("SQL query to execute"),
172
+ database: z.string().optional().describe("Database name"),
173
+ }, async ({ connection_name, sql, database }) => {
174
+ const config = await backend.findConnection(connection_name);
175
+ if (!config)
176
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
177
+ const safetyOptions = sqlSafetyFromEnv();
178
+ if (config?.db_type === "mongodb") {
179
+ const aggregate = parseMongoAggregateCommand(sql);
180
+ if (aggregate) {
181
+ const safety = evaluateMongoAggregateSafety(aggregate, safetyOptions);
182
+ if (!safety.allowed)
183
+ return toolError("SQL_BLOCKED", safety.reason ?? "Query blocked.");
184
+ }
185
+ }
186
+ else {
187
+ const safety = evaluateSqlSafety(sql, safetyOptions);
188
+ if (!safety.allowed)
189
+ return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
190
+ }
191
+ // MongoDB shell commands bypass the SQL safety evaluator; pass MCP
192
+ // safety flags to the desktop executor for command-aware gating.
193
+ return bridgeRequest("/execute-query", {
194
+ connection_name,
195
+ sql,
196
+ database,
197
+ allow_writes: safetyOptions.allowWrites,
198
+ allow_dangerous: safetyOptions.allowDangerous,
199
+ }, "Query sent to DBX");
219
200
  });
220
- if (res.ok)
221
- return text(successMsg);
222
- return text(`Failed: ${await res.text()}`);
223
- }
224
- catch {
225
- return text("DBX is not running. Please start DBX first.");
226
201
  }
202
+ return server;
203
+ }
204
+ async function bridgeRequest(path, body, successMsg) {
205
+ const res = await postBridge(path, body);
206
+ if (res.ok)
207
+ return text(successMsg);
208
+ const message = res.text.startsWith("DBX is not running") ? res.text : `Failed: ${res.text}`;
209
+ return toolError("DBX_NOT_RUNNING", message);
227
210
  }
228
211
  async function main() {
212
+ const backend = await createBackend();
213
+ const server = createDbxMcpServer(backend);
229
214
  const transport = new StdioServerTransport();
230
215
  await server.connect(transport);
231
216
  }
232
- main().catch((e) => {
233
- console.error("MCP Server failed to start:", e);
234
- process.exit(1);
235
- });
217
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
218
+ main().catch((e) => {
219
+ console.error("MCP Server failed to start:", e);
220
+ process.exit(1);
221
+ });
222
+ }
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "@dbx-app/mcp-server",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "mcpName": "io.github.t8y2/dbx",
5
5
  "description": "MCP server for DBX — query databases from Claude Code, Cursor, and other AI agents",
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": ">=22.13.0"
9
+ },
7
10
  "bin": {
11
+ "mcp-server": "dist/index.js",
8
12
  "dbx-mcp-server": "dist/index.js"
9
13
  },
10
14
  "files": [
11
- "dist"
15
+ "dist",
16
+ "server.json"
12
17
  ],
13
- "scripts": {
14
- "start": "tsx src/index.ts",
15
- "test": "tsx --test tests/*.test.ts",
16
- "build": "tsc",
17
- "prepublishOnly": "tsc"
18
- },
19
18
  "keywords": [
20
19
  "mcp",
21
20
  "database",
@@ -29,28 +28,22 @@
29
28
  "repository": {
30
29
  "type": "git",
31
30
  "url": "https://github.com/t8y2/dbx",
32
- "directory": "mcp"
31
+ "directory": "packages/mcp-server"
33
32
  },
34
- "homepage": "https://github.com/t8y2/dbx/tree/main/mcp",
33
+ "homepage": "https://github.com/t8y2/dbx/tree/main/packages/mcp-server",
35
34
  "dependencies": {
36
35
  "@modelcontextprotocol/sdk": "^1.12.1",
37
- "better-sqlite3": "^12.9.0",
38
- "keytar": "^7.9.0",
39
- "mysql2": "^3.14.1",
40
- "pg": "^8.16.0",
41
- "zod": "^3.25.20"
36
+ "zod": "^3.25.20",
37
+ "@dbx-app/node-core": "^0.4.4"
42
38
  },
43
39
  "devDependencies": {
44
- "@types/better-sqlite3": "^7.6.13",
45
40
  "@types/node": "^22.15.21",
46
- "@types/pg": "^8.15.4",
47
41
  "tsx": "^4.19.4",
48
42
  "typescript": "^5.8.3"
49
43
  },
50
- "pnpm": {
51
- "onlyBuiltDependencies": [
52
- "better-sqlite3",
53
- "esbuild"
54
- ]
44
+ "scripts": {
45
+ "start": "tsx src/index.ts",
46
+ "test": "pnpm --filter @dbx-app/node-core build && tsx --test tests/*.test.ts",
47
+ "build": "pnpm --filter @dbx-app/node-core build && tsc"
55
48
  }
56
- }
49
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.t8y2/dbx",
4
+ "description": "Query and manage databases from AI agents using connections configured in DBX.",
5
+ "repository": {
6
+ "url": "https://github.com/t8y2/dbx",
7
+ "source": "github"
8
+ },
9
+ "version": "0.4.4",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@dbx-app/mcp-server",
14
+ "version": "0.4.4",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }
@@ -1,25 +0,0 @@
1
- export interface ConnectionConfig {
2
- id: string;
3
- name: string;
4
- db_type: string;
5
- driver_profile?: string;
6
- host: string;
7
- port: number;
8
- username: string;
9
- password: string;
10
- database?: string;
11
- url_params?: string;
12
- ssh_enabled: boolean;
13
- proxy_enabled?: boolean;
14
- proxy_type?: "socks5" | "http";
15
- proxy_host?: string;
16
- proxy_port?: number;
17
- proxy_username?: string;
18
- proxy_password?: string;
19
- ssl: boolean;
20
- }
21
- export declare function canonicalizeConnection(config: ConnectionConfig): ConnectionConfig;
22
- export declare function loadConnections(): Promise<ConnectionConfig[]>;
23
- export declare function findConnection(name: string): Promise<ConnectionConfig | undefined>;
24
- export declare function addConnection(config: Omit<ConnectionConfig, "id">): Promise<ConnectionConfig>;
25
- export declare function removeConnection(name: string): Promise<boolean>;