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.
- {sql_code_graph-1.4.3.dist-info → sql_code_graph-1.5.0.dist-info}/METADATA +1 -1
- {sql_code_graph-1.4.3.dist-info → sql_code_graph-1.5.0.dist-info}/RECORD +11 -11
- sqlcg/__init__.py +1 -1
- sqlcg/cli/commands/install.py +56 -43
- sqlcg/cli/commands/mcp.py +58 -10
- sqlcg/cli/main.py +34 -3
- sqlcg/server/models.py +3 -0
- sqlcg/server/server.py +9 -0
- sqlcg/server/tools.py +11 -1
- {sql_code_graph-1.4.3.dist-info → sql_code_graph-1.5.0.dist-info}/WHEEL +0 -0
- {sql_code_graph-1.4.3.dist-info → sql_code_graph-1.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-code-graph
|
|
3
|
-
Version: 1.
|
|
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
|
|
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=
|
|
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=
|
|
13
|
-
sqlcg/cli/commands/mcp.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
65
|
-
sql_code_graph-1.
|
|
66
|
-
sql_code_graph-1.
|
|
67
|
-
sql_code_graph-1.
|
|
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
sqlcg/cli/commands/install.py
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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]
|
|
105
|
-
"
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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,
|
|
81
|
-
stale_by_commits, connected_clients, uptime, writer_queue
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
File without changes
|
|
File without changes
|