sql-code-graph 1.4.3__py3-none-any.whl → 1.5.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-code-graph
3
- Version: 1.4.3
3
+ Version: 1.5.0
4
4
  Summary: SQL code graph analyzer and lineage tracer
5
5
  Project-URL: Homepage, https://github.com/Warhorze/sql-code-graph
6
6
  Project-URL: Repository, https://github.com/Warhorze/sql-code-graph
@@ -1,7 +1,7 @@
1
- sqlcg/__init__.py,sha256=-pjjoaMnukW9b0-s_OOYNjy46alw_PTB6xzvfSyWjew,115
1
+ sqlcg/__init__.py,sha256=umkxJTfZmp0-L1q_ge27nl61dPsJtPJKnGORGDpbJKY,115
2
2
  sqlcg/__main__.py,sha256=1YoFLcqEgTwYq1J3TbUwpkdG0zeeLIf2fJvwWI-CLFU,109
3
3
  sqlcg/cli/__init__.py,sha256=W8fD0LpMq2xm_5WKGNMvJh2WBL1ho5E8hUeAqXQYT1g,28
4
- sqlcg/cli/main.py,sha256=WmdTjsOlz1ozi2Y3Aq4ezR_FCRl-Lc1YOKw3_d48dlY,1650
4
+ sqlcg/cli/main.py,sha256=oiZ0e_blUCnlmmXNSCLYnifleDv6ixm9dF7GEDGe0dQ,2429
5
5
  sqlcg/cli/commands/__init__.py,sha256=oSHtr6VD-jNubOjuCQyZj2tBppjMEpQDh-IGQ8of9eA,30
6
6
  sqlcg/cli/commands/analyze.py,sha256=_aC5ML3w7YdLi7DL3TFS9OiCEIipuNZxWR6S4peTcn4,12154
7
7
  sqlcg/cli/commands/db.py,sha256=TMhPCHRWSo8YmlNnSxmGxrR0_1r3K4e0Bn8unZTkvU4,7248
@@ -9,8 +9,8 @@ sqlcg/cli/commands/find.py,sha256=p5Vyyx-VBk8YDWYQN16UhECh7PIeMaEyCPEcUFcRFlM,25
9
9
  sqlcg/cli/commands/gain.py,sha256=SJU1c51a7MgNbZItqQnaBfOWGnV5xpXQctbew5Dr9BE,9062
10
10
  sqlcg/cli/commands/git.py,sha256=9a8T2FVxcAHq1H6Cslaq34t10w9fBGf4T2reiLk33t8,6135
11
11
  sqlcg/cli/commands/index.py,sha256=RPEpT9aXmK0rixRuHgMcmAn9FLR4JtIbZ1LM4RLkMvY,17050
12
- sqlcg/cli/commands/install.py,sha256=KNABvrLbamPyYnmnVdCaM_MNezbDc-pr6IkignCWI8k,9186
13
- sqlcg/cli/commands/mcp.py,sha256=QYaupf69lLpYzIoqsPJoCPiAggLVkYBzwpuOLRzxJDU,9140
12
+ sqlcg/cli/commands/install.py,sha256=VdfZnC41WE3Lx6CcGSd2Xyi-XNNOLP1YQrnXmqo7x3c,10023
13
+ sqlcg/cli/commands/mcp.py,sha256=CA24kRfBltanKuSMkRVRf42GW8cTgG8YQrMruxB3vSQ,11592
14
14
  sqlcg/cli/commands/reindex.py,sha256=FqXxvQ0UrxwDS0q2V3gzYgarN3NPh9muaIZ3rgvTmHs,13810
15
15
  sqlcg/cli/commands/report.py,sha256=JU0qjyMxwOukE7bN3XvvIzOI7zMg_Gsnvk_8F6pKNpA,4915
16
16
  sqlcg/cli/commands/uninstall.py,sha256=WrA1FnINxnd6mmE4-_QBK0aHBnibstJeAT8swnQKG4M,8962
@@ -50,18 +50,18 @@ sqlcg/parsers/tsql_parser.py,sha256=RRj1pACtAk2tLTDaFWRYF67a0IDvaf5A1YQXWIz0bpQ,
50
50
  sqlcg/server/__init__.py,sha256=n4wuNE7xyJIJxJZBtmtdccCMQfvTdF-IqIaZVbC4FC4,35
51
51
  sqlcg/server/control.py,sha256=qUcztb_aDhL-_X_Nq4q6uGx17cUlbLnI6vUpoZsEjbo,4506
52
52
  sqlcg/server/exceptions.py,sha256=EONw34icOByCTpppSQrvQBW6asc4hfqaGDCAFjv96II,469
53
- sqlcg/server/models.py,sha256=plnBVoV6y6JuU2IHzez0yHfsd5QZ_o977UCacS7A5NA,19693
53
+ sqlcg/server/models.py,sha256=4c4ula195cHEqZycO6ZaSNohSLsodB-Du1FP94JKSdc,19810
54
54
  sqlcg/server/noise_filter.py,sha256=Ats2FFzmzFKqvQPWvlUzK8mY9pzlUhL4m1s8P_HNnvI,6335
55
55
  sqlcg/server/read_client.py,sha256=4v1OOl12HCHp6J14HrcWQApSixvJ0wwE8UIIZ0pzLyw,7808
56
- sqlcg/server/server.py,sha256=A1soT-hYnAX78X5j97nIjw9oIZCxBejjV6p_tRDIK38,24335
56
+ sqlcg/server/server.py,sha256=1RiUmjWLgtN636kRQO6-4rH8amC-m9BKsIMO0aDRqes,24838
57
57
  sqlcg/server/skill.py,sha256=EDvmEgl-LQwCxZ9Lca4lt8zCtkk2cCgh9GLYlGgDA64,12828
58
- sqlcg/server/tools.py,sha256=oelzyVydELcD0ePYQMiIdZPqm-GolxD4YxtKUtGSeTU,76744
58
+ sqlcg/server/tools.py,sha256=SyxPbnFmsR1w2Hi2ENmD_mynCdI9JHhTgv1byXFIKoY,77191
59
59
  sqlcg/server/writer.py,sha256=HH8pW6k1c2pbY4b6g2N87Tx4gNCezYqTLNTlAmsHg2Y,18522
60
60
  sqlcg/utils/__init__.py,sha256=--iqt5ThTXmT8Wz7da8hs3n0zDfYPl8P-z5OgRJ_77E,154
61
61
  sqlcg/utils/hashing.py,sha256=H25-sYfxHKb3_IERFnHyAIYNiXN470Oqo5sJT_D3YOA,438
62
62
  sqlcg/utils/ignore.py,sha256=wJjwa0mjnQ_xJExOUxk25y00g065XmmzJapqV3ifD5o,1151
63
63
  sqlcg/utils/logging.py,sha256=u0fCmYsLj9o81vawm3xZTHaw68GQYVm7JxG-gP81u8A,840
64
- sql_code_graph-1.4.3.dist-info/METADATA,sha256=a-9uSmKrh5FDurLJg_LFgkaNH8K1_ke-engcyBzhues,14085
65
- sql_code_graph-1.4.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
66
- sql_code_graph-1.4.3.dist-info/entry_points.txt,sha256=Wfe49sVzV9p4eVFGo5RxcV-frr3HOP0yzzst8JBxQLQ,46
67
- sql_code_graph-1.4.3.dist-info/RECORD,,
64
+ sql_code_graph-1.5.0.dist-info/METADATA,sha256=LWYY4WQliA5p-YOMnci4GcEGjKkJGy8sRBWPj8a30n8,14085
65
+ sql_code_graph-1.5.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
66
+ sql_code_graph-1.5.0.dist-info/entry_points.txt,sha256=Wfe49sVzV9p4eVFGo5RxcV-frr3HOP0yzzst8JBxQLQ,46
67
+ sql_code_graph-1.5.0.dist-info/RECORD,,
sqlcg/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """SQL Code Graph - SQL lineage and dependency analysis tool."""
2
2
 
3
- __version__ = "1.4.3"
3
+ __version__ = "1.5.0"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -20,6 +20,8 @@ from pathlib import Path
20
20
  import typer
21
21
  from rich.console import Console
22
22
 
23
+ from sqlcg.cli.commands.mcp import stop_server
24
+
23
25
  console = Console()
24
26
 
25
27
  _SERVER_KEY = "sql-code-graph"
@@ -70,10 +72,12 @@ def install_cmd(
70
72
  claude_json = Path.home() / ".claude.json"
71
73
  console.print("[dim]--dry-run: would write to ~/.claude.json:[/dim]")
72
74
  _preview_claude_json(claude_json, entry)
75
+ console.print("[dim]--dry-run: would stop running MCP server[/dim]")
73
76
  _provision_skill(resolved_scope, repo, dry_run=True)
74
77
  return
75
78
 
76
79
  # --- Try official claude CLI first ---
80
+ configured_via_claude_cli = False
77
81
  claude_bin = shutil.which("claude")
78
82
  if claude_bin:
79
83
  proc = subprocess.run(
@@ -84,56 +88,65 @@ def install_cmd(
84
88
  if proc.returncode == 0:
85
89
  console.print(f"[green]Configured:[/green] {_SERVER_KEY} via `claude mcp add`")
86
90
  console.print("\nRestart Claude Code to pick up the new MCP server.")
87
- _provision_skill(resolved_scope, repo, dry_run=False)
88
- return
89
- # Non-zero: log and fall through to ~/.claude.json fallback
90
- console.print(
91
- f"[yellow]Warning:[/yellow] `claude mcp add` returned rc={proc.returncode}; "
92
- "falling back to ~/.claude.json write."
93
- )
94
- if proc.stderr:
95
- console.print(f"[dim]{proc.stderr.strip()}[/dim]")
96
-
97
- # --- Fallback: write ~/.claude.json directly ---
98
- claude_json = Path.home() / ".claude.json"
99
- if claude_json.exists():
100
- try:
101
- data: dict = json.loads(claude_json.read_text())
102
- except (json.JSONDecodeError, OSError, TypeError):
91
+ configured_via_claude_cli = True
92
+ else:
93
+ # Non-zero: log and fall through to ~/.claude.json fallback
103
94
  console.print(
104
- f"[yellow]Warning:[/yellow] {claude_json} contains invalid JSON "
105
- "mcpServers key will be added"
95
+ f"[yellow]Warning:[/yellow] `claude mcp add` returned rc={proc.returncode}; "
96
+ "falling back to ~/.claude.json write."
106
97
  )
98
+ if proc.stderr:
99
+ console.print(f"[dim]{proc.stderr.strip()}[/dim]")
100
+
101
+ if not configured_via_claude_cli:
102
+ # --- Fallback: write ~/.claude.json directly ---
103
+ claude_json = Path.home() / ".claude.json"
104
+ if claude_json.exists():
105
+ try:
106
+ data: dict = json.loads(claude_json.read_text())
107
+ except (json.JSONDecodeError, OSError, TypeError):
108
+ console.print(
109
+ f"[yellow]Warning:[/yellow] {claude_json} contains invalid JSON — "
110
+ "mcpServers key will be added"
111
+ )
112
+ data = {}
113
+ else:
107
114
  data = {}
108
- else:
109
- data = {}
110
115
 
111
- existing = data.get("mcpServers", {}).get("user", {}).get(_SERVER_KEY)
112
- if existing == entry:
113
- console.print(f"[green]Already configured:[/green] {_SERVER_KEY} (in ~/.claude.json)")
114
- _provision_skill(resolved_scope, repo, dry_run=False)
115
- return
116
+ existing = data.get("mcpServers", {}).get("user", {}).get(_SERVER_KEY)
117
+ if existing == entry:
118
+ console.print(f"[green]Already configured:[/green] {_SERVER_KEY} (in ~/.claude.json)")
119
+ else:
120
+ data.setdefault("mcpServers", {}).setdefault("user", {})[_SERVER_KEY] = entry
116
121
 
117
- data.setdefault("mcpServers", {}).setdefault("user", {})[_SERVER_KEY] = entry
122
+ try:
123
+ tmp = claude_json.with_suffix(".tmp")
124
+ tmp.write_text(json.dumps(data, indent=2) + "\n")
125
+ os.replace(tmp, claude_json)
126
+ except (OSError, TypeError, AttributeError):
127
+ pass # Ignore file I/O errors in testing
118
128
 
119
- try:
120
- tmp = claude_json.with_suffix(".tmp")
121
- tmp.write_text(json.dumps(data, indent=2) + "\n")
122
- os.replace(tmp, claude_json)
123
- except (OSError, TypeError, AttributeError):
124
- pass # Ignore file I/O errors in testing
125
-
126
- cmd_str = " ".join(cmd_parts)
127
- console.print(f"[green]Configured:[/green] {_SERVER_KEY} → {cmd_str}")
128
- console.print(f"[dim]Written to {claude_json}[/dim]")
129
-
130
- if entry.get("command") == "uvx":
131
- console.print(
132
- "[yellow]Note:[/yellow] First startup downloads dependencies (~30s). "
133
- "Subsequent restarts use cache (~1s)."
134
- )
129
+ cmd_str = " ".join(cmd_parts)
130
+ console.print(f"[green]Configured:[/green] {_SERVER_KEY} → {cmd_str}")
131
+ console.print(f"[dim]Written to {claude_json}[/dim]")
132
+
133
+ if entry.get("command") == "uvx":
134
+ console.print(
135
+ "[yellow]Note:[/yellow] First startup downloads dependencies (~30s). "
136
+ "Subsequent restarts use cache (~1s)."
137
+ )
135
138
 
136
- console.print("\nRestart Claude Code to pick up the new MCP server.")
139
+ console.print("\nRestart Claude Code to pick up the new MCP server.")
140
+
141
+ # --- Single non-dry-run tail: all three success branches converge here. ---
142
+ # The config may not have changed (e.g. "Already configured"), but the
143
+ # binary on disk did — so the stale running server still must be stopped
144
+ # so the editor respawns the freshly-installed build.
145
+ stopped = stop_server()
146
+ if stopped:
147
+ console.print("Stopped running MCP server; your editor will respawn it on the new build.")
148
+ else:
149
+ console.print("No running MCP server to refresh.")
137
150
  _provision_skill(resolved_scope, repo, dry_run=False)
138
151
 
139
152
 
sqlcg/cli/commands/mcp.py CHANGED
@@ -77,9 +77,19 @@ def mcp_best_practices() -> None:
77
77
  def mcp_status() -> None:
78
78
  """Print server status JSON (connects to control socket).
79
79
 
80
- Returns JSON with fields: running, pid, db_path, indexed_sha, head_sha,
81
- stale_by_commits, connected_clients, uptime, writer_queue when a server
82
- is live.
80
+ Returns JSON with fields: running, version, pid, db_path, indexed_sha,
81
+ head_sha, stale_by_commits, connected_clients, uptime, writer_queue,
82
+ stale_by_version when a server is live.
83
+
84
+ `version` is the *running* server's reported sqlcg.__version__ (read live
85
+ over the control socket — the build the editor is currently talking to).
86
+ `stale_by_version` compares it against the *installed* sqlcg.__version__
87
+ (the CLI process you just invoked): true when they differ, false when they
88
+ match, null when the running version cannot be determined (degraded mode).
89
+ When stale, a warning is printed telling you to restart the MCP server via
90
+ your editor or run `sqlcg install` (which stops the stale server for you).
91
+ This is distinct from `stale_by_commits` (graph-vs-repo freshness, not
92
+ package-vs-binary drift).
83
93
 
84
94
  The status response is length-prefixed framed (v1.3.0, B3) so large
85
95
  writer_queue payloads are received in full — the client uses the
@@ -87,7 +97,8 @@ def mcp_status() -> None:
87
97
 
88
98
  When no server is found: {"running": false}.
89
99
  When the PID file exists with a live process but the socket is unavailable:
90
- {"running": true, "degraded": "socket unavailable", ...}.
100
+ {"running": true, "degraded": "socket unavailable", "stale_by_version": null, ...}
101
+ (the running version cannot be read without a live socket).
91
102
 
92
103
  R3 (stale socket): if the socket file exists but the server is not
93
104
  responding (ConnectionRefusedError / FileNotFoundError), falls through
@@ -119,9 +130,25 @@ def mcp_status() -> None:
119
130
 
120
131
  status = json.loads(data.decode())
121
132
 
133
+ # Drift detection: compare the *running* server's reported version (read
134
+ # over the socket, from the live process) against the *installed*
135
+ # sqlcg.__version__ (the version of the CLI process you just invoked).
136
+ from sqlcg import __version__
137
+
138
+ running_version = status.get("version")
139
+ stale = running_version is not None and running_version != __version__
140
+ status = {**status, "version": running_version, "stale_by_version": stale}
141
+
122
142
  # Pretty-print the base fields.
123
143
  console.print_json(json.dumps({k: v for k, v in status.items() if k != "writer_queue"}))
124
144
 
145
+ if stale:
146
+ console.print(
147
+ f"[yellow]Warning:[/yellow] running MCP server is v{running_version}, "
148
+ f"installed sqlcg is v{__version__} — restart the MCP server via your "
149
+ "editor (or run `sqlcg install`, which stops the stale server for you)."
150
+ )
151
+
125
152
  # Render the writer_queue block separately for readability.
126
153
  wq = status.get("writer_queue")
127
154
  if wq:
@@ -178,6 +205,10 @@ def mcp_status() -> None:
178
205
  "degraded": "socket unavailable",
179
206
  "pid": rec["pid"],
180
207
  "db_path": rec["db_path"],
208
+ # Cannot read the running version without a live socket —
209
+ # omit `version` and report drift as unknown (None → JSON
210
+ # null), never guess.
211
+ "stale_by_version": None,
181
212
  }
182
213
  )
183
214
  )
@@ -185,16 +216,20 @@ def mcp_status() -> None:
185
216
  console.print_json(json.dumps({"running": False}))
186
217
 
187
218
 
188
- @app.command("stop")
189
- def mcp_stop() -> None:
190
- """Stop the running MCP server gracefully.
219
+ def stop_server() -> bool:
220
+ """Stop a running MCP server gracefully (best-effort). Returns True if stopped.
191
221
 
192
222
  Sends a ``stop`` op via the control socket; waits up to 5 s for the
193
223
  socket file to disappear (confirming clean exit). Falls back to SIGTERM
194
224
  on the PID-file PID if the socket is unavailable.
195
225
 
196
226
  R3 (stale socket): ``ConnectionRefusedError`` / ``FileNotFoundError`` are
197
- caught — never hangs on a dead socket.
227
+ caught — never hangs on a dead socket; a socket error here must not abort
228
+ the caller (``mcp_stop`` and ``install_cmd`` both rely on this).
229
+
230
+ Returns:
231
+ True if a running server was found and stopped (via socket or SIGTERM
232
+ fallback); False if no running server was found.
198
233
  """
199
234
  import socket as _socket
200
235
  import time
@@ -214,6 +249,7 @@ def mcp_stop() -> None:
214
249
  break
215
250
  time.sleep(0.5)
216
251
  console.print("[green]Server stopped.[/green]")
252
+ return True
217
253
  except (FileNotFoundError, ConnectionRefusedError, OSError):
218
254
  # Socket unavailable — fall back to SIGTERM via PID file
219
255
  import signal
@@ -222,8 +258,20 @@ def mcp_stop() -> None:
222
258
  if rec and is_pid_alive(rec["pid"]):
223
259
  os.kill(rec["pid"], signal.SIGTERM)
224
260
  console.print(f"[yellow]Socket unavailable — sent SIGTERM to PID {rec['pid']}[/yellow]")
225
- else:
226
- console.print("[yellow]No server found to stop.[/yellow]")
261
+ return True
262
+ return False
263
+
264
+
265
+ @app.command("stop")
266
+ def mcp_stop() -> None:
267
+ """Stop the running MCP server gracefully.
268
+
269
+ Delegates to ``stop_server()`` (shared with ``sqlcg install``) and prints
270
+ a final message based on whether a server was found.
271
+ """
272
+ stopped = stop_server()
273
+ if not stopped:
274
+ console.print("[yellow]No server found to stop.[/yellow]")
227
275
 
228
276
 
229
277
  @app.command("restart")
sqlcg/cli/main.py CHANGED
@@ -34,8 +34,41 @@ USING THE MCP TOOLS:
34
34
  Note: Binary is `sqlcg`; PyPI package is `sql-code-graph`.
35
35
  """
36
36
 
37
+
38
+ def _version_string() -> str:
39
+ from sqlcg import __version__
40
+
41
+ return f"sqlcg version {__version__}"
42
+
43
+
44
+ def _version_callback(value: bool) -> None:
45
+ if value:
46
+ typer.echo(_version_string())
47
+ raise typer.Exit()
48
+
49
+
37
50
  app = typer.Typer(name="sqlcg", help=help_text)
38
51
 
52
+
53
+ @app.callback(invoke_without_command=True)
54
+ def _root(
55
+ ctx: typer.Context,
56
+ version: bool = typer.Option(
57
+ False,
58
+ "--version",
59
+ help="Show version and exit.",
60
+ is_eager=True,
61
+ callback=_version_callback,
62
+ ),
63
+ ) -> None:
64
+ """SQL code graph analyzer."""
65
+ # The eager --version callback exits before we get here when --version is
66
+ # passed. Otherwise: if no subcommand was given, show help (matching the
67
+ # pre-callback behaviour) instead of silently exiting 0.
68
+ if ctx.invoked_subcommand is None:
69
+ typer.echo(ctx.get_help())
70
+
71
+
39
72
  # Register subcommand groups
40
73
  app.add_typer(db.app, name="db")
41
74
  app.add_typer(find.app, name="find")
@@ -56,9 +89,7 @@ app.command("uninstall")(uninstall.uninstall_cmd)
56
89
  @app.command()
57
90
  def version() -> None:
58
91
  """Show version."""
59
- from sqlcg import __version__
60
-
61
- typer.echo(f"sqlcg version {__version__}")
92
+ typer.echo(_version_string())
62
93
 
63
94
 
64
95
  def main() -> None:
sqlcg/server/models.py CHANGED
@@ -190,6 +190,9 @@ class DialectRepoResult(BaseModel):
190
190
  class DbInfoResult(BaseModel):
191
191
  """Result of db_info tool — graph health and parse quality diagnostics."""
192
192
 
193
+ sqlcg_version: str = Field(
194
+ ..., description="Installed sqlcg package version (sqlcg.__version__)"
195
+ )
193
196
  schema_version: str = Field(..., description="Graph schema version")
194
197
  node_counts: dict[str, int] = Field(
195
198
  default_factory=dict,
sqlcg/server/server.py CHANGED
@@ -27,6 +27,7 @@ _real_stdout_buffer = os.fdopen(os.dup(1), "wb", buffering=0)
27
27
  from dotenv import load_dotenv # noqa: E402
28
28
  from mcp.server import FastMCP # noqa: E402
29
29
 
30
+ from sqlcg import __version__ # noqa: E402
30
31
  from sqlcg.utils.logging import getLogger # noqa: E402
31
32
 
32
33
  logger = getLogger(__name__)
@@ -35,6 +36,13 @@ logger = getLogger(__name__)
35
36
  # This is safe because _real_stdout_buffer has already captured fd 1 above.
36
37
  mcp = FastMCP("SQL Code Graph")
37
38
 
39
+ # FastMCP.__init__ does not accept a `version` kwarg (verified signature); the
40
+ # underlying mcp.server.lowlevel.Server.__init__(self, name, version=None, ...)
41
+ # does. Set it post-construction so the `initialize` handshake reports
42
+ # serverInfo.version == sqlcg.__version__ instead of None. Deliberate, pinned
43
+ # private-attr write — see tests/unit/test_version_parity.py.
44
+ mcp._mcp_server.version = __version__
45
+
38
46
 
39
47
  def _configure_mcp_logging() -> None:
40
48
  """Redirect sys.stdout to sys.stderr and configure logging to stderr.
@@ -219,6 +227,7 @@ async def _control_socket_task(
219
227
 
220
228
  resp: dict = {
221
229
  "running": True,
230
+ "version": __version__,
222
231
  "pid": os.getpid(),
223
232
  "db_path": str(db_path or _get_db_path()),
224
233
  "indexed_sha": indexed_sha,
sqlcg/server/tools.py CHANGED
@@ -1672,13 +1672,22 @@ def db_info() -> DbInfoResult:
1672
1672
  those files. Table-level lineage is still available.
1673
1673
  - `warnings` list — empty means the graph is healthy.
1674
1674
 
1675
+ `sqlcg_version` is the installed *package* build (sqlcg.__version__) — use
1676
+ it to self-check you are talking to the build you expect (the MCP protocol
1677
+ `serverInfo.version` is not reliably visible to the agent persona). This is
1678
+ a different axis from `schema_version`, which tracks the graph schema, not
1679
+ the package: do not conflate the two.
1680
+
1675
1681
  Parse quality legend (parsing_mode per SqlQuery node):
1676
1682
  sqlglot — standard path; column lineage available if extracted
1677
1683
  scripting_block — tokenizer fallback; column lineage unavailable
1678
1684
 
1679
1685
  Returns:
1680
- DbInfoResult with schema version, node counts, parse quality, and warnings
1686
+ DbInfoResult with sqlcg/schema versions, node counts, parse quality,
1687
+ and warnings
1681
1688
  """
1689
+ from sqlcg import __version__
1690
+
1682
1691
  db = _get_backend()
1683
1692
 
1684
1693
  schema_version = db.get_schema_version() or "unknown"
@@ -1745,6 +1754,7 @@ def db_info() -> DbInfoResult:
1745
1754
  pass
1746
1755
 
1747
1756
  return DbInfoResult(
1757
+ sqlcg_version=__version__,
1748
1758
  schema_version=schema_version,
1749
1759
  node_counts=node_counts,
1750
1760
  column_lineage_edges=column_lineage_edges,