@deepsql/mcp 0.11.0 → 0.13.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/AGENT-SETUP.md +83 -31
- package/CLAUDE.md +382 -0
- package/README.md +109 -25
- package/claude_desktop_config.customer.example.json +3 -11
- package/codex_config.customer.example.toml +12 -8
- package/deepsql-phase1-lib.js +149 -27
- package/deepsql-phase1-server.js +1 -1
- package/package.json +3 -2
- package/skills/SKILL_BODY.md +125 -0
- package/src/api/client.js +35 -1
- package/src/cli.js +65 -20
- package/src/cli.test.js +1 -1
- package/src/commands/analyze.js +165 -0
- package/src/commands/analyze.test.js +180 -0
- package/src/commands/explain.js +18 -34
- package/src/commands/mcp.js +592 -8
- package/src/commands/mcp.test.js +529 -0
- package/src/commands/query.js +95 -13
- package/src/commands/query.test.js +214 -0
package/README.md
CHANGED
|
@@ -1,42 +1,126 @@
|
|
|
1
|
-
# DeepSQL MCP
|
|
1
|
+
# DeepSQL CLI + MCP server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `@deepsql/mcp` package ships two things in one binary:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **`deepsql`** — a CLI for talking to a self-hosted DeepSQL backend from a
|
|
6
|
+
terminal (auth, connections, SQL execution, plan analysis, index
|
|
7
|
+
suggestions, slow-query analyses, admin ops).
|
|
8
|
+
- **`deepsql mcp`** — a stdio MCP server that exposes the same backend to
|
|
9
|
+
editor agents (Claude Code, Cursor, Codex, Claude Desktop) so they can
|
|
10
|
+
query and reason about the user's databases.
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- Read-only SQL execution and explain through backend-enforced MCP endpoints
|
|
12
|
+
Both share one auth file (`~/.config/deepsql/auth.json`, mode 0600). Log
|
|
13
|
+
in once with `deepsql login`; the MCP server uses the same token
|
|
14
|
+
automatically — no token needs to be embedded in your editor's config.
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Install
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| `DEEPSQL_API_BASE_URL` | yes | `https://customer-deepsql.example.com/api/` |
|
|
19
|
-
| `DEEPSQL_AUTH_TOKEN` | yes | `dsql_mcp_...` |
|
|
20
|
-
| `DEEPSQL_MCP_TIMEOUT_MS` | no | `120000` |
|
|
21
|
-
| `DEEPSQL_MCP_USER_ID` | no | `codex-mcp` |
|
|
22
|
-
| `DEEPSQL_MCP_PROJECT_ID` | no | `codex-mcp` |
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @deepsql/mcp@latest
|
|
20
|
+
deepsql --version
|
|
21
|
+
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Requires Node ≥ 20.
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
## Quick start
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
```bash
|
|
28
|
+
deepsql login --url https://your-deepsql-host.example.com
|
|
29
|
+
deepsql connections list
|
|
30
|
+
deepsql connections use <connection-name>
|
|
31
|
+
deepsql query "SELECT 1 AS ok"
|
|
32
|
+
```
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
## Wire into your editor (one command per editor)
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
The installer writes the MCP server entry into the editor's config AND
|
|
37
|
+
installs a "DBA consult" skill that auto-triggers when the user asks
|
|
38
|
+
the agent to do database work:
|
|
33
39
|
|
|
34
40
|
```bash
|
|
35
|
-
|
|
41
|
+
deepsql mcp config --install --for claude-code # via `claude mcp add --scope user`
|
|
42
|
+
deepsql mcp config --install --for claude-desktop # macOS ~/Library/.../Claude/...
|
|
43
|
+
deepsql mcp config --install --for cursor # ~/.cursor/mcp.json + ~/.cursor/rules/deepsql.mdc
|
|
44
|
+
deepsql mcp config --install --for codex # ~/.codex/config.toml + ~/.codex/AGENTS.md
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
Pass `--print` to see what would be written without touching disk.
|
|
48
|
+
Pass `--no-skill` to install only the MCP entry. Pass `--force` to
|
|
49
|
+
overwrite a stale entry. See `deepsql mcp --help` for details.
|
|
50
|
+
|
|
51
|
+
Restart the editor for the entry to load.
|
|
52
|
+
|
|
53
|
+
## What the MCP server exposes
|
|
54
|
+
|
|
55
|
+
10 tools, all read-only at the schema/retrieval layer and policy-gated
|
|
56
|
+
at the SQL layer:
|
|
57
|
+
|
|
58
|
+
| Tool | Purpose |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `list_connections` | Connections this token has access to |
|
|
61
|
+
| `get_schema` | Cached schema metadata (tables, columns, FKs, types) |
|
|
62
|
+
| `get_database_objects` | Tables, views, functions, procedures |
|
|
63
|
+
| `get_brain_context` | Retrieval brain: tables/columns/FKs/training docs/rules for a question |
|
|
64
|
+
| `list_business_rules` | Active business rules and SQL guardrails for a connection |
|
|
65
|
+
| `get_relationships` | Inferred + validated foreign keys with confidence scores |
|
|
66
|
+
| `get_anti_patterns` | Schema-level or query-level anti-patterns |
|
|
67
|
+
| `analyze_slow_queries` | Recent slow queries with fingerprints, durations, examples |
|
|
68
|
+
| `execute_sql` | Run any SQL — backend enforces role-based policy (developers read-only, admins can mutate with two-step confirm) |
|
|
69
|
+
| `analyze_query_plan` | AI-enriched plan analysis (parsed plan tree, performance issues, index recommendations, written summary that uses the connection's schema + business rules) |
|
|
70
|
+
|
|
71
|
+
EXPLAIN and EXPLAIN ANALYZE are just SQL — pass them as the query to
|
|
72
|
+
`execute_sql`. For the AI-enriched plan analysis with the LLM-written
|
|
73
|
+
summary, use `analyze_query_plan`.
|
|
74
|
+
|
|
75
|
+
## Runtime guidance for agents
|
|
76
|
+
|
|
77
|
+
`CLAUDE.md` (bundled in this package, at
|
|
78
|
+
`node_modules/@deepsql/mcp/CLAUDE.md` after install) is the runtime
|
|
79
|
+
guide for editor agents that have these tools loaded. It covers the
|
|
80
|
+
"DBA consult" pattern, decision tree, hard rules around the role-gated
|
|
81
|
+
mutation flow, and common foot-guns. The installer also drops a
|
|
82
|
+
shortened, trigger-focused version of that guide as a native skill so
|
|
83
|
+
the pattern fires automatically on phrases like "add a table", "write
|
|
84
|
+
a migration", "design a schema", "query the database".
|
|
85
|
+
|
|
86
|
+
## Agent-driven setup (one paste)
|
|
87
|
+
|
|
88
|
+
For a fresh customer install, paste `AGENT-SETUP.md` (bundled at
|
|
89
|
+
`node_modules/@deepsql/mcp/AGENT-SETUP.md`) into Claude Code / Cursor /
|
|
90
|
+
Codex. The agent walks the user through install, login, connection
|
|
91
|
+
registration, editor integration, and end-to-end validation in ~5
|
|
92
|
+
minutes.
|
|
93
|
+
|
|
94
|
+
## Manual install (only if you don't want the CLI shim)
|
|
95
|
+
|
|
96
|
+
If you'd rather skip the `deepsql mcp config --install` flow and wire
|
|
97
|
+
the editor config by hand, see the example files bundled with this
|
|
98
|
+
package: `claude_desktop_config.customer.example.json` and
|
|
99
|
+
`codex_config.customer.example.toml`. The token-embedded shape in
|
|
100
|
+
those examples still works, but `deepsql mcp config --install` is the
|
|
101
|
+
recommended path now.
|
|
102
|
+
|
|
103
|
+
## Run the server directly (advanced)
|
|
39
104
|
|
|
40
105
|
```bash
|
|
41
|
-
|
|
106
|
+
deepsql mcp # uses the saved profile + auth token
|
|
107
|
+
npx -y @deepsql/mcp # one-off invocation without install
|
|
42
108
|
```
|
|
109
|
+
|
|
110
|
+
Both work; the CLI shim is preferred because it shares the saved
|
|
111
|
+
profile and never asks you to paste a token into editor config.
|
|
112
|
+
|
|
113
|
+
## Environment variables
|
|
114
|
+
|
|
115
|
+
| Variable | Required | Used by | Example |
|
|
116
|
+
|----------|----------|---------|---------|
|
|
117
|
+
| `DEEPSQL_API_BASE_URL` | for npx-style invocation | `deepsql mcp` if no saved profile | `https://customer-deepsql.example.com/api/` |
|
|
118
|
+
| `DEEPSQL_AUTH_TOKEN` | for npx-style invocation | bearer for backend; `deepsql login` writes one to the profile file | `dsql_mcp_…` |
|
|
119
|
+
| `DEEPSQL_MCP_USER_ID` | no | identifies the editor that invoked the MCP server in audit logs | `claude-desktop` / `cursor-mcp` / `codex-mcp` |
|
|
120
|
+
| `DEEPSQL_CALLER_AGENT` | no | overrides the CLI's audit identity when an agent shells out to `deepsql` | `claude-code` |
|
|
121
|
+
| `DEEPSQL_MCP_TIMEOUT_MS` | no | per-request timeout for MCP server calls | `120000` |
|
|
122
|
+
|
|
123
|
+
If `deepsql login` has been run, both the CLI and the spawned MCP
|
|
124
|
+
server pick up `DEEPSQL_API_BASE_URL` + `DEEPSQL_AUTH_TOKEN` from
|
|
125
|
+
`~/.config/deepsql/auth.json` automatically — you don't need to set
|
|
126
|
+
the env vars for either.
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"_comment": "Manual Claude Desktop config example. Most users should run `deepsql mcp config --install --for claude-desktop` instead — that handles the OS-specific path, backup, and the DBA-consult skill in one shot. Use this template only if you need a custom layout (per-workspace, embedded token, etc.).",
|
|
2
3
|
"mcpServers": {
|
|
3
4
|
"deepsql": {
|
|
4
|
-
"command": "
|
|
5
|
-
"args": [
|
|
6
|
-
"-y",
|
|
7
|
-
"@deepsql/mcp"
|
|
8
|
-
],
|
|
9
|
-
"env": {
|
|
10
|
-
"DEEPSQL_API_BASE_URL": "https://customer-deepsql.example.com/api/",
|
|
11
|
-
"DEEPSQL_AUTH_TOKEN": "dsql_mcp_replace_me",
|
|
12
|
-
"DEEPSQL_MCP_USER_ID": "claude-desktop",
|
|
13
|
-
"DEEPSQL_MCP_PROJECT_ID": "claude-desktop"
|
|
14
|
-
}
|
|
5
|
+
"command": "deepsql",
|
|
6
|
+
"args": ["mcp"]
|
|
15
7
|
}
|
|
16
8
|
}
|
|
17
9
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Manual Codex CLI MCP config example. Most users should run
|
|
2
|
+
# `deepsql mcp config --install --for codex`
|
|
3
|
+
# instead — that handles the file path, backup, and the DBA-consult skill
|
|
4
|
+
# in one shot. Use this template only if you need a custom layout (e.g.
|
|
5
|
+
# embedded token, alternate `DEEPSQL_*` env vars, non-default profile).
|
|
6
|
+
#
|
|
7
|
+
# The form below uses the `deepsql mcp` shim, which reads auth from
|
|
8
|
+
# ~/.config/deepsql/auth.json (written by `deepsql login`) — no token
|
|
9
|
+
# needs to be embedded in the config.
|
|
4
10
|
|
|
5
|
-
[mcp_servers.deepsql
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
DEEPSQL_MCP_USER_ID = "codex-mcp"
|
|
9
|
-
DEEPSQL_MCP_PROJECT_ID = "codex-mcp"
|
|
11
|
+
[mcp_servers.deepsql]
|
|
12
|
+
command = "deepsql"
|
|
13
|
+
args = ["mcp"]
|
package/deepsql-phase1-lib.js
CHANGED
|
@@ -168,8 +168,14 @@ const TOOL_DEFINITIONS = [
|
|
|
168
168
|
},
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
|
-
name: "
|
|
172
|
-
description:
|
|
171
|
+
name: "execute_sql",
|
|
172
|
+
description:
|
|
173
|
+
"Execute a SQL statement through DeepSQL. Routes through the same policy "
|
|
174
|
+
+ "gate as the SQL Editor: developers can run SELECT/WITH/SHOW/EXPLAIN; admins "
|
|
175
|
+
+ "can additionally run DML/DDL with a two-step confirmation. Pass `confirmMutation: "
|
|
176
|
+
+ "true` to confirm a mutation. EXPLAIN and EXPLAIN ANALYZE are valid SQL — just "
|
|
177
|
+
+ "type them as the query, no separate mode flag needed. Multi-statement input "
|
|
178
|
+
+ "and unsafe DELETE/UPDATE without WHERE are still rejected.",
|
|
173
179
|
inputSchema: {
|
|
174
180
|
type: "object",
|
|
175
181
|
properties: {
|
|
@@ -179,19 +185,29 @@ const TOOL_DEFINITIONS = [
|
|
|
179
185
|
},
|
|
180
186
|
query: {
|
|
181
187
|
type: "string",
|
|
182
|
-
description:
|
|
188
|
+
description:
|
|
189
|
+
"SQL to execute. Any single-statement SQL the connection's actor is "
|
|
190
|
+
+ "allowed to run: SELECT/WITH/SHOW/EXPLAIN for any role, plus DML/DDL "
|
|
191
|
+
+ "for admins.",
|
|
183
192
|
},
|
|
184
193
|
limit: {
|
|
185
194
|
type: "integer",
|
|
186
195
|
minimum: 1,
|
|
187
196
|
maximum: 1000,
|
|
188
|
-
description: "
|
|
197
|
+
description: "Row limit for SELECT results. Defaults to 100.",
|
|
189
198
|
},
|
|
190
199
|
timeoutSeconds: {
|
|
191
200
|
type: "integer",
|
|
192
201
|
minimum: 1,
|
|
193
202
|
maximum: 60,
|
|
194
|
-
description: "
|
|
203
|
+
description: "Per-query timeout. Defaults to the backend default.",
|
|
204
|
+
},
|
|
205
|
+
confirmMutation: {
|
|
206
|
+
type: "boolean",
|
|
207
|
+
description:
|
|
208
|
+
"Required `true` on the second call when running DML/DDL — the first "
|
|
209
|
+
+ "call returns `requiresConfirmation: true` with a warnings list; "
|
|
210
|
+
+ "review and re-send with confirmMutation=true to actually execute.",
|
|
195
211
|
},
|
|
196
212
|
},
|
|
197
213
|
required: ["connectionId", "query"],
|
|
@@ -199,8 +215,14 @@ const TOOL_DEFINITIONS = [
|
|
|
199
215
|
},
|
|
200
216
|
},
|
|
201
217
|
{
|
|
202
|
-
name: "
|
|
203
|
-
description:
|
|
218
|
+
name: "analyze_query_plan",
|
|
219
|
+
description:
|
|
220
|
+
"Get DeepSQL's AI-enriched analysis of a query's execution plan. Returns the "
|
|
221
|
+
+ "parsed plan tree, performance issues, index recommendations, and a written "
|
|
222
|
+
+ "summary that takes into account the connection's schema, business rules, "
|
|
223
|
+
+ "and anti-patterns. With `useAnalyze: true` the query is actually executed "
|
|
224
|
+
+ "(EXPLAIN ANALYZE semantics) — mutating statements then go through the same "
|
|
225
|
+
+ "admin/WHERE/confirmation gates as execute_sql.",
|
|
204
226
|
inputSchema: {
|
|
205
227
|
type: "object",
|
|
206
228
|
properties: {
|
|
@@ -210,7 +232,21 @@ const TOOL_DEFINITIONS = [
|
|
|
210
232
|
},
|
|
211
233
|
query: {
|
|
212
234
|
type: "string",
|
|
213
|
-
description:
|
|
235
|
+
description:
|
|
236
|
+
"The underlying SQL to plan. Do NOT wrap in EXPLAIN — the server does "
|
|
237
|
+
+ "that based on `useAnalyze`.",
|
|
238
|
+
},
|
|
239
|
+
useAnalyze: {
|
|
240
|
+
type: "boolean",
|
|
241
|
+
description:
|
|
242
|
+
"If true, run EXPLAIN ANALYZE (actually executes the query for real "
|
|
243
|
+
+ "timings). For mutating statements this requires admin role + confirm.",
|
|
244
|
+
},
|
|
245
|
+
confirmMutation: {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
description:
|
|
248
|
+
"Required `true` to confirm a mutating useAnalyze=true call. Same "
|
|
249
|
+
+ "two-step flow as execute_sql.",
|
|
214
250
|
},
|
|
215
251
|
},
|
|
216
252
|
required: ["connectionId", "query"],
|
|
@@ -354,6 +390,20 @@ function buildHeaders(config, extraHeaders = {}) {
|
|
|
354
390
|
headers.Authorization = `Bearer ${config.authToken}`;
|
|
355
391
|
}
|
|
356
392
|
|
|
393
|
+
// Origin-tracking headers so the backend audit row can distinguish
|
|
394
|
+
// CLI/MCP traffic and identify which editor invoked the MCP server.
|
|
395
|
+
// `clientAgent` carries the value of DEEPSQL_MCP_USER_ID — editor configs
|
|
396
|
+
// set this to "claude-desktop", "cursor-mcp", "codex-mcp", etc.
|
|
397
|
+
if (config.clientType) {
|
|
398
|
+
headers["X-DeepSQL-Client-Type"] = config.clientType;
|
|
399
|
+
}
|
|
400
|
+
if (config.clientAgent) {
|
|
401
|
+
headers["X-DeepSQL-Client-Agent"] = config.clientAgent;
|
|
402
|
+
}
|
|
403
|
+
if (config.clientVersion) {
|
|
404
|
+
headers["X-DeepSQL-Client-Version"] = config.clientVersion;
|
|
405
|
+
}
|
|
406
|
+
|
|
357
407
|
return headers;
|
|
358
408
|
}
|
|
359
409
|
|
|
@@ -502,11 +552,56 @@ function summarizeAntiPatterns(payload, kind) {
|
|
|
502
552
|
}
|
|
503
553
|
|
|
504
554
|
function summarizeSlowQueries(payload) {
|
|
505
|
-
|
|
555
|
+
// Backend returns SlowQueryAnalysis with `topSlowQueries` (the field name
|
|
556
|
+
// varies; tolerate both `queries` and `topSlowQueries`).
|
|
557
|
+
const list = Array.isArray(payload?.topSlowQueries)
|
|
558
|
+
? payload.topSlowQueries
|
|
559
|
+
: Array.isArray(payload?.queries) ? payload.queries : [];
|
|
506
560
|
const total = payload?.totalCount ?? list.length;
|
|
507
561
|
const avg = payload?.avgDurationMs;
|
|
508
562
|
const max = payload?.maxDurationMs;
|
|
509
|
-
|
|
563
|
+
|
|
564
|
+
// Three counts matter to a calling agent that's about to EXPLAIN one of
|
|
565
|
+
// these queries:
|
|
566
|
+
//
|
|
567
|
+
// recovered = sourceTruncated AND queryTextRecoveredFromLogs
|
|
568
|
+
// → the live stats source truncated this query, but DeepSQL recovered
|
|
569
|
+
// the full SQL from previously-ingested slow-log data in
|
|
570
|
+
// query_lineage. EXPLAIN will work.
|
|
571
|
+
//
|
|
572
|
+
// stillTruncated = sourceTruncated AND NOT queryTextRecoveredFromLogs
|
|
573
|
+
// → still truncated; EXPLAIN will fail or return a partial plan.
|
|
574
|
+
//
|
|
575
|
+
// neither → normal full-text query, no warning needed.
|
|
576
|
+
const recovered = list.filter((q) =>
|
|
577
|
+
q && q.sourceTruncated === true && q.queryTextRecoveredFromLogs === true
|
|
578
|
+
).length;
|
|
579
|
+
const stillTruncated = list.filter((q) =>
|
|
580
|
+
q && q.sourceTruncated === true && q.queryTextRecoveredFromLogs !== true
|
|
581
|
+
).length;
|
|
582
|
+
|
|
583
|
+
const parts = [];
|
|
584
|
+
if (recovered > 0) {
|
|
585
|
+
parts.push(
|
|
586
|
+
` ℹ ${recovered} ${recovered === 1 ? "query was" : "queries were"} truncated by `
|
|
587
|
+
+ `the database server (default 1024B) but DeepSQL recovered the full SQL `
|
|
588
|
+
+ `from previously-ingested slow-log data — EXPLAIN against \`queryText\` will work.`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (stillTruncated > 0) {
|
|
592
|
+
parts.push(
|
|
593
|
+
` ⚠ ${stillTruncated} ${stillTruncated === 1 ? "query is" : "queries are"} still `
|
|
594
|
+
+ `truncated and DeepSQL has no full-text copy on file. EXPLAIN will be unreliable. `
|
|
595
|
+
+ `Fix: ingest the slow query log file for this connection, OR raise `
|
|
596
|
+
+ `\`pg_stat_statements.track_activity_query_size\` (PG) / `
|
|
597
|
+
+ `\`performance_schema_max_sql_text_length\` (MySQL) and restart, then re-collect.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return `${total} slow query/queries`
|
|
602
|
+
+ `${avg != null ? `, avg=${avg}ms` : ""}`
|
|
603
|
+
+ `${max != null ? `, max=${max}ms` : ""}.`
|
|
604
|
+
+ parts.join("");
|
|
510
605
|
}
|
|
511
606
|
|
|
512
607
|
function summarizeQueryResult(payload) {
|
|
@@ -554,10 +649,10 @@ function buildToolResult(name, payload, extra = {}) {
|
|
|
554
649
|
case "analyze_slow_queries":
|
|
555
650
|
summary = summarizeSlowQueries(payload);
|
|
556
651
|
break;
|
|
557
|
-
case "
|
|
652
|
+
case "execute_sql":
|
|
558
653
|
summary = summarizeQueryResult(payload);
|
|
559
654
|
break;
|
|
560
|
-
case "
|
|
655
|
+
case "analyze_query_plan":
|
|
561
656
|
summary = summarizeExplain(payload);
|
|
562
657
|
break;
|
|
563
658
|
default:
|
|
@@ -698,42 +793,53 @@ async function handleToolCall(config, name, args = {}) {
|
|
|
698
793
|
return buildToolResult(name, payload);
|
|
699
794
|
}
|
|
700
795
|
|
|
701
|
-
case "
|
|
796
|
+
case "execute_sql": {
|
|
702
797
|
const connectionId = String(args.connectionId || "").trim();
|
|
703
|
-
const
|
|
704
|
-
if (!
|
|
705
|
-
|
|
706
|
-
}
|
|
798
|
+
const query = String(args.query || "").trim();
|
|
799
|
+
if (!connectionId) return buildToolError("connectionId is required.");
|
|
800
|
+
if (!query) return buildToolError("query is required.");
|
|
707
801
|
|
|
802
|
+
// Talk to the canonical Editor endpoint. Backend enforces role-based
|
|
803
|
+
// mutation policy + per-connection ACL + chat data-access policy +
|
|
804
|
+
// WHERE-clause guard + two-step confirmation, then audits the call.
|
|
805
|
+
// Client-side parser validation removed in 0.13.0 — the backend is
|
|
806
|
+
// the source of truth and was always going to be.
|
|
708
807
|
const payload = await callDeepSqlApi(
|
|
709
808
|
config,
|
|
710
|
-
|
|
809
|
+
`/connections/${encodeURIComponent(connectionId)}/query`,
|
|
711
810
|
{
|
|
712
811
|
method: "POST",
|
|
713
812
|
json: {
|
|
714
|
-
|
|
715
|
-
query: validation.normalizedQuery,
|
|
813
|
+
query,
|
|
716
814
|
limit: clampInteger(args.limit, 1, 1000, 100),
|
|
717
815
|
timeoutSeconds: clampInteger(args.timeoutSeconds, 1, 60, null),
|
|
816
|
+
mutationConfirmed: args.confirmMutation === true,
|
|
718
817
|
},
|
|
719
818
|
},
|
|
720
819
|
);
|
|
721
820
|
|
|
821
|
+
// Surface a "requiresConfirmation" response as a non-error structured
|
|
822
|
+
// payload so the calling agent can read warnings and re-send with
|
|
823
|
+
// confirmMutation=true without parsing tool-error text.
|
|
722
824
|
return buildToolResult(name, payload);
|
|
723
825
|
}
|
|
724
826
|
|
|
725
|
-
case "
|
|
827
|
+
case "analyze_query_plan": {
|
|
726
828
|
const connectionId = String(args.connectionId || "").trim();
|
|
727
|
-
const
|
|
728
|
-
if (!
|
|
729
|
-
|
|
730
|
-
}
|
|
829
|
+
const query = String(args.query || "").trim();
|
|
830
|
+
if (!connectionId) return buildToolError("connectionId is required.");
|
|
831
|
+
if (!query) return buildToolError("query is required.");
|
|
731
832
|
|
|
732
|
-
|
|
833
|
+
// Route through the canonical Editor endpoint (ExplainController).
|
|
834
|
+
// For useAnalyze=true the backend applies the same mutation policy
|
|
835
|
+
// as execute_sql before wrapping in EXPLAIN ANALYZE.
|
|
836
|
+
const payload = await callDeepSqlApi(config, "/explain/analyze", {
|
|
733
837
|
method: "POST",
|
|
734
838
|
json: {
|
|
735
839
|
connectionId,
|
|
736
|
-
query
|
|
840
|
+
query,
|
|
841
|
+
useAnalyze: args.useAnalyze === true,
|
|
842
|
+
mutationConfirmed: args.confirmMutation === true,
|
|
737
843
|
},
|
|
738
844
|
});
|
|
739
845
|
|
|
@@ -749,12 +855,28 @@ function createConfigFromEnv(env = process.env) {
|
|
|
749
855
|
const rawBaseUrl = env.DEEPSQL_API_BASE_URL || "http://localhost:8080/api/";
|
|
750
856
|
const baseUrl = rawBaseUrl.endsWith("/") ? rawBaseUrl : `${rawBaseUrl}/`;
|
|
751
857
|
|
|
858
|
+
// Resolve our npm version once, lazily — `require("./package.json")`
|
|
859
|
+
// would normally pull it in, but we use a try/catch so the lib still
|
|
860
|
+
// works in test contexts where the package metadata isn't on disk.
|
|
861
|
+
let clientVersion = null;
|
|
862
|
+
try {
|
|
863
|
+
clientVersion = require("./package.json").version;
|
|
864
|
+
} catch {
|
|
865
|
+
// best-effort
|
|
866
|
+
}
|
|
867
|
+
|
|
752
868
|
return {
|
|
753
869
|
baseUrl,
|
|
754
870
|
authToken: env.DEEPSQL_AUTH_TOKEN || "",
|
|
755
871
|
timeoutMs: clampInteger(env.DEEPSQL_MCP_TIMEOUT_MS, 1000, 600000, 120000),
|
|
756
872
|
defaultUserId: env.DEEPSQL_MCP_USER_ID || "mcp-phase1",
|
|
757
873
|
defaultProjectId: env.DEEPSQL_MCP_PROJECT_ID || "mcp-phase1",
|
|
874
|
+
// Origin metadata for the backend audit row. The MCP server always
|
|
875
|
+
// identifies as `mcp`; the agent name comes from DEEPSQL_MCP_USER_ID
|
|
876
|
+
// which editor configs set to claude-desktop / cursor-mcp / codex-mcp.
|
|
877
|
+
clientType: "mcp",
|
|
878
|
+
clientAgent: env.DEEPSQL_MCP_USER_ID || null,
|
|
879
|
+
clientVersion,
|
|
758
880
|
};
|
|
759
881
|
}
|
|
760
882
|
|
package/deepsql-phase1-server.js
CHANGED
|
@@ -193,7 +193,7 @@ class DeepSqlPhase1McpServer {
|
|
|
193
193
|
},
|
|
194
194
|
serverInfo: SERVER_INFO,
|
|
195
195
|
instructions:
|
|
196
|
-
"DeepSQL
|
|
196
|
+
"DeepSQL MCP exposes the user's database catalogs plus DeepSQL's retrieval brain (relevant tables/columns/FKs, business rules, inferred relationships, anti-patterns, slow-query analysis). Workflow: call list_connections first to get UUIDs; call get_brain_context with the user's question to ground generation in retrieved schema; call execute_sql to run the query (admins can run DDL/DML with a two-step confirmMutation flow, developers are server-enforced read-only); call analyze_query_plan for AI-enriched plan analysis with the connection's schema + business rules in scope. EXPLAIN and EXPLAIN ANALYZE are valid SQL — pass them as the query to execute_sql; use analyze_query_plan when you want the LLM-written summary, not raw plan rows. Always pass connectionId (UUID), not connection names.",
|
|
197
197
|
});
|
|
198
198
|
return;
|
|
199
199
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepsql/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "DeepSQL CLI and stdio MCP server for self-hosted deployments",
|
|
5
5
|
"bin": {
|
|
6
6
|
"deepsql": "bin/deepsql.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"CLAUDE.md",
|
|
13
13
|
"AGENT-SETUP.md",
|
|
14
14
|
"bin",
|
|
15
|
+
"skills",
|
|
15
16
|
"src",
|
|
16
17
|
"deepsql-phase1-server.js",
|
|
17
18
|
"deepsql-phase1-lib.js",
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
"codex_config.customer.example.toml"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
|
-
"test": "node --test deepsql-phase1-lib.test.js src/**/*.test.js"
|
|
23
|
+
"test": "node --test deepsql-phase1-lib.test.js src/*.test.js src/**/*.test.js"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=20"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# DeepSQL — your database DBA consult
|
|
2
|
+
|
|
3
|
+
You have DeepSQL's MCP tools loaded. **DeepSQL is the source of truth for
|
|
4
|
+
the live schema, business rules, FK relationships, and anti-patterns of
|
|
5
|
+
the database the user is working against.** Treat it the way a thoughtful
|
|
6
|
+
engineer treats a DBA: consult before you commit anything schema-shaped.
|
|
7
|
+
|
|
8
|
+
This skill triggers any time the user is doing database work. The rules
|
|
9
|
+
below are non-negotiable.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Trigger checklist — before generating any DDL, migration, or non-trivial SQL
|
|
14
|
+
|
|
15
|
+
The user said something like "add a table", "track this", "write a
|
|
16
|
+
migration", "design a model", "query the database", or "write the SELECT
|
|
17
|
+
for…". Before you generate **any** SQL or schema-shaped output, run:
|
|
18
|
+
|
|
19
|
+
1. `list_connections` — get the UUID of the connection the user means
|
|
20
|
+
(don't pass connection names anywhere; tools take UUIDs).
|
|
21
|
+
|
|
22
|
+
2. `get_brain_context(connectionId, "<one-line description of the feature/question>")`
|
|
23
|
+
— surfaces the tables, columns, FKs, training docs, and business rules
|
|
24
|
+
most relevant to the work at hand. **Read the results, don't just
|
|
25
|
+
regurgitate them.**
|
|
26
|
+
|
|
27
|
+
3. `get_schema(connectionId)` if you need a full column inventory for any
|
|
28
|
+
table `get_brain_context` surfaced. Don't infer column types from
|
|
29
|
+
variable names in the codebase — they drift.
|
|
30
|
+
|
|
31
|
+
4. `list_business_rules(connectionId, question="<feature>")` — rules the
|
|
32
|
+
feature MUST respect. If `always_filter_cancelled` is on, your
|
|
33
|
+
aggregate views inherit that filter from day one. Apply these silently;
|
|
34
|
+
don't ask the user permission to follow their own rules.
|
|
35
|
+
|
|
36
|
+
5. `get_relationships(connectionId)` if you're declaring a foreign key —
|
|
37
|
+
the brain may already infer it with a confidence score, and the FK
|
|
38
|
+
naming convention this team uses lives here.
|
|
39
|
+
|
|
40
|
+
6. `get_anti_patterns(connectionId, kind="table")` if you're committing a
|
|
41
|
+
schema shape — the brain has flagged patterns to avoid in this
|
|
42
|
+
specific database.
|
|
43
|
+
|
|
44
|
+
## Then narrate what you found before proposing schema
|
|
45
|
+
|
|
46
|
+
Don't jump straight to `CREATE TABLE …`. Tell the user what DeepSQL said:
|
|
47
|
+
|
|
48
|
+
> "DeepSQL says you already have a `customers` table with `email`,
|
|
49
|
+
> `tenant_id`, `created_at`, plus an inferred FK to
|
|
50
|
+
> `accounts.customer_id` at 0.94 confidence. The business rule
|
|
51
|
+
> `always_filter_cancelled` is on `customers.status`. The anti-pattern
|
|
52
|
+
> report flagged 'wide-table' on `customer_profiles` — adding more
|
|
53
|
+
> columns there is discouraged.
|
|
54
|
+
>
|
|
55
|
+
> **I'd extend `customers` with the two new fields you need rather
|
|
56
|
+
> than add a `users` table. Want me to draft the migration?**"
|
|
57
|
+
|
|
58
|
+
That narration is the difference between an agent that ships features fast
|
|
59
|
+
and an agent that earns the team's trust. **Make it a reflex.**
|
|
60
|
+
|
|
61
|
+
If the consult tells you to stop — there's already a table that does what
|
|
62
|
+
the user asked for, or the shape they want is on the anti-pattern list —
|
|
63
|
+
**say so**. Push back politely and propose the better path. The user
|
|
64
|
+
usually doesn't know about either; that's exactly why DeepSQL exists.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Running SQL
|
|
69
|
+
|
|
70
|
+
| You want to… | Use this tool |
|
|
71
|
+
|---|---|
|
|
72
|
+
| Run any SQL (SELECT / EXPLAIN / DML / DDL) | `execute_sql(connectionId, query, ...)` |
|
|
73
|
+
| Get AI-enriched plan analysis for a query | `analyze_query_plan(connectionId, query, useAnalyze=false)` |
|
|
74
|
+
| Actually execute the query AND get the plan (real timings) | `analyze_query_plan(..., useAnalyze=true)` |
|
|
75
|
+
|
|
76
|
+
`EXPLAIN` and `EXPLAIN ANALYZE` are just SQL — type them as the query if
|
|
77
|
+
you want raw plan output. Use `analyze_query_plan` when you want the
|
|
78
|
+
AI-enriched analysis (issues, index recommendations, written summary).
|
|
79
|
+
|
|
80
|
+
### Mutations are role-gated and two-step
|
|
81
|
+
|
|
82
|
+
`execute_sql` enforces the same policy as the SQL Editor:
|
|
83
|
+
|
|
84
|
+
- **Developer + SELECT/WITH/SHOW/EXPLAIN** → runs immediately.
|
|
85
|
+
- **Developer + DML/DDL** → 403 `EDITOR_MUTATION_FORBIDDEN`. Don't retry.
|
|
86
|
+
Tell the user: "Your DeepSQL role doesn't allow DML/DDL on this
|
|
87
|
+
connection; ask the workspace admin to grant write access or to run the
|
|
88
|
+
change."
|
|
89
|
+
- **Admin + DML/DDL (no `confirmMutation`)** → returns
|
|
90
|
+
`requiresConfirmation: true` with a `warnings` array. **Show the
|
|
91
|
+
warnings to the user verbatim. Wait for explicit OK.** Then re-call
|
|
92
|
+
with `confirmMutation: true`. **Do not silently retry with
|
|
93
|
+
`confirmMutation: true` on the user's behalf** — that defeats the
|
|
94
|
+
confirmation step.
|
|
95
|
+
|
|
96
|
+
### Row limits
|
|
97
|
+
|
|
98
|
+
`execute_sql` defaults to 100 rows, max 1000. If you asked for "all
|
|
99
|
+
customers" and got 100, that's the limit kicking in — not the real count.
|
|
100
|
+
Either bump `limit` or `SELECT COUNT(*)` first.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Every call is audited
|
|
105
|
+
|
|
106
|
+
Every tool call you make is logged to the DeepSQL `security_events` table
|
|
107
|
+
with the user's identity, the editor that invoked the MCP server
|
|
108
|
+
(claude-desktop, cursor-mcp, codex-mcp), the connection, the truncated
|
|
109
|
+
statement, and the outcome. Workspace admins can search this. Don't do
|
|
110
|
+
anything through these tools you wouldn't be willing to defend in that
|
|
111
|
+
view.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Full reference
|
|
116
|
+
|
|
117
|
+
The complete runtime guide — every decision-tree branch, every foot-gun,
|
|
118
|
+
all three session playbooks (answer-a-question, mutation, DBA-consult) —
|
|
119
|
+
lives in `node_modules/@deepsql/mcp/CLAUDE.md`. Read it the first time
|
|
120
|
+
you handle a non-trivial database request.
|
|
121
|
+
|
|
122
|
+
Capabilities that aren't MCP-exposed yet (index recommendations, daily
|
|
123
|
+
digest, slow-query streaming optimization) live in the CLI: `deepsql
|
|
124
|
+
indexes`, `deepsql digest`, `deepsql slow-queries optimize`. Point the
|
|
125
|
+
user at them.
|