@deepsql/mcp 0.21.0 → 0.22.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepsql/mcp",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "DeepSQL CLI, DBA Agent TUI, and stdio MCP server for self-hosted deployments",
5
5
  "bin": {
6
6
  "deepsql": "bin/deepsql.js",
@@ -1,57 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- // Postinstall: pre-install the DeepSQL Agent (Hermes) runtime at `npm install -g`
5
- // time so the first `deepsql` launch is instant instead of triggering a heavy
6
- // one-time download. Best-effort and NON-FATAL — it never fails the npm install.
4
+ // Postinstall: intentionally a no-op heavy-wise.
7
5
  //
8
- // Opt out (e.g. pure-MCP users who only want the editor tools, not the agent):
9
- // DEEPSQL_SKIP_AGENT_SETUP=1 npm install -g @deepsql/mcp
6
+ // The DeepSQL Agent is now a THIN client `deepsql` / `deepsql agent` talk to
7
+ // the server-side agent over the backend's brokered /agent/chat endpoint, so
8
+ // there is NO local agent runtime to download. `npm install -g @deepsql/mcp` is
9
+ // fast and quiet; nothing to pre-build.
10
10
  //
11
- // It auto-skips when there's nothing/no-one to set up for:
12
- // - CI environments (process.env.CI)
13
- // - non-global installs (local dev `npm install`, dependency installs)
14
- // The profile itself is provisioned later, on first `deepsql` after login,
15
- // because it needs the user's token.
16
-
17
- function log(msg) {
18
- process.stdout.write(`${msg}\n`);
19
- }
20
-
21
- function skip(reason) {
22
- // Silent for the common "not global / CI" cases; only note an explicit opt-out.
23
- if (reason) log(`DeepSQL Agent: ${reason}`);
24
- process.exit(0);
25
- }
11
+ // We only print a short next-steps hint on a global install (and stay silent for
12
+ // CI / local dependency installs so we never clutter other projects' logs).
26
13
 
27
14
  try {
28
- if (process.env.DEEPSQL_SKIP_AGENT_SETUP) {
29
- skip("agent runtime setup skipped (DEEPSQL_SKIP_AGENT_SETUP set).");
30
- }
31
- if (process.env.CI) skip();
32
- // Only pre-install on an explicit global install (or a forced run). Local /
33
- // dependency installs shouldn't pull a Python+Node runtime.
15
+ if (process.env.CI) process.exit(0);
34
16
  const isGlobal = String(process.env.npm_config_global || "") === "true";
35
- if (!isGlobal && !process.env.DEEPSQL_FORCE_AGENT_SETUP) skip();
36
-
37
- const { ensureHermes, hermesInstalled } = require("../src/agent/bootstrap");
38
-
39
- if (hermesInstalled()) {
40
- log("✓ DeepSQL Agent runtime already present.");
41
- process.exit(0);
42
- }
17
+ if (!isGlobal) process.exit(0);
43
18
 
44
- log("DeepSQL Agent: pre-installing the agent runtime so `deepsql` starts instantly.");
45
- log(" (Opt out next time with DEEPSQL_SKIP_AGENT_SETUP=1.)");
46
- const ok = ensureHermes({ stderr: process.stdout });
47
- if (ok) {
48
- log("✓ DeepSQL Agent runtime ready. Run `deepsql login`, then `deepsql`.");
49
- } else {
50
- // Non-fatal: the lazy installer will retry on first `deepsql agent`.
51
- log("DeepSQL Agent: runtime not pre-installed; it'll be set up on first `deepsql` run.");
52
- }
53
- } catch (e) {
54
- // Never break the npm install over agent pre-setup.
55
- log(`DeepSQL Agent: skipped runtime pre-install (${e && e.message ? e.message : e}).`);
19
+ process.stdout.write(
20
+ "\nDeepSQL installed. Next:\n" +
21
+ " deepsql login --url <your-deepsql-url>\n" +
22
+ " deepsql # chat with the DeepSQL Agent (connects to your server)\n\n"
23
+ );
24
+ } catch {
25
+ // Never let a cosmetic hint fail the install.
56
26
  }
57
27
  process.exit(0);
@@ -113,7 +113,9 @@ function ensureHermes(io, { quiet = false } = {}) {
113
113
  });
114
114
 
115
115
  if (hermesInstalled()) {
116
- err.write("✓ DeepSQL Agent runtime installed. (Ignore any \"run hermes setup\" note above — DeepSQL configures it for you.)\n");
116
+ err.write(quiet
117
+ ? "✓ DeepSQL Agent runtime installed.\n"
118
+ : "✓ DeepSQL Agent runtime installed. (Ignore any \"run hermes setup\" note above — DeepSQL configures it for you.)\n");
117
119
  return true;
118
120
  }
119
121
  err.write(
package/src/cli.js CHANGED
@@ -90,13 +90,13 @@ const GLOBAL_OPTIONS = [
90
90
 
91
91
  const COMMAND_HELP = {
92
92
  agent: {
93
- description: "Launch the DeepSQL Agent — an interactive chat TUI for DBA work, BI questions, and database guardrails. Running `deepsql` with no command in a terminal launches it too.",
94
- usage: "deepsql agent [options] (or just: deepsql)",
93
+ description: "Chat with the DeepSQL Agent — DBA work, BI questions, and database guardrails. A thin client: it connects to your server's agent (the same one behind the web Agent tab and Slack), nothing installs locally. Bare `deepsql` in a terminal opens interactive chat; pass a question for a one-shot.",
94
+ usage: 'deepsql agent ["<question>"] [options] (or just: deepsql)',
95
95
  options: [
96
96
  ["--url <url>", "DeepSQL instance to use (default: saved login)"],
97
- ["--connection <name>", "Default connection for the session"],
97
+ ["--connection <name>", "Connection to ground answers on (default: saved active connection)"],
98
98
  ],
99
- notes: "First run installs the agent runtime. Uses your saved `deepsql login` no LLM key needed (the backend proxies the model).",
99
+ notes: "No local runtime or LLM key needed — the server runs the agent and proxies the model. Interactive chat resumes your most recent conversation (shared with the web Agent tab).",
100
100
  },
101
101
 
102
102
  login: {
@@ -1,15 +1,44 @@
1
1
  "use strict";
2
2
 
3
- // `deepsql agent` (and bare `deepsql` in a terminal): launch the DeepSQL Agent
4
- // a branded Hermes TUI for DBA / BI / database-guard chat. Resolves the saved
5
- // login, lazily installs the runtime, provisions the `deepsql` profile pointed
6
- // at the backend LLM proxy (no LLM key needed), then hands the terminal to the TUI.
3
+ // `deepsql agent` (and bare `deepsql` in a terminal): the DeepSQL Agent, as a
4
+ // THIN client. It does not run an agent runtime locally it talks to the
5
+ // server-side agent through the backend's brokered, authenticated
6
+ // `/agent/chat` endpoint (the same agent that powers the web Agent tab and
7
+ // Slack). Identity, profile, connection scope, and conversation history are all
8
+ // resolved server-side from your saved `deepsql login` — nothing heavy installs,
9
+ // and your terminal shares the same conversations as the web app.
10
+ //
11
+ // deepsql agent "how many active hotels?" one-shot
12
+ // deepsql interactive chat (resumes latest)
7
13
 
8
- const { spawn } = require("node:child_process");
14
+ const readline = require("node:readline");
15
+ const { request } = require("../api/client");
9
16
  const { resolveSession } = require("./_session");
10
- const { ensureHermes, ensureProfile, hermesBin, launchEnv, PROFILE } = require("../agent/bootstrap");
17
+ const { resolveConnectionId } = require("./_connections");
18
+
19
+ // The brokered turn can run a full multi-tool agent loop server-side; allow well
20
+ // past the backend's own per-turn ceiling so we don't abandon a valid answer.
21
+ const TURN_TIMEOUT_MS = 320000;
22
+
23
+ async function runTurn(session, connectionId, message, conversationId) {
24
+ return request(session.baseUrl, "/agent/chat", {
25
+ method: "POST",
26
+ token: session.token,
27
+ json: { message, connectionId, conversationId },
28
+ timeoutMs: TURN_TIMEOUT_MS,
29
+ });
30
+ }
31
+
32
+ function renderReply(stdout, resp) {
33
+ if (resp && resp.ok && resp.answer && String(resp.answer).trim()) {
34
+ stdout.write(`\n${String(resp.answer).trim()}\n`);
35
+ } else {
36
+ stdout.write(`\n⚠ ${(resp && resp.error) || "the agent run ended early"}\n`);
37
+ }
38
+ }
11
39
 
12
40
  async function run(opts, io = {}) {
41
+ const stdout = io.stdout || process.stdout;
13
42
  const stderr = io.stderr || process.stderr;
14
43
 
15
44
  let session;
@@ -20,26 +49,69 @@ async function run(opts, io = {}) {
20
49
  return 1;
21
50
  }
22
51
 
23
- if (!ensureHermes(io)) {
24
- stderr.write("Could not set up the DeepSQL Agent runtime. See https://github.com/NousResearch/hermes-agent for manual install.\n");
25
- return 1;
52
+ // Connection is optional — the agent can chat without one and will ask if a
53
+ // question needs a database. Resolve the active default when present.
54
+ let connectionId = null;
55
+ try {
56
+ connectionId = await resolveConnectionId(session, opts.connection);
57
+ } catch {
58
+ /* no default connection set — fine */
26
59
  }
27
- if (!ensureProfile(session, io)) {
28
- stderr.write("Could not provision the DeepSQL Agent profile.\n");
29
- return 1;
60
+
61
+ const message = (opts.positional || []).join(" ").trim();
62
+
63
+ // One-shot: a question on the command line.
64
+ if (message) {
65
+ try {
66
+ const resp = await runTurn(session, connectionId, message, null);
67
+ renderReply(stdout, resp);
68
+ return resp && resp.ok ? 0 : 1;
69
+ } catch (e) {
70
+ stderr.write(`Agent request failed: ${e.message}\n`);
71
+ return 1;
72
+ }
30
73
  }
31
- // Branding is delivered via HERMES_TUI_DIR in launchEnv (Hermes runs our
32
- // prebuilt branded TUI directly — no esbuild), set just below.
33
74
 
34
- // Hand the terminal to the branded TUI (profile config sets interface=tui).
35
- // launchEnv augments PATH (node/uv for the TUI + MCP server) and injects the
36
- // provider vars that keep Hermes' first-run guard from ever firing.
37
- const child = spawn(hermesBin(), ["-p", PROFILE, "--tui"], { stdio: "inherit", env: launchEnv(session) });
75
+ // Interactive: a line REPL against the server agent. Requires a TTY.
76
+ if (!stdout.isTTY) {
77
+ stderr.write('Usage: deepsql agent "<question>" (or run in a terminal for interactive chat)\n');
78
+ return 2;
79
+ }
80
+
81
+ stdout.write("DeepSQL Agent — connected to your server agent. Type a question; `exit` or Ctrl-C to quit.\n");
82
+ // conversationId stays null on the first turn so the server resumes your most
83
+ // recent conversation for this connection (shared with the web Agent tab);
84
+ // subsequent turns reuse the id it returns.
85
+ let conversationId = null;
86
+ const rl = readline.createInterface({ input: process.stdin, output: stdout, prompt: "\nyou › " });
87
+ rl.prompt();
88
+
38
89
  return await new Promise((resolve) => {
39
- child.on("exit", (code) => resolve(code ?? 0));
40
- child.on("error", (err) => {
41
- stderr.write(`Failed to launch the DeepSQL Agent: ${err.message}\n`);
42
- resolve(1);
90
+ rl.on("line", async (line) => {
91
+ const text = line.trim();
92
+ if (!text) {
93
+ rl.prompt();
94
+ return;
95
+ }
96
+ if (text === "exit" || text === "quit") {
97
+ rl.close();
98
+ return;
99
+ }
100
+ rl.pause();
101
+ stdout.write("…thinking\n");
102
+ try {
103
+ const resp = await runTurn(session, connectionId, text, conversationId);
104
+ if (resp && resp.conversationId) conversationId = resp.conversationId;
105
+ renderReply(stdout, resp);
106
+ } catch (e) {
107
+ stdout.write(`\n⚠ ${e.message}\n`);
108
+ }
109
+ rl.resume();
110
+ rl.prompt();
111
+ });
112
+ rl.on("close", () => {
113
+ stdout.write("\nBye.\n");
114
+ resolve(0);
43
115
  });
44
116
  });
45
117
  }