@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/LICENSE +661 -0
- package/README.md +27 -3
- package/dist/index.d.ts +7 -1
- package/dist/index.js +198 -211
- package/package.json +16 -23
- package/server.json +20 -0
- package/dist/connections.d.ts +0 -25
- package/dist/connections.js +0 -130
- package/dist/database.d.ts +0 -21
- package/dist/database.js +0 -408
- package/dist/schema-context.d.ts +0 -25
- package/dist/schema-context.js +0 -43
- package/dist/sql-safety.d.ts +0 -10
- package/dist/sql-safety.js +0 -103
- package/dist/web-backend.d.ts +0 -9
- package/dist/web-backend.js +0 -114
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
33
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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.
|
|
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
|
-
"
|
|
38
|
-
"
|
|
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
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
}
|
package/dist/connections.d.ts
DELETED
|
@@ -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>;
|