@dbx-app/mcp-server 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -17
- package/dist/connections.d.ts +2 -0
- package/dist/connections.js +45 -31
- package/dist/index.js +81 -25
- package/dist/web-backend.d.ts +9 -0
- package/dist/web-backend.js +114 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ MCP server for [DBX](https://github.com/t8y2/dbx) — lets AI agents (Claude Cod
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Zero config** — Automatically reads your DBX connections (including passwords from system keyring)
|
|
10
|
-
- **
|
|
10
|
+
- **7 tools** — List/add/remove connections, list tables, describe table, execute SQL, open table in DBX UI
|
|
11
11
|
- **Connection pooling** — Reuses database connections across queries
|
|
12
12
|
- **PostgreSQL & MySQL** — Supports PostgreSQL, MySQL, and compatible databases (Doris, StarRocks, etc.)
|
|
13
13
|
- **DBX UI integration** — Open tables directly in the DBX desktop app from your AI agent
|
|
@@ -69,6 +69,8 @@ In Claude Code, just ask:
|
|
|
69
69
|
| Tool | Description |
|
|
70
70
|
|---|---|
|
|
71
71
|
| `dbx_list_connections` | List all database connections configured in DBX |
|
|
72
|
+
| `dbx_add_connection` | Add a new database connection |
|
|
73
|
+
| `dbx_remove_connection` | Remove a database connection |
|
|
72
74
|
| `dbx_list_tables` | List tables and views for a connection |
|
|
73
75
|
| `dbx_describe_table` | Get column definitions for a table |
|
|
74
76
|
| `dbx_execute_query` | Execute a SQL query (max 100 rows) |
|
|
@@ -79,17 +81,14 @@ In Claude Code, just ask:
|
|
|
79
81
|
```
|
|
80
82
|
AI Agent → MCP Server → Database
|
|
81
83
|
↓
|
|
82
|
-
DBX
|
|
83
|
-
+ system keyring (passwords)
|
|
84
|
+
DBX SQLite database (dbx.db)
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
The MCP server reads your database connections from DBX's
|
|
87
|
+
The MCP server reads your database connections from DBX's SQLite database:
|
|
87
88
|
|
|
88
|
-
- **macOS**: `~/Library/Application Support/com.dbx.app/
|
|
89
|
-
- **Linux**: `~/.config/com.dbx.app/
|
|
90
|
-
- **Windows**: `%APPDATA%\com.dbx.app\
|
|
91
|
-
|
|
92
|
-
Passwords are retrieved from the system keyring (macOS Keychain / Linux Secret Service / Windows Credential Manager).
|
|
89
|
+
- **macOS**: `~/Library/Application Support/com.dbx.app/dbx.db`
|
|
90
|
+
- **Linux**: `~/.config/com.dbx.app/dbx.db`
|
|
91
|
+
- **Windows**: `%APPDATA%\com.dbx.app\dbx.db`
|
|
93
92
|
|
|
94
93
|
## DBX UI Integration
|
|
95
94
|
|
|
@@ -112,8 +111,8 @@ MIT
|
|
|
112
111
|
|
|
113
112
|
### 特性
|
|
114
113
|
|
|
115
|
-
- **零配置** — 自动读取 DBX
|
|
116
|
-
- **
|
|
114
|
+
- **零配置** — 自动读取 DBX 的连接配置
|
|
115
|
+
- **7 个工具** — 列出/添加/删除连接、列出表、查看表结构、执行 SQL、在 DBX 中打开表
|
|
117
116
|
- **连接池** — 跨查询复用数据库连接
|
|
118
117
|
- **PostgreSQL 和 MySQL** — 支持 PostgreSQL、MySQL 及兼容数据库(Doris、StarRocks 等)
|
|
119
118
|
- **DBX UI 联动** — 从 AI 助手直接在 DBX 桌面端打开表
|
|
@@ -161,6 +160,8 @@ npx @dbx-app/mcp-server
|
|
|
161
160
|
| 工具 | 说明 |
|
|
162
161
|
|---|---|
|
|
163
162
|
| `dbx_list_connections` | 列出 DBX 中所有已配置的数据库连接 |
|
|
163
|
+
| `dbx_add_connection` | 添加新的数据库连接 |
|
|
164
|
+
| `dbx_remove_connection` | 删除数据库连接 |
|
|
164
165
|
| `dbx_list_tables` | 列出指定连接的表和视图 |
|
|
165
166
|
| `dbx_describe_table` | 获取表的列定义 |
|
|
166
167
|
| `dbx_execute_query` | 执行 SQL 查询(最多返回 100 行) |
|
|
@@ -168,13 +169,11 @@ npx @dbx-app/mcp-server
|
|
|
168
169
|
|
|
169
170
|
### 工作原理
|
|
170
171
|
|
|
171
|
-
MCP Server 从 DBX
|
|
172
|
-
|
|
173
|
-
- **macOS**: `~/Library/Application Support/com.dbx.app/connections.json`
|
|
174
|
-
- **Linux**: `~/.config/com.dbx.app/connections.json`
|
|
175
|
-
- **Windows**: `%APPDATA%\com.dbx.app\connections.json`
|
|
172
|
+
MCP Server 从 DBX 的 SQLite 数据库读取连接信息:
|
|
176
173
|
|
|
177
|
-
|
|
174
|
+
- **macOS**: `~/Library/Application Support/com.dbx.app/dbx.db`
|
|
175
|
+
- **Linux**: `~/.config/com.dbx.app/dbx.db`
|
|
176
|
+
- **Windows**: `%APPDATA%\com.dbx.app\dbx.db`
|
|
178
177
|
|
|
179
178
|
### DBX UI 联动
|
|
180
179
|
|
package/dist/connections.d.ts
CHANGED
|
@@ -14,3 +14,5 @@ export interface ConnectionConfig {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function loadConnections(): Promise<ConnectionConfig[]>;
|
|
16
16
|
export declare function findConnection(name: string): Promise<ConnectionConfig | undefined>;
|
|
17
|
+
export declare function addConnection(config: Omit<ConnectionConfig, "id">): Promise<ConnectionConfig>;
|
|
18
|
+
export declare function removeConnection(name: string): Promise<boolean>;
|
package/dist/connections.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { homedir, platform } from "node:os";
|
|
4
|
-
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
5
|
function appDataDir() {
|
|
6
6
|
const home = homedir();
|
|
7
7
|
switch (platform()) {
|
|
@@ -13,41 +13,27 @@ function appDataDir() {
|
|
|
13
13
|
return join(home, ".config", "com.dbx.app");
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return await keytar.default.getPassword(KEYRING_SERVICE, `connection:${connectionId}:${key}`);
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function getFileSecret(connectionId, key) {
|
|
26
|
-
try {
|
|
27
|
-
const secretsPath = join(appDataDir(), "secrets.json");
|
|
28
|
-
const data = JSON.parse(await readFile(secretsPath, "utf-8"));
|
|
29
|
-
return data[`connection:${connectionId}:${key}`] ?? null;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
16
|
+
function openDb(readonly = false) {
|
|
17
|
+
const dbPath = join(appDataDir(), "dbx.db");
|
|
18
|
+
return new Database(dbPath, { readonly });
|
|
34
19
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
return fromKeyring;
|
|
39
|
-
const fromFile = await getFileSecret(connectionId, key);
|
|
40
|
-
return fromFile ?? "";
|
|
20
|
+
function getSecret(db, connectionId, key) {
|
|
21
|
+
const row = db.prepare("SELECT secret FROM connection_secrets WHERE connection_id = ? AND key = ?").get(connectionId, key);
|
|
22
|
+
return row?.secret ?? "";
|
|
41
23
|
}
|
|
42
24
|
export async function loadConnections() {
|
|
43
|
-
const configPath = join(appDataDir(), "connections.json");
|
|
44
25
|
try {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
26
|
+
const db = openDb(true);
|
|
27
|
+
const rows = db.prepare("SELECT id, config_json FROM connections").all();
|
|
28
|
+
const configs = [];
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const config = JSON.parse(row.config_json);
|
|
31
|
+
config.id = row.id;
|
|
48
32
|
if (!config.password)
|
|
49
|
-
config.password =
|
|
33
|
+
config.password = getSecret(db, row.id, "password");
|
|
34
|
+
configs.push(config);
|
|
50
35
|
}
|
|
36
|
+
db.close();
|
|
51
37
|
return configs;
|
|
52
38
|
}
|
|
53
39
|
catch {
|
|
@@ -58,3 +44,31 @@ export async function findConnection(name) {
|
|
|
58
44
|
const connections = await loadConnections();
|
|
59
45
|
return connections.find((c) => c.name.toLowerCase() === name.toLowerCase());
|
|
60
46
|
}
|
|
47
|
+
export async function addConnection(config) {
|
|
48
|
+
const id = randomUUID();
|
|
49
|
+
const db = openDb();
|
|
50
|
+
const sanitized = { ...config, id, password: "" };
|
|
51
|
+
const configJson = JSON.stringify(sanitized);
|
|
52
|
+
const insert = db.transaction(() => {
|
|
53
|
+
db.prepare("INSERT INTO connections (id, config_json) VALUES (?, ?)").run(id, configJson);
|
|
54
|
+
if (config.password) {
|
|
55
|
+
db.prepare("INSERT INTO connection_secrets (connection_id, key, secret) VALUES (?, ?, ?)").run(id, "password", config.password);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
insert();
|
|
59
|
+
db.close();
|
|
60
|
+
return { ...config, id };
|
|
61
|
+
}
|
|
62
|
+
export async function removeConnection(name) {
|
|
63
|
+
const connection = await findConnection(name);
|
|
64
|
+
if (!connection)
|
|
65
|
+
return false;
|
|
66
|
+
const db = openDb();
|
|
67
|
+
const remove = db.transaction(() => {
|
|
68
|
+
db.prepare("DELETE FROM connections WHERE id = ?").run(connection.id);
|
|
69
|
+
db.prepare("DELETE FROM connection_secrets WHERE connection_id = ?").run(connection.id);
|
|
70
|
+
});
|
|
71
|
+
remove();
|
|
72
|
+
db.close();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,25 @@ import { z } from "zod";
|
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { homedir, platform } from "node:os";
|
|
8
|
-
import { loadConnections, findConnection } from "./connections.js";
|
|
9
|
-
import { listTables, describeTable, executeQuery } from "./database.js";
|
|
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
|
+
const isWebMode = !!process.env.DBX_WEB_URL;
|
|
11
|
+
let backend;
|
|
12
|
+
if (isWebMode) {
|
|
13
|
+
const web = await import("./web-backend.js");
|
|
14
|
+
backend = web;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
backend = {
|
|
18
|
+
loadConnections: desktopLoadConnections,
|
|
19
|
+
findConnection: desktopFindConnection,
|
|
20
|
+
addConnection: desktopAddConnection,
|
|
21
|
+
removeConnection: desktopRemoveConnection,
|
|
22
|
+
listTables: desktopListTables,
|
|
23
|
+
describeTable: desktopDescribeTable,
|
|
24
|
+
executeQuery: desktopExecuteQuery,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
10
27
|
function text(s) {
|
|
11
28
|
return { content: [{ type: "text", text: s }] };
|
|
12
29
|
}
|
|
@@ -19,10 +36,10 @@ function mdTable(headers, rows) {
|
|
|
19
36
|
}
|
|
20
37
|
const server = new McpServer({
|
|
21
38
|
name: "dbx",
|
|
22
|
-
version: "0.
|
|
39
|
+
version: "0.2.1",
|
|
23
40
|
});
|
|
24
41
|
server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
|
|
25
|
-
const connections = await loadConnections();
|
|
42
|
+
const connections = await backend.loadConnections();
|
|
26
43
|
if (connections.length === 0)
|
|
27
44
|
return text("No connections configured in DBX.");
|
|
28
45
|
const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
|
|
@@ -32,10 +49,10 @@ server.tool("dbx_list_tables", "List tables and views for a database connection"
|
|
|
32
49
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
33
50
|
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
34
51
|
}, async ({ connection_name, schema }) => {
|
|
35
|
-
const config = await findConnection(connection_name);
|
|
52
|
+
const config = await backend.findConnection(connection_name);
|
|
36
53
|
if (!config)
|
|
37
54
|
return text(`Connection "${connection_name}" not found`);
|
|
38
|
-
const tables = await listTables(config, schema);
|
|
55
|
+
const tables = await backend.listTables(config, schema);
|
|
39
56
|
if (tables.length === 0)
|
|
40
57
|
return text("No tables found.");
|
|
41
58
|
const rows = tables.map((t) => [t.name, t.type]);
|
|
@@ -46,10 +63,10 @@ server.tool("dbx_describe_table", "Get column definitions for a table", {
|
|
|
46
63
|
table: z.string().describe("Table name"),
|
|
47
64
|
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
48
65
|
}, async ({ connection_name, table, schema }) => {
|
|
49
|
-
const config = await findConnection(connection_name);
|
|
66
|
+
const config = await backend.findConnection(connection_name);
|
|
50
67
|
if (!config)
|
|
51
68
|
return text(`Connection "${connection_name}" not found`);
|
|
52
|
-
const columns = await describeTable(config, table, schema);
|
|
69
|
+
const columns = await backend.describeTable(config, table, schema);
|
|
53
70
|
if (columns.length === 0)
|
|
54
71
|
return text("No columns found.");
|
|
55
72
|
const rows = columns.map((c) => [
|
|
@@ -65,11 +82,11 @@ server.tool("dbx_execute_query", "Execute a SQL query on a database connection (
|
|
|
65
82
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
66
83
|
sql: z.string().describe("SQL query to execute"),
|
|
67
84
|
}, async ({ connection_name, sql }) => {
|
|
68
|
-
const config = await findConnection(connection_name);
|
|
85
|
+
const config = await backend.findConnection(connection_name);
|
|
69
86
|
if (!config)
|
|
70
87
|
return text(`Connection "${connection_name}" not found`);
|
|
71
88
|
try {
|
|
72
|
-
const result = await executeQuery(config, sql);
|
|
89
|
+
const result = await backend.executeQuery(config, sql);
|
|
73
90
|
if (result.columns.length === 0)
|
|
74
91
|
return text(`Query executed. ${result.row_count} row(s) affected.`);
|
|
75
92
|
const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
|
|
@@ -80,6 +97,35 @@ server.tool("dbx_execute_query", "Execute a SQL query on a database connection (
|
|
|
80
97
|
return text(`Query error: ${msg}`);
|
|
81
98
|
}
|
|
82
99
|
});
|
|
100
|
+
server.tool("dbx_add_connection", "Add a new database connection to DBX", {
|
|
101
|
+
name: z.string().describe("Connection name"),
|
|
102
|
+
db_type: z.string().describe("Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch"),
|
|
103
|
+
host: z.string().describe("Database host"),
|
|
104
|
+
port: z.number().describe("Database port"),
|
|
105
|
+
username: z.string().default("").describe("Username"),
|
|
106
|
+
password: z.string().default("").describe("Password"),
|
|
107
|
+
database: z.string().optional().describe("Default database name"),
|
|
108
|
+
ssl: z.boolean().default(false).describe("Enable SSL"),
|
|
109
|
+
}, async ({ name, db_type, host, port, username, password, database, ssl }) => {
|
|
110
|
+
const existing = await backend.findConnection(name);
|
|
111
|
+
if (existing)
|
|
112
|
+
return text(`Connection "${name}" already exists.`);
|
|
113
|
+
const config = await backend.addConnection({
|
|
114
|
+
name, db_type, host, port, username, password,
|
|
115
|
+
database, ssl, ssh_enabled: false,
|
|
116
|
+
});
|
|
117
|
+
await notifyReload();
|
|
118
|
+
return text(`Connection "${config.name}" added (id: ${config.id}).`);
|
|
119
|
+
});
|
|
120
|
+
server.tool("dbx_remove_connection", "Remove a database connection from DBX", {
|
|
121
|
+
connection_name: z.string().describe("Name of the connection to remove"),
|
|
122
|
+
}, async ({ connection_name }) => {
|
|
123
|
+
const removed = await backend.removeConnection(connection_name);
|
|
124
|
+
if (!removed)
|
|
125
|
+
return text(`Connection "${connection_name}" not found.`);
|
|
126
|
+
await notifyReload();
|
|
127
|
+
return text(`Connection "${connection_name}" removed.`);
|
|
128
|
+
});
|
|
83
129
|
function formatCell(value) {
|
|
84
130
|
if (value === null || value === undefined)
|
|
85
131
|
return "NULL";
|
|
@@ -103,21 +149,31 @@ async function getBridgeUrl() {
|
|
|
103
149
|
const port = (await readFile(portFile, "utf-8")).trim();
|
|
104
150
|
return `http://127.0.0.1:${port}`;
|
|
105
151
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})
|
|
152
|
+
async function notifyReload() {
|
|
153
|
+
try {
|
|
154
|
+
const bridgeUrl = await getBridgeUrl();
|
|
155
|
+
await fetch(`${bridgeUrl}/reload-connections`, { method: "POST" });
|
|
156
|
+
}
|
|
157
|
+
catch { }
|
|
158
|
+
}
|
|
159
|
+
// Desktop-only tools: open table and execute-and-show require the Tauri bridge
|
|
160
|
+
if (!isWebMode) {
|
|
161
|
+
server.tool("dbx_open_table", "Open a table in DBX desktop app UI. Requires DBX to be running.", {
|
|
162
|
+
connection_name: z.string().describe("Name of the DBX connection"),
|
|
163
|
+
table: z.string().describe("Table name to open"),
|
|
164
|
+
database: z.string().optional().describe("Database name"),
|
|
165
|
+
schema: z.string().optional().describe("Schema name"),
|
|
166
|
+
}, async ({ connection_name, table, database, schema }) => {
|
|
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
|
+
return bridgeRequest("/execute-query", { connection_name, sql, database }, "Query sent to DBX");
|
|
175
|
+
});
|
|
176
|
+
}
|
|
121
177
|
async function bridgeRequest(path, body, successMsg) {
|
|
122
178
|
try {
|
|
123
179
|
const bridgeUrl = await getBridgeUrl();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConnectionConfig } from "./connections.js";
|
|
2
|
+
import type { TableInfo, ColumnInfo, QueryResult } from "./database.js";
|
|
3
|
+
export declare function loadConnections(): Promise<ConnectionConfig[]>;
|
|
4
|
+
export declare function findConnection(name: string): Promise<ConnectionConfig | undefined>;
|
|
5
|
+
export declare function addConnection(config: Omit<ConnectionConfig, "id">): Promise<ConnectionConfig>;
|
|
6
|
+
export declare function removeConnection(name: string): Promise<boolean>;
|
|
7
|
+
export declare function listTables(config: ConnectionConfig, schema?: string): Promise<TableInfo[]>;
|
|
8
|
+
export declare function describeTable(config: ConnectionConfig, table: string, schema?: string): Promise<ColumnInfo[]>;
|
|
9
|
+
export declare function executeQuery(config: ConnectionConfig, sql: string): Promise<QueryResult>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const baseUrl = process.env.DBX_WEB_URL.replace(/\/+$/, "");
|
|
2
|
+
const password = process.env.DBX_WEB_PASSWORD || "";
|
|
3
|
+
let sessionCookie = null;
|
|
4
|
+
async function ensureAuth() {
|
|
5
|
+
if (sessionCookie)
|
|
6
|
+
return;
|
|
7
|
+
if (!password)
|
|
8
|
+
return; // no password set, assume no auth required
|
|
9
|
+
const res = await fetch(`${baseUrl}/api/auth/login`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { "Content-Type": "application/json" },
|
|
12
|
+
body: JSON.stringify({ password }),
|
|
13
|
+
redirect: "manual",
|
|
14
|
+
});
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
throw new Error(`Authentication failed: ${res.status} ${res.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
const setCookie = res.headers.get("set-cookie");
|
|
19
|
+
if (setCookie) {
|
|
20
|
+
const match = setCookie.match(/dbx_session=([^;]+)/);
|
|
21
|
+
if (match) {
|
|
22
|
+
sessionCookie = match[1];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function headers(extra) {
|
|
27
|
+
const h = { "Content-Type": "application/json", ...extra };
|
|
28
|
+
if (sessionCookie) {
|
|
29
|
+
h["Cookie"] = `dbx_session=${sessionCookie}`;
|
|
30
|
+
}
|
|
31
|
+
return h;
|
|
32
|
+
}
|
|
33
|
+
async function apiFetch(path, init) {
|
|
34
|
+
await ensureAuth();
|
|
35
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
36
|
+
...init,
|
|
37
|
+
headers: headers(init?.headers),
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.text().catch(() => "");
|
|
41
|
+
throw new Error(`API request ${path} failed: ${res.status} ${res.statusText} ${body}`);
|
|
42
|
+
}
|
|
43
|
+
return res;
|
|
44
|
+
}
|
|
45
|
+
export async function loadConnections() {
|
|
46
|
+
const res = await apiFetch("/api/connection/list");
|
|
47
|
+
return res.json();
|
|
48
|
+
}
|
|
49
|
+
export async function findConnection(name) {
|
|
50
|
+
const connections = await loadConnections();
|
|
51
|
+
return connections.find((c) => c.name.toLowerCase() === name.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
export async function addConnection(config) {
|
|
54
|
+
const res = await apiFetch("/api/connection/save", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: JSON.stringify({ configs: [config] }),
|
|
57
|
+
});
|
|
58
|
+
const saved = (await res.json());
|
|
59
|
+
return saved;
|
|
60
|
+
}
|
|
61
|
+
export async function removeConnection(name) {
|
|
62
|
+
const connection = await findConnection(name);
|
|
63
|
+
if (!connection)
|
|
64
|
+
return false;
|
|
65
|
+
await apiFetch(`/api/connection/delete?id=${encodeURIComponent(connection.id)}`, { method: "DELETE" });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
async function ensureConnected(config) {
|
|
69
|
+
await apiFetch("/api/connection/connect", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: JSON.stringify({ config }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export async function listTables(config, schema) {
|
|
75
|
+
await ensureConnected(config);
|
|
76
|
+
const params = new URLSearchParams({
|
|
77
|
+
connection_id: config.id,
|
|
78
|
+
database: config.database || "",
|
|
79
|
+
schema: schema || "",
|
|
80
|
+
});
|
|
81
|
+
const res = await apiFetch(`/api/schema/tables?${params}`);
|
|
82
|
+
return res.json();
|
|
83
|
+
}
|
|
84
|
+
export async function describeTable(config, table, schema) {
|
|
85
|
+
await ensureConnected(config);
|
|
86
|
+
const params = new URLSearchParams({
|
|
87
|
+
connection_id: config.id,
|
|
88
|
+
database: config.database || "",
|
|
89
|
+
schema: schema || "",
|
|
90
|
+
table,
|
|
91
|
+
});
|
|
92
|
+
const res = await apiFetch(`/api/schema/columns?${params}`);
|
|
93
|
+
return res.json();
|
|
94
|
+
}
|
|
95
|
+
export async function executeQuery(config, sql) {
|
|
96
|
+
await ensureConnected(config);
|
|
97
|
+
const res = await apiFetch("/api/query/execute", {
|
|
98
|
+
method: "POST",
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
connectionId: config.id,
|
|
101
|
+
database: config.database || "",
|
|
102
|
+
sql,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
const data = (await res.json());
|
|
106
|
+
const rows = data.rows.map((row) => {
|
|
107
|
+
const obj = {};
|
|
108
|
+
data.columns.forEach((col, i) => {
|
|
109
|
+
obj[col] = row[i];
|
|
110
|
+
});
|
|
111
|
+
return obj;
|
|
112
|
+
});
|
|
113
|
+
return { columns: data.columns, rows, row_count: rows.length };
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbx-app/mcp-server",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.t8y2/dbx",
|
|
5
|
-
"description": "MCP server for DBX
|
|
5
|
+
"description": "MCP server for DBX \u2014 query databases from Claude Code, Cursor, and other AI agents",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"dbx-mcp-server": "dist/index.js"
|
|
@@ -33,15 +33,23 @@
|
|
|
33
33
|
"homepage": "https://github.com/t8y2/dbx/tree/main/mcp",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
36
|
+
"better-sqlite3": "^12.9.0",
|
|
36
37
|
"keytar": "^7.9.0",
|
|
37
38
|
"mysql2": "^3.14.1",
|
|
38
39
|
"pg": "^8.16.0",
|
|
39
40
|
"zod": "^3.25.20"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
42
44
|
"@types/node": "^22.15.21",
|
|
43
45
|
"@types/pg": "^8.15.4",
|
|
44
46
|
"tsx": "^4.19.4",
|
|
45
47
|
"typescript": "^5.8.3"
|
|
48
|
+
},
|
|
49
|
+
"pnpm": {
|
|
50
|
+
"onlyBuiltDependencies": [
|
|
51
|
+
"better-sqlite3",
|
|
52
|
+
"esbuild"
|
|
53
|
+
]
|
|
46
54
|
}
|
|
47
|
-
}
|
|
55
|
+
}
|