datasecops-cli 0.5.1__tar.gz → 0.5.2__tar.gz

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.
Files changed (57) hide show
  1. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/CHANGELOG.md +21 -0
  2. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/PKG-INFO +54 -4
  3. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/README.md +53 -3
  4. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/docs/getting-started.md +16 -13
  5. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/docs/mcp-server.md +12 -2
  6. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/mcp-servers.json +9 -1
  7. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/pyproject.toml +1 -1
  8. datasecops_cli-0.5.2/src/datasecops_cli/__init__.py +1 -0
  9. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/main.py +40 -43
  10. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/menus/configuration.py +11 -57
  11. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/menus/development.py +24 -3
  12. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/menus/downloads.py +11 -57
  13. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/linting_service.py +29 -1
  14. datasecops_cli-0.5.2/src/datasecops_cli/utilities/mcp.py +74 -0
  15. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_mcp/connection.py +8 -0
  16. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_mcp/server.py +71 -0
  17. datasecops_cli-0.5.1/src/datasecops_cli/__init__.py +0 -1
  18. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/.github/workflows/auto-tag.yml +0 -0
  19. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/.github/workflows/publish-cli.yml +0 -0
  20. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/.github/workflows/test.yml +0 -0
  21. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/.gitignore +0 -0
  22. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/DEVELOPMENT.md +0 -0
  23. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/LICENSE +0 -0
  24. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/docs/git-operations.md +0 -0
  25. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/docs/legacy.md +0 -0
  26. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/docs/legacy_plan_of_action.md +0 -0
  27. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/setup.ps1 +0 -0
  28. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/setup.sh +0 -0
  29. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/config.py +0 -0
  30. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/menus/__init__.py +0 -0
  31. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/menus/git_operations.py +0 -0
  32. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/models/__init__.py +0 -0
  33. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/models/git_helpers.py +0 -0
  34. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/models/project_config.py +0 -0
  35. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/__init__.py +0 -0
  36. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  37. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  38. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/dbt_runner.py +0 -0
  39. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  40. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/download_service.py +0 -0
  41. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/git_service.py +0 -0
  42. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/skill_service.py +0 -0
  43. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/snowflake_service.py +0 -0
  44. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/services/upstream_service.py +0 -0
  45. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/utilities/__init__.py +0 -0
  46. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/utilities/display.py +0 -0
  47. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/utilities/file_utils.py +0 -0
  48. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  49. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_mcp/__init__.py +0 -0
  50. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/src/datasecops_mcp/__main__.py +0 -0
  51. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/__init__.py +0 -0
  52. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_config.py +0 -0
  53. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_file_utils.py +0 -0
  54. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_main.py +0 -0
  55. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_models.py +0 -0
  56. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_version.py +0 -0
  57. {datasecops_cli-0.5.1 → datasecops_cli-0.5.2}/tests/test_yaml_utils.py +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to the DataSecOps CLI are documented in this file.
4
4
 
5
+ ## [0.5.2] - 2026-06-05
6
+
7
+ ### Added
8
+
9
+ - **`get_sqlfluff_config` MCP tool** — exposes the rendered `.sqlfluff` INI config file from the native app, allowing AI tools to retrieve the exact linting configuration without needing a local `.sqlfluff` file.
10
+ - **`get_documentation` MCP tool** — exposes framework documentation topics (model development, data governance, deployment, etc.) so AI tools can reference framework conventions directly.
11
+
12
+ ### Changed
13
+
14
+ - **Cortex Code MCP config is now project-level** — MCP server registration for Cortex Code now writes `.snowflake/cortex/mcp.json` in the project root instead of calling `cortex mcp add`. This enables project-scoped MCP servers in Cortex Code Desktop and removes the dependency on the `cortex` binary being installed.
15
+ - **All MCP servers included in Cortex Code config** — the datasecops-framework, GitHub, Azure DevOps, and dbt MCP servers are all written to the project-level config file with `"type": "stdio"` field.
16
+ - **Cortex Code always available as an AI tool option** — removed the `shutil.which("cortex")` check since we no longer need the cortex binary for MCP configuration.
17
+ - **Expanded `_ALLOWED_PROCEDURES`** — added `get_sqlfluff_config_file` and `get_documentation` to the MCP connection allowlist.
18
+
19
+ ### Fixed
20
+
21
+ - **Linting now installs dbt-snowflake at framework-pinned version** — the CLI lint menu fetches both SQLFluff and dbt requirements from the native app and ensures all packages (including `dbt-core` and `dbt-snowflake`) are installed at the correct versions before linting.
22
+ - **Linting runs `dbt deps` if packages are missing** — if `packages.yml` exists but `dbt_packages/` is empty, `dbt deps` is run automatically before linting to satisfy the sqlfluff dbt templater.
23
+ - **Default `.sqlfluffignore` created on first lint** — excludes `target/`, `dbt_packages/`, `dbt_modules/`, `logs/`, `macros/`, `models/cortex/agents`, `models/cortex/semantic_views`, `objects/stages/config/`, and `objects/file_formats/config/`.
24
+ - **MCP linting tools check for dbt-snowflake** — the MCP server lint/fix tools now verify `dbt-snowflake` is installed (and install it if missing) before running SQLFluff.
25
+
5
26
  ## [0.5.1] - 2026-06-04
6
27
 
7
28
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -158,10 +158,60 @@ The package includes an MCP (Model Context Protocol) server that exposes your fr
158
158
 
159
159
  ### Setup for AI Tools
160
160
 
161
- **Cortex Code:**
161
+ **Cortex Code** — add to `.snowflake/cortex/mcp.json` in your project root:
162
162
 
163
- ```bash
164
- cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
163
+ ```json
164
+ {
165
+ "mcpServers": {
166
+ "datasecops-framework": {
167
+ "type": "stdio",
168
+ "command": "datasecops-mcp",
169
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
170
+ },
171
+ "github": {
172
+ "type": "stdio",
173
+ "command": "npx",
174
+ "args": ["-y", "@modelcontextprotocol/server-github"],
175
+ "env": {
176
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-github-pat>"
177
+ }
178
+ },
179
+ "dbt": {
180
+ "type": "stdio",
181
+ "command": "uvx",
182
+ "args": ["dbt-mcp"]
183
+ }
184
+ }
185
+ }
186
+ ```
187
+
188
+ For Azure DevOps instead of GitHub:
189
+
190
+ ```json
191
+ {
192
+ "mcpServers": {
193
+ "datasecops-framework": {
194
+ "type": "stdio",
195
+ "command": "datasecops-mcp",
196
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
197
+ },
198
+ "azure-devops": {
199
+ "type": "stdio",
200
+ "command": "npx",
201
+ "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
202
+ "env": {
203
+ "AZURE_DEVOPS_ORG_URL": "https://dev.azure.com/<your-org>",
204
+ "AZURE_DEVOPS_AUTH_METHOD": "pat",
205
+ "AZURE_DEVOPS_PAT": "<your-azure-pat>"
206
+ }
207
+ },
208
+ "dbt": {
209
+ "type": "stdio",
210
+ "command": "uvx",
211
+ "args": ["dbt-mcp"]
212
+ }
213
+ }
214
+ }
165
215
  ```
166
216
 
167
217
  **VS Code / Cursor** — add to `.vscode/mcp.json` or `.cursor/mcp.json`:
@@ -139,10 +139,60 @@ The package includes an MCP (Model Context Protocol) server that exposes your fr
139
139
 
140
140
  ### Setup for AI Tools
141
141
 
142
- **Cortex Code:**
142
+ **Cortex Code** — add to `.snowflake/cortex/mcp.json` in your project root:
143
143
 
144
- ```bash
145
- cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
144
+ ```json
145
+ {
146
+ "mcpServers": {
147
+ "datasecops-framework": {
148
+ "type": "stdio",
149
+ "command": "datasecops-mcp",
150
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
151
+ },
152
+ "github": {
153
+ "type": "stdio",
154
+ "command": "npx",
155
+ "args": ["-y", "@modelcontextprotocol/server-github"],
156
+ "env": {
157
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-github-pat>"
158
+ }
159
+ },
160
+ "dbt": {
161
+ "type": "stdio",
162
+ "command": "uvx",
163
+ "args": ["dbt-mcp"]
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ For Azure DevOps instead of GitHub:
170
+
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "datasecops-framework": {
175
+ "type": "stdio",
176
+ "command": "datasecops-mcp",
177
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
178
+ },
179
+ "azure-devops": {
180
+ "type": "stdio",
181
+ "command": "npx",
182
+ "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
183
+ "env": {
184
+ "AZURE_DEVOPS_ORG_URL": "https://dev.azure.com/<your-org>",
185
+ "AZURE_DEVOPS_AUTH_METHOD": "pat",
186
+ "AZURE_DEVOPS_PAT": "<your-azure-pat>"
187
+ }
188
+ },
189
+ "dbt": {
190
+ "type": "stdio",
191
+ "command": "uvx",
192
+ "args": ["dbt-mcp"]
193
+ }
194
+ }
195
+ }
146
196
  ```
147
197
 
148
198
  **VS Code / Cursor** — add to `.vscode/mcp.json` or `.cursor/mcp.json`:
@@ -215,20 +215,23 @@ Create `.cursor/mcp.json` in your project root with the same format as above.
215
215
 
216
216
  ### Cortex Code (CoCo)
217
217
 
218
- Add servers from the command line:
218
+ Create `.snowflake/cortex/mcp.json` in your project root:
219
219
 
220
- ```bash
221
- # DataSecOps Framework
222
- cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
223
-
224
- # dbt
225
- cortex mcp add dbt -- uvx dbt-mcp
226
-
227
- # GitHub
228
- cortex mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_TOKEN -- npx -y @modelcontextprotocol/server-github
229
-
230
- # OR Azure DevOps
231
- cortex mcp add azure-devops -e AZURE_DEVOPS_ORG_URL=$AZURE_DEVOPS_ORG_URL -e AZURE_DEVOPS_AUTH_METHOD=pat -e AZURE_DEVOPS_PAT=$AZURE_DEVOPS_PAT -- npx -y @tiberriver256/mcp-server-azure-devops
220
+ ```json
221
+ {
222
+ "mcpServers": {
223
+ "datasecops-framework": {
224
+ "type": "stdio",
225
+ "command": "datasecops-mcp",
226
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
227
+ },
228
+ "dbt": {
229
+ "type": "stdio",
230
+ "command": "uvx",
231
+ "args": ["dbt-mcp"]
232
+ }
233
+ }
234
+ }
232
235
  ```
233
236
 
234
237
  Verify all servers are connected:
@@ -24,8 +24,18 @@ If `--connection-name` and `--app-database` are not provided, the server falls b
24
24
 
25
25
  ### Cortex Code
26
26
 
27
- ```bash
28
- cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
27
+ Create `.snowflake/cortex/mcp.json` in your project root:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "datasecops-framework": {
33
+ "type": "stdio",
34
+ "command": "datasecops-mcp",
35
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
36
+ }
37
+ }
38
+ }
29
39
  ```
30
40
 
31
41
  ### Claude Code
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "datasecops-framework": {
4
+ "type": "stdio",
4
5
  "command": "datasecops-mcp",
5
6
  "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"],
6
- "env": {},
7
7
  "description": "DataSecOps Framework governance rules, branching conventions, linting config, and project profiles from your Snowflake Native App"
8
8
  },
9
9
  "github": {
10
+ "type": "stdio",
10
11
  "command": "npx",
11
12
  "args": ["-y", "@modelcontextprotocol/server-github"],
12
13
  "env": {
@@ -15,6 +16,7 @@
15
16
  "description": "GitHub API access for PRs, issues, checks, and repository management"
16
17
  },
17
18
  "azure-devops": {
19
+ "type": "stdio",
18
20
  "command": "npx",
19
21
  "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
20
22
  "env": {
@@ -23,6 +25,12 @@
23
25
  "AZURE_DEVOPS_PAT": "<your-azure-pat>"
24
26
  },
25
27
  "description": "Azure DevOps API access for PRs, pipelines, work items, and boards"
28
+ },
29
+ "dbt": {
30
+ "type": "stdio",
31
+ "command": "uvx",
32
+ "args": ["dbt-mcp"],
33
+ "description": "dbt lineage, model discovery, codegen, and semantic layer"
26
34
  }
27
35
  }
28
36
  }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.5.1"
7
+ version = "0.5.2"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.5.2"
@@ -307,66 +307,63 @@ def _run_setup(project_dir: Path):
307
307
 
308
308
  info_line("")
309
309
  info_line("Which AI tool are you configuring?")
310
- tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code"]
311
- if shutil.which("cortex"):
312
- tool_options.append("Cortex Code")
310
+ tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code", "Cortex Code"]
313
311
  tool_options.append("Skip")
314
312
  tool_choice = select_from_list(tool_options, "tool", add_back=False)
315
313
 
316
314
  if tool_choice == "Cortex Code":
317
315
  info_line("")
318
316
  info_line("Adding MCP servers to Cortex Code...")
319
- try:
320
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
321
- "datasecops-mcp", "--connection-name", connection_name,
322
- "--app-database", app_database],
323
- capture_output=True, text=True)
324
- success_line("Added datasecops-framework MCP server")
325
- except FileNotFoundError:
326
- warning_line(
327
- "cortex not found — run manually: cortex mcp add datasecops-framework "
328
- f"-- datasecops-mcp --connection-name {connection_name} --app-database {app_database}"
329
- )
317
+ mcp_path = project_dir / ".snowflake" / "cortex" / "mcp.json"
318
+
319
+ servers = {"datasecops-framework": {
320
+ "type": "stdio",
321
+ "command": "datasecops-mcp",
322
+ "args": ["--connection-name", connection_name, "--app-database", app_database],
323
+ }}
330
324
 
331
325
  # Add AI Agent MCP server if enabled
332
326
  if ai_agent_mcp_url:
333
- try:
334
- result = subprocess.run(["cortex", "mcp", "add", "datasecops-docs", ai_agent_mcp_url, "--transport", "http"],
335
- capture_output=True, text=True)
336
- if result.returncode == 0:
337
- success_line("Added datasecops-docs MCP server (AI Agent documentation search)")
338
- else:
339
- warning_line(f"Failed to add AI Agent MCP server: {result.stderr.strip() or 'unknown error'}")
340
- except Exception as agent_err:
341
- warning_line(f"Could not add AI Agent MCP server: {agent_err}")
327
+ snowflake_pat = get_input_string(
328
+ "Enter your Snowflake PAT for datasecops-docs MCP server (or press Enter to skip): ",
329
+ allow_empty=True,
330
+ )
331
+ server_entry = {
332
+ "type": "http",
333
+ "url": ai_agent_mcp_url,
334
+ }
335
+ if snowflake_pat:
336
+ server_entry["headers"] = {"Authorization": f"Bearer {snowflake_pat}"}
337
+ servers["datasecops-docs"] = server_entry
342
338
 
343
339
  if platform_choice == "GitHub":
344
340
  github_token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
345
341
  if github_token:
346
- try:
347
- subprocess.run(["cortex", "mcp", "add", "github",
348
- "-e", f"GITHUB_PERSONAL_ACCESS_TOKEN={github_token}",
349
- "--", "npx", "-y", "@modelcontextprotocol/server-github"],
350
- capture_output=True, text=True)
351
- success_line("Added GitHub MCP server")
352
- except FileNotFoundError:
353
- warning_line("cortex not found — configure GitHub MCP server manually")
342
+ servers["github"] = {
343
+ "type": "stdio",
344
+ "command": "npx",
345
+ "args": ["-y", "@modelcontextprotocol/server-github"],
346
+ "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": github_token},
347
+ }
354
348
  elif platform_choice == "Azure DevOps":
355
349
  azure_org = get_input_string("Enter your Azure DevOps org URL (e.g. https://dev.azure.com/myorg): ")
356
350
  azure_pat = get_input_string("Enter your Azure DevOps PAT (or press Enter to skip): ", allow_empty=True)
357
351
  if azure_pat:
358
- try:
359
- subprocess.run(["cortex", "mcp", "add", "azure-devops",
360
- "-e", f"AZURE_DEVOPS_ORG_URL={azure_org}",
361
- "-e", "AZURE_DEVOPS_AUTH_METHOD=pat",
362
- "-e", f"AZURE_DEVOPS_PAT={azure_pat}",
363
- "--", "npx", "-y", "@tiberriver256/mcp-server-azure-devops"],
364
- capture_output=True, text=True)
365
- success_line("Added Azure DevOps MCP server")
366
- except FileNotFoundError:
367
- warning_line("cortex not found — configure Azure DevOps MCP server manually")
368
-
369
- success_line("Cortex Code MCP servers configured")
352
+ servers["azure-devops"] = {
353
+ "type": "stdio",
354
+ "command": "npx",
355
+ "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
356
+ "env": {
357
+ "AZURE_DEVOPS_ORG_URL": azure_org,
358
+ "AZURE_DEVOPS_AUTH_METHOD": "pat",
359
+ "AZURE_DEVOPS_PAT": azure_pat,
360
+ },
361
+ }
362
+
363
+ from datasecops_cli.utilities.file_utils import ensure_dir, write_file
364
+ ensure_dir(mcp_path.parent)
365
+ write_file(mcp_path, json.dumps({"mcpServers": servers}, indent=2))
366
+ success_line(f"MCP config written to {mcp_path}")
370
367
 
371
368
  elif tool_choice in ("VS Code (Copilot)", "Cursor", "Claude Code"):
372
369
  dir_map = {
@@ -150,9 +150,7 @@ class ConfigurationMenu:
150
150
  # Select target tool
151
151
  info_line("")
152
152
  info_line("Which AI tool are you configuring?")
153
- tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code"]
154
- if shutil.which("cortex"):
155
- tool_options.append("Cortex Code")
153
+ tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code", "Cortex Code"]
156
154
  tool_choice = select_from_list(tool_options, "tool", add_back=False)
157
155
 
158
156
  if tool_choice == "Cortex Code":
@@ -163,60 +161,16 @@ class ConfigurationMenu:
163
161
  complete_action()
164
162
 
165
163
  def _install_mcp_cortex(self, servers: list[str], platform: str) -> None:
166
- """Install MCP servers via cortex mcp add."""
167
- info_line("")
168
- for server in servers:
169
- if server == "datasecops-framework":
170
- conn = self.datasecops_config.connection_name
171
- app_db = self.datasecops_config.app_database
172
- try:
173
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
174
- "datasecops-mcp", "--connection-name", conn,
175
- "--app-database", app_db],
176
- capture_output=True, text=True)
177
- success_line("Added datasecops-framework MCP server")
178
- except FileNotFoundError:
179
- warning_line(
180
- "cortex not found — run manually: cortex mcp add datasecops-framework "
181
- f"-- datasecops-mcp --connection-name {conn} --app-database {app_db}"
182
- )
183
-
184
- elif server == "GitHub":
185
- token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
186
- if token:
187
- try:
188
- subprocess.run(["cortex", "mcp", "add", "github",
189
- "-e", f"GITHUB_PERSONAL_ACCESS_TOKEN={token}",
190
- "--", "npx", "-y", "@modelcontextprotocol/server-github"],
191
- capture_output=True, text=True)
192
- success_line("Added GitHub MCP server")
193
- except FileNotFoundError:
194
- warning_line("cortex not found — configure GitHub MCP server manually")
195
-
196
- elif server == "Azure DevOps":
197
- org = get_input_string("Enter your Azure DevOps org URL (e.g. https://dev.azure.com/myorg): ")
198
- pat = get_input_string("Enter your Azure DevOps PAT (or press Enter to skip): ", allow_empty=True)
199
- if pat:
200
- try:
201
- subprocess.run(["cortex", "mcp", "add", "azure-devops",
202
- "-e", f"AZURE_DEVOPS_ORG_URL={org}",
203
- "-e", "AZURE_DEVOPS_AUTH_METHOD=pat",
204
- "-e", f"AZURE_DEVOPS_PAT={pat}",
205
- "--", "npx", "-y", "@tiberriver256/mcp-server-azure-devops"],
206
- capture_output=True, text=True)
207
- success_line("Added Azure DevOps MCP server")
208
- except FileNotFoundError:
209
- warning_line("cortex not found — configure Azure DevOps MCP server manually")
210
-
211
- elif server == "dbt":
212
- try:
213
- subprocess.run(["cortex", "mcp", "add", "dbt", "--", "uvx", "dbt-mcp"],
214
- capture_output=True, text=True)
215
- success_line("Added dbt MCP server")
216
- except FileNotFoundError:
217
- warning_line("cortex not found — run manually: cortex mcp add dbt -- uvx dbt-mcp")
218
-
219
- success_line("Cortex Code MCP servers configured")
164
+ """Write MCP server config to .snowflake/cortex/mcp.json for Cortex Code."""
165
+ from datasecops_cli.utilities.mcp import write_cortex_mcp_config
166
+
167
+ mcp_path = self.project_dir / ".snowflake" / "cortex" / "mcp.json"
168
+ write_cortex_mcp_config(
169
+ mcp_path=mcp_path,
170
+ servers=servers,
171
+ connection_name=self.datasecops_config.connection_name,
172
+ app_database=self.datasecops_config.app_database,
173
+ )
220
174
 
221
175
  def _install_mcp_json(self, servers: list[str], platform: str, tool: str) -> None:
222
176
  """Write MCP server config as mcp.json for VS Code, Cursor, or Claude Code."""
@@ -164,9 +164,30 @@ class DevelopmentMenu:
164
164
  info_line("No .sqlfluff config found — downloading from framework...")
165
165
  if not self.downloads.download_sqlfluff_config(profiles_dir=self.profiles_dir, dbt_project_dir=self.linting.project_dir):
166
166
  return False
167
- # Fetch pinned versions from the native app and ensure they're installed
168
- required = self.downloads.get_sqlfluff_requirements() if self.downloads else None
169
- return self.linting.ensure_templater_installed(required_packages=required or None)
167
+ # Fetch pinned versions from the native app (sqlfluff + dbt) and ensure they're installed
168
+ required = []
169
+ if self.downloads:
170
+ sqlfluff_pkgs = self.downloads.get_sqlfluff_requirements() or []
171
+ dbt_pkgs = self.downloads.get_dbt_requirements() or []
172
+ required = sqlfluff_pkgs + dbt_pkgs
173
+ if not self.linting.ensure_templater_installed(required_packages=required or None):
174
+ return False
175
+ # Ensure dbt project packages are installed (required by sqlfluff dbt templater)
176
+ self._ensure_dbt_packages()
177
+ return True
178
+
179
+ def _ensure_dbt_packages(self) -> None:
180
+ """Ensure dbt packages are installed for the sqlfluff dbt templater."""
181
+ packages_dir = self.linting.project_dir / "dbt_packages"
182
+ if packages_dir.exists() and any(packages_dir.iterdir()):
183
+ return
184
+
185
+ packages_yml = self.linting.project_dir / "packages.yml"
186
+ if not packages_yml.exists():
187
+ return
188
+
189
+ info_line("Installing dbt packages (required for linting)...")
190
+ self.dbt.deps()
170
191
 
171
192
  def _lint_menu(self) -> None:
172
193
  clear()
@@ -134,9 +134,7 @@ class DownloadsMenu:
134
134
  # Select target tool
135
135
  info_line("")
136
136
  info_line("Which AI tool are you configuring?")
137
- tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code"]
138
- if shutil.which("cortex"):
139
- tool_options.append("Cortex Code")
137
+ tool_options = ["VS Code (Copilot)", "Cursor", "Claude Code", "Cortex Code"]
140
138
  tool_choice = select_from_list(tool_options, "tool", add_back=False)
141
139
 
142
140
  if tool_choice == "Cortex Code":
@@ -147,60 +145,16 @@ class DownloadsMenu:
147
145
  complete_action()
148
146
 
149
147
  def _install_mcp_cortex(self, servers: list[str], platform: str) -> None:
150
- """Install MCP servers via cortex mcp add."""
151
- info_line("")
152
- for server in servers:
153
- if server == "datasecops-framework":
154
- conn = self.datasecops_config.connection_name
155
- app_db = self.datasecops_config.app_database
156
- try:
157
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
158
- "datasecops-mcp", "--connection-name", conn,
159
- "--app-database", app_db],
160
- capture_output=True, text=True)
161
- success_line("Added datasecops-framework MCP server")
162
- except FileNotFoundError:
163
- warning_line(
164
- "cortex not found — run manually: cortex mcp add datasecops-framework "
165
- f"-- datasecops-mcp --connection-name {conn} --app-database {app_db}"
166
- )
167
-
168
- elif server == "GitHub":
169
- token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
170
- if token:
171
- try:
172
- subprocess.run(["cortex", "mcp", "add", "github",
173
- "-e", f"GITHUB_PERSONAL_ACCESS_TOKEN={token}",
174
- "--", "npx", "-y", "@modelcontextprotocol/server-github"],
175
- capture_output=True, text=True)
176
- success_line("Added GitHub MCP server")
177
- except FileNotFoundError:
178
- warning_line("cortex not found — configure GitHub MCP server manually")
179
-
180
- elif server == "Azure DevOps":
181
- org = get_input_string("Enter your Azure DevOps org URL (e.g. https://dev.azure.com/myorg): ")
182
- pat = get_input_string("Enter your Azure DevOps PAT (or press Enter to skip): ", allow_empty=True)
183
- if pat:
184
- try:
185
- subprocess.run(["cortex", "mcp", "add", "azure-devops",
186
- "-e", f"AZURE_DEVOPS_ORG_URL={org}",
187
- "-e", "AZURE_DEVOPS_AUTH_METHOD=pat",
188
- "-e", f"AZURE_DEVOPS_PAT={pat}",
189
- "--", "npx", "-y", "@tiberriver256/mcp-server-azure-devops"],
190
- capture_output=True, text=True)
191
- success_line("Added Azure DevOps MCP server")
192
- except FileNotFoundError:
193
- warning_line("cortex not found — configure Azure DevOps MCP server manually")
194
-
195
- elif server == "dbt":
196
- try:
197
- subprocess.run(["cortex", "mcp", "add", "dbt", "--", "uvx", "dbt-mcp"],
198
- capture_output=True, text=True)
199
- success_line("Added dbt MCP server")
200
- except FileNotFoundError:
201
- warning_line("cortex not found — run manually: cortex mcp add dbt -- uvx dbt-mcp")
202
-
203
- success_line("Cortex Code MCP servers configured")
148
+ """Write MCP server config to .snowflake/cortex/mcp.json for Cortex Code."""
149
+ from datasecops_cli.utilities.mcp import write_cortex_mcp_config
150
+
151
+ mcp_path = Path.cwd() / ".snowflake" / "cortex" / "mcp.json"
152
+ write_cortex_mcp_config(
153
+ mcp_path=mcp_path,
154
+ servers=servers,
155
+ connection_name=self.datasecops_config.connection_name,
156
+ app_database=self.datasecops_config.app_database,
157
+ )
204
158
 
205
159
  def _install_mcp_json(self, servers: list[str], platform: str, tool: str) -> None:
206
160
  """Write MCP server config as mcp.json for VS Code, Cursor, or Claude Code."""
@@ -82,7 +82,7 @@ class LintingService:
82
82
  return self.install_requirements(to_install)
83
83
 
84
84
  # No pinned versions — just ensure sqlfluff packages are present
85
- missing = [pkg for pkg in ["sqlfluff", "sqlfluff-templater-dbt"] if not installed.get(pkg)]
85
+ missing = [pkg for pkg in ["sqlfluff", "sqlfluff-templater-dbt", "dbt-snowflake"] if not installed.get(pkg)]
86
86
  if not missing:
87
87
  return True
88
88
  warning_line(f"Missing packages: {', '.join(missing)}")
@@ -99,6 +99,9 @@ class LintingService:
99
99
  config_path = self.project_dir / ".sqlfluff"
100
100
  if config_path.exists():
101
101
  cmd += ["--config", str(config_path)]
102
+
103
+ # Ensure .sqlfluffignore is created with defaults if it doesn't exist
104
+ self._ensure_sqlfluffignore()
102
105
 
103
106
  info_line(f"Running: sqlfluff {action}")
104
107
  result = subprocess.run(cmd, capture_output=False, cwd=str(self.project_dir))
@@ -109,6 +112,31 @@ class LintingService:
109
112
  else:
110
113
  error_line(f"SQLFluff {action} failed with exit code {result.returncode}")
111
114
  return result
115
+
116
+ def _ensure_sqlfluffignore(self) -> None:
117
+ """Create a default .sqlfluffignore if one doesn't exist."""
118
+ ignore_path = self.project_dir / ".sqlfluffignore"
119
+ if ignore_path.exists():
120
+ return
121
+ default_content = "\n".join([
122
+ "# Directories to exclude from linting",
123
+ "target/",
124
+ "dbt_packages/",
125
+ "dbt_modules/",
126
+ "logs/",
127
+ "macros/",
128
+ "",
129
+ "# Ignore cortex agents and semantic views - spec is part of the body",
130
+ "models/cortex/agents",
131
+ "models/cortex/semantic_views",
132
+ "",
133
+ "# Ignore config-only object directories",
134
+ "objects/stages/config/",
135
+ "objects/file_formats/config/",
136
+ "",
137
+ ])
138
+ ignore_path.write_text(default_content, encoding="utf-8")
139
+ info_line(f"Created default .sqlfluffignore at {ignore_path}")
112
140
 
113
141
  def lint_modified(self, fix: bool = False, changed_files: list[str] = None) -> None:
114
142
  if not changed_files:
@@ -0,0 +1,74 @@
1
+ """MCP server configuration utilities."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from datasecops_cli.utilities.display import get_input_string, success_line
7
+ from datasecops_cli.utilities.file_utils import ensure_dir, write_file
8
+
9
+
10
+ def build_cortex_mcp_servers(
11
+ servers: list[str],
12
+ connection_name: str,
13
+ app_database: str,
14
+ ) -> dict:
15
+ """Build an MCP server config dict for selected servers, prompting for credentials.
16
+
17
+ Returns a dict suitable for {"mcpServers": ...} JSON output.
18
+ """
19
+ server_config = {}
20
+
21
+ for server in servers:
22
+ if server == "datasecops-framework":
23
+ server_config["datasecops-framework"] = {
24
+ "type": "stdio",
25
+ "command": "datasecops-mcp",
26
+ "args": ["--connection-name", connection_name, "--app-database", app_database],
27
+ }
28
+
29
+ elif server == "GitHub":
30
+ token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
31
+ if token:
32
+ server_config["github"] = {
33
+ "type": "stdio",
34
+ "command": "npx",
35
+ "args": ["-y", "@modelcontextprotocol/server-github"],
36
+ "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": token},
37
+ }
38
+
39
+ elif server == "Azure DevOps":
40
+ org = get_input_string("Enter your Azure DevOps org URL (e.g. https://dev.azure.com/myorg): ")
41
+ pat = get_input_string("Enter your Azure DevOps PAT (or press Enter to skip): ", allow_empty=True)
42
+ if pat:
43
+ server_config["azure-devops"] = {
44
+ "type": "stdio",
45
+ "command": "npx",
46
+ "args": ["-y", "@tiberriver256/mcp-server-azure-devops"],
47
+ "env": {
48
+ "AZURE_DEVOPS_ORG_URL": org,
49
+ "AZURE_DEVOPS_AUTH_METHOD": "pat",
50
+ "AZURE_DEVOPS_PAT": pat,
51
+ },
52
+ }
53
+
54
+ elif server == "dbt":
55
+ server_config["dbt"] = {
56
+ "type": "stdio",
57
+ "command": "uvx",
58
+ "args": ["dbt-mcp"],
59
+ }
60
+
61
+ return server_config
62
+
63
+
64
+ def write_cortex_mcp_config(
65
+ mcp_path: Path,
66
+ servers: list[str],
67
+ connection_name: str,
68
+ app_database: str,
69
+ ) -> None:
70
+ """Build and write .snowflake/cortex/mcp.json for Cortex Code."""
71
+ server_config = build_cortex_mcp_servers(servers, connection_name, app_database)
72
+ ensure_dir(mcp_path.parent)
73
+ write_file(mcp_path, json.dumps({"mcpServers": server_config}, indent=2))
74
+ success_line(f"MCP config written to {mcp_path}")
@@ -79,6 +79,8 @@ class MCPSnowflakeService:
79
79
  "get_source_definitions",
80
80
  "get_access_groups",
81
81
  "get_support_contacts",
82
+ "get_sqlfluff_config_file",
83
+ "get_documentation",
82
84
  })
83
85
 
84
86
  def _call_procedure(self, proc_name: str, *args) -> Any:
@@ -118,6 +120,12 @@ class MCPSnowflakeService:
118
120
  def get_support_contacts(self) -> list:
119
121
  return self._call_procedure("get_support_contacts") or []
120
122
 
123
+ def get_sqlfluff_config_file(self) -> str:
124
+ return self._call_procedure("get_sqlfluff_config_file") or ""
125
+
126
+ def get_documentation(self) -> list:
127
+ return self._call_procedure("get_documentation") or []
128
+
121
129
  def close(self):
122
130
  if self._conn:
123
131
  self._conn.close()
@@ -311,6 +311,36 @@ def get_support_contacts() -> str:
311
311
  return json.dumps(raw, indent=2)
312
312
 
313
313
 
314
+ @mcp.tool()
315
+ def get_sqlfluff_config() -> str:
316
+ """Get the rendered SQLFluff configuration file (.sqlfluff INI format).
317
+
318
+ Returns the fully rendered .sqlfluff config file content as an INI string.
319
+ This is the actual config that should be written to the project's .sqlfluff file.
320
+ Use this when you need the exact linting configuration for the project.
321
+ """
322
+ sf = get_snowflake_service()
323
+ raw = sf.get_sqlfluff_config_file()
324
+ if not raw:
325
+ return "No rendered SQLFluff config found in native app"
326
+ return raw
327
+
328
+
329
+ @mcp.tool()
330
+ def get_documentation() -> str:
331
+ """Get all documentation topics from the framework.
332
+
333
+ Returns documentation topics with their content, covering areas like
334
+ model development, object development, data governance, and deployment.
335
+ Use this to understand framework conventions and best practices.
336
+ """
337
+ sf = get_snowflake_service()
338
+ raw = sf.get_documentation()
339
+ if not raw:
340
+ return "No documentation found in native app"
341
+ return json.dumps(raw, indent=2)
342
+
343
+
314
344
  # --- Git / Source Control Tools ---
315
345
 
316
346
 
@@ -412,6 +442,7 @@ def lint_sql(file_path: str) -> str:
412
442
  from pathlib import Path
413
443
 
414
444
  project_dir = _find_project_dir()
445
+ _ensure_dbt_packages(project_dir)
415
446
  target = Path(file_path)
416
447
  if not target.is_absolute():
417
448
  target = project_dir / target
@@ -453,6 +484,7 @@ def fix_sql(file_path: str) -> str:
453
484
  from pathlib import Path
454
485
 
455
486
  project_dir = _find_project_dir()
487
+ _ensure_dbt_packages(project_dir)
456
488
  target = Path(file_path)
457
489
  if not target.is_absolute():
458
490
  target = project_dir / target
@@ -493,6 +525,7 @@ def lint_project(fix: bool = False) -> str:
493
525
  from pathlib import Path
494
526
 
495
527
  project_dir = _find_project_dir()
528
+ _ensure_dbt_packages(project_dir)
496
529
  models_dir = project_dir / "models"
497
530
  if not models_dir.exists():
498
531
  return json.dumps({"error": f"No models directory found at {models_dir}"})
@@ -546,6 +579,44 @@ def _find_project_dir() -> "Path":
546
579
  return cwd
547
580
 
548
581
 
582
+ def _ensure_dbt_packages(project_dir: "Path") -> None:
583
+ """Ensure dbt-snowflake and dbt packages are available for sqlfluff dbt templater."""
584
+ import subprocess
585
+ from pathlib import Path
586
+
587
+ # Check that dbt-snowflake Python package is installed (required by sqlfluff-templater-dbt)
588
+ try:
589
+ import dbt.adapters.snowflake # noqa: F401
590
+ except ImportError:
591
+ # Attempt to install dbt-snowflake
592
+ subprocess.run(
593
+ ["uv", "pip", "install", "dbt-snowflake"],
594
+ capture_output=True, text=True,
595
+ )
596
+
597
+ # Ensure dbt project packages are installed (refs to macros in packages)
598
+ packages_dir = project_dir / "dbt_packages"
599
+ if packages_dir.exists() and any(packages_dir.iterdir()):
600
+ return # Already installed
601
+
602
+ # Also check legacy dbt_modules directory
603
+ modules_dir = project_dir / "dbt_modules"
604
+ if modules_dir.exists() and any(modules_dir.iterdir()):
605
+ return
606
+
607
+ # Check if packages.yml exists before running deps
608
+ packages_yml = project_dir / "packages.yml"
609
+ if not packages_yml.exists():
610
+ return # No packages to install
611
+
612
+ # Run dbt deps to install packages
613
+ import shutil
614
+ for cmd in [["dbtf", "deps"], ["dbt", "deps"]]:
615
+ if shutil.which(cmd[0]):
616
+ subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
617
+ return
618
+
619
+
549
620
  def main():
550
621
  """Run the MCP server via stdio transport."""
551
622
  import argparse
@@ -1 +0,0 @@
1
- __version__ = "0.5.1"
File without changes
File without changes
File without changes