@dbx-app/mcp-server 0.4.1 → 0.4.3
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 +25 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +158 -205
- 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 |
|
|
@@ -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
|
| 工具 | 说明 |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
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 function createDbxMcpServer(backend: Backend, options?: {
|
|
5
|
+
isWebMode?: boolean;
|
|
6
|
+
}): McpServer;
|
package/dist/index.js
CHANGED
|
@@ -1,31 +1,9 @@
|
|
|
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 { pathToFileURL } from "node:url";
|
|
4
5
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
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
|
-
}
|
|
6
|
+
import { buildSchemaContext, createBackend, evaluateSqlSafety, formatSchemaContext, notifyReload, postBridge, sqlSafetyFromEnv, } from "@dbx-app/node-core";
|
|
29
7
|
function text(s) {
|
|
30
8
|
return { content: [{ type: "text", text: s }] };
|
|
31
9
|
}
|
|
@@ -36,200 +14,175 @@ function mdTable(headers, rows) {
|
|
|
36
14
|
const body = rows.map((r) => `| ${r.map((c, i) => (c || "").padEnd(widths[i])).join(" | ")} |`).join("\n");
|
|
37
15
|
return `${header}\n${sep}\n${body}`;
|
|
38
16
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
17
|
+
export function createDbxMcpServer(backend, options = {}) {
|
|
18
|
+
const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: "dbx",
|
|
21
|
+
version: "0.4.2",
|
|
148
22
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
}
|
|
178
|
-
}
|
|
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}`;
|
|
183
|
-
}
|
|
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.", {
|
|
23
|
+
server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
|
|
24
|
+
const connections = await backend.loadConnections();
|
|
25
|
+
if (connections.length === 0)
|
|
26
|
+
return text("No connections configured in DBX.");
|
|
27
|
+
const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
|
|
28
|
+
return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
|
|
29
|
+
});
|
|
30
|
+
server.tool("dbx_list_tables", "List tables and views for a database connection", {
|
|
194
31
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
32
|
+
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
33
|
+
}, async ({ connection_name, schema }) => {
|
|
34
|
+
const config = await backend.findConnection(connection_name);
|
|
35
|
+
if (!config)
|
|
36
|
+
return text(`Connection "${connection_name}" not found`);
|
|
37
|
+
const tables = await backend.listTables(config, schema);
|
|
38
|
+
if (tables.length === 0)
|
|
39
|
+
return text("No tables found.");
|
|
40
|
+
const rows = tables.map((t) => [t.name, t.type]);
|
|
41
|
+
return text(mdTable(["Table", "Type"], rows));
|
|
200
42
|
});
|
|
201
|
-
server.tool("
|
|
43
|
+
server.tool("dbx_describe_table", "Get column definitions for a table", {
|
|
44
|
+
connection_name: z.string().describe("Name of the DBX connection"),
|
|
45
|
+
table: z.string().describe("Table name"),
|
|
46
|
+
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
47
|
+
}, async ({ connection_name, table, schema }) => {
|
|
48
|
+
const config = await backend.findConnection(connection_name);
|
|
49
|
+
if (!config)
|
|
50
|
+
return text(`Connection "${connection_name}" not found`);
|
|
51
|
+
const columns = await backend.describeTable(config, table, schema);
|
|
52
|
+
if (columns.length === 0)
|
|
53
|
+
return text("No columns found.");
|
|
54
|
+
const rows = columns.map((c) => [
|
|
55
|
+
c.is_primary_key ? `${c.name} (PK)` : c.name,
|
|
56
|
+
c.data_type,
|
|
57
|
+
c.is_nullable ? "YES" : "NO",
|
|
58
|
+
c.column_default ?? "",
|
|
59
|
+
c.comment ?? "",
|
|
60
|
+
]);
|
|
61
|
+
return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
|
|
62
|
+
});
|
|
63
|
+
server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
|
|
202
64
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
203
65
|
sql: z.string().describe("SQL query to execute"),
|
|
204
|
-
|
|
205
|
-
|
|
66
|
+
}, async ({ connection_name, sql }) => {
|
|
67
|
+
const config = await backend.findConnection(connection_name);
|
|
68
|
+
if (!config)
|
|
69
|
+
return text(`Connection "${connection_name}" not found`);
|
|
206
70
|
const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
|
|
207
71
|
if (!safety.allowed)
|
|
208
72
|
return text(`Query blocked: ${safety.reason}`);
|
|
209
|
-
|
|
73
|
+
try {
|
|
74
|
+
const result = await backend.executeQuery(config, sql);
|
|
75
|
+
if (result.columns.length === 0)
|
|
76
|
+
return text(`Query executed. ${result.row_count} row(s) affected.`);
|
|
77
|
+
const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
|
|
78
|
+
return text(`${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
82
|
+
return text(`Query error: ${msg}`);
|
|
83
|
+
}
|
|
210
84
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
85
|
+
server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
|
|
86
|
+
connection_name: z.string().describe("Name of the DBX connection"),
|
|
87
|
+
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
88
|
+
tables: z.array(z.string()).optional().describe("Specific table names to include"),
|
|
89
|
+
max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
|
|
90
|
+
}, async ({ connection_name, schema, tables, max_tables }) => {
|
|
91
|
+
const config = await backend.findConnection(connection_name);
|
|
92
|
+
if (!config)
|
|
93
|
+
return text(`Connection "${connection_name}" not found`);
|
|
94
|
+
const context = await buildSchemaContext(backend, config, { schema, tables, maxTables: max_tables });
|
|
95
|
+
if (context.tables.length === 0)
|
|
96
|
+
return text("No matching tables found.");
|
|
97
|
+
return text(formatSchemaContext(context));
|
|
98
|
+
});
|
|
99
|
+
server.tool("dbx_add_connection", "Add a new database connection to DBX", {
|
|
100
|
+
name: z.string().describe("Connection name"),
|
|
101
|
+
db_type: z
|
|
102
|
+
.string()
|
|
103
|
+
.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"),
|
|
104
|
+
host: z.string().describe("Database host"),
|
|
105
|
+
port: z.number().optional().describe("Database port (TDengine defaults to 6041)"),
|
|
106
|
+
username: z.string().default("").describe("Username"),
|
|
107
|
+
password: z.string().default("").describe("Password"),
|
|
108
|
+
database: z.string().optional().describe("Default database name"),
|
|
109
|
+
ssl: z.boolean().default(false).describe("Enable SSL"),
|
|
110
|
+
}, async ({ name, db_type, host, port, username, password, database, ssl }) => {
|
|
111
|
+
const existing = await backend.findConnection(name);
|
|
112
|
+
if (existing)
|
|
113
|
+
return text(`Connection "${name}" already exists.`);
|
|
114
|
+
const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
|
|
115
|
+
const resolvedPort = port ?? (db_type === "tdengine" ? 6041 : FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
|
|
116
|
+
if (resolvedPort === undefined)
|
|
117
|
+
return text("Port is required for this database type.");
|
|
118
|
+
const config = await backend.addConnection({
|
|
119
|
+
name,
|
|
120
|
+
db_type,
|
|
121
|
+
host,
|
|
122
|
+
port: resolvedPort,
|
|
123
|
+
username,
|
|
124
|
+
password,
|
|
125
|
+
database,
|
|
126
|
+
ssl,
|
|
127
|
+
ssh_enabled: false,
|
|
128
|
+
});
|
|
129
|
+
await notifyReload();
|
|
130
|
+
return text(`Connection "${config.name}" added (id: ${config.id}).`);
|
|
131
|
+
});
|
|
132
|
+
server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
|
|
133
|
+
connection_name: z.string().describe("Name of the connection to remove"),
|
|
134
|
+
}, async ({ connection_name }) => {
|
|
135
|
+
const removed = await backend.removeConnection(connection_name);
|
|
136
|
+
if (!removed)
|
|
137
|
+
return text(`Connection "${connection_name}" not found.`);
|
|
138
|
+
await notifyReload();
|
|
139
|
+
return text(`Connection "${connection_name}" removed.`);
|
|
140
|
+
});
|
|
141
|
+
// Desktop-only tools: open table and execute-and-show require the Tauri bridge
|
|
142
|
+
if (!isWebMode) {
|
|
143
|
+
server.tool("dbx_open_table", "Open a table in DBX desktop app UI. Requires DBX to be running.", {
|
|
144
|
+
connection_name: z.string().describe("Name of the DBX connection"),
|
|
145
|
+
table: z.string().describe("Table name to open"),
|
|
146
|
+
database: z.string().optional().describe("Database name"),
|
|
147
|
+
schema: z.string().optional().describe("Schema name"),
|
|
148
|
+
}, async ({ connection_name, table, database, schema }) => {
|
|
149
|
+
return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
|
|
150
|
+
});
|
|
151
|
+
server.tool("dbx_execute_and_show", "Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.", {
|
|
152
|
+
connection_name: z.string().describe("Name of the DBX connection"),
|
|
153
|
+
sql: z.string().describe("SQL query to execute"),
|
|
154
|
+
database: z.string().optional().describe("Database name"),
|
|
155
|
+
}, async ({ connection_name, sql, database }) => {
|
|
156
|
+
const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
|
|
157
|
+
if (!safety.allowed)
|
|
158
|
+
return text(`Query blocked: ${safety.reason}`);
|
|
159
|
+
return bridgeRequest("/execute-query", { connection_name, sql, database }, "Query sent to DBX");
|
|
219
160
|
});
|
|
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
161
|
}
|
|
162
|
+
return server;
|
|
163
|
+
}
|
|
164
|
+
function formatCell(value) {
|
|
165
|
+
if (value === null || value === undefined)
|
|
166
|
+
return "NULL";
|
|
167
|
+
if (typeof value === "object")
|
|
168
|
+
return JSON.stringify(value);
|
|
169
|
+
return String(value);
|
|
170
|
+
}
|
|
171
|
+
async function bridgeRequest(path, body, successMsg) {
|
|
172
|
+
const res = await postBridge(path, body);
|
|
173
|
+
if (res.ok)
|
|
174
|
+
return text(successMsg);
|
|
175
|
+
return text(res.text.startsWith("DBX is not running") ? res.text : `Failed: ${res.text}`);
|
|
227
176
|
}
|
|
228
177
|
async function main() {
|
|
178
|
+
const backend = await createBackend();
|
|
179
|
+
const server = createDbxMcpServer(backend);
|
|
229
180
|
const transport = new StdioServerTransport();
|
|
230
181
|
await server.connect(transport);
|
|
231
182
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
183
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
184
|
+
main().catch((e) => {
|
|
185
|
+
console.error("MCP Server failed to start:", e);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
|
188
|
+
}
|
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.3",
|
|
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.3"
|
|
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.3",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@dbx-app/mcp-server",
|
|
14
|
+
"version": "0.4.3",
|
|
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>;
|