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