datasecops-cli 0.5.1__tar.gz → 0.5.3__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.
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/CHANGELOG.md +29 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/PKG-INFO +54 -4
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/README.md +53 -3
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/docs/getting-started.md +16 -13
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/docs/mcp-server.md +12 -2
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/mcp-servers.json +9 -1
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/pyproject.toml +1 -1
- datasecops_cli-0.5.3/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/main.py +40 -43
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/configuration.py +11 -57
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/development.py +24 -3
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/downloads.py +11 -57
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/linting_service.py +29 -1
- datasecops_cli-0.5.3/src/datasecops_cli/utilities/mcp.py +74 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_mcp/connection.py +8 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_mcp/server.py +129 -8
- datasecops_cli-0.5.1/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/.github/workflows/test.yml +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/.gitignore +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/LICENSE +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/docs/git-operations.md +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/docs/legacy.md +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/setup.ps1 +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/setup.sh +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/__init__.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_config.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_main.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_models.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_version.py +0 -0
- {datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.3] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **MCP linting tools report progress** — `lint_sql`, `fix_sql`, and `lint_project` now use MCP `Context` to send progress notifications and info messages during execution, so callers can see what stage the tool is at (project discovery, running SQLFluff, processing results).
|
|
10
|
+
- **MCP linting tools no longer block the event loop** — subprocess calls to SQLFluff are wrapped in `asyncio.to_thread` so the MCP server remains responsive while linting runs.
|
|
11
|
+
- **`_ensure_dbt_packages` skips `dbt deps` when packages exist** — `dbt deps` is only triggered when `dbt_packages/` is actually empty, but the `dbt-snowflake` Python package check always runs to ensure the dbt templater works.
|
|
12
|
+
|
|
13
|
+
## [0.5.2] - 2026-06-05
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`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.
|
|
18
|
+
- **`get_documentation` MCP tool** — exposes framework documentation topics (model development, data governance, deployment, etc.) so AI tools can reference framework conventions directly.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **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.
|
|
23
|
+
- **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.
|
|
24
|
+
- **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.
|
|
25
|
+
- **Expanded `_ALLOWED_PROCEDURES`** — added `get_sqlfluff_config_file` and `get_documentation` to the MCP connection allowlist.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **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.
|
|
30
|
+
- **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.
|
|
31
|
+
- **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/`.
|
|
32
|
+
- **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.
|
|
33
|
+
|
|
5
34
|
## [0.5.1] - 2026-06-04
|
|
6
35
|
|
|
7
36
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasecops-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
-
```
|
|
164
|
-
|
|
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
|
-
```
|
|
145
|
-
|
|
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
|
-
|
|
218
|
+
Create `.snowflake/cortex/mcp.json` in your project root:
|
|
219
219
|
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.3"
|
|
@@ -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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
"""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 =
|
|
169
|
-
|
|
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
|
-
"""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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."""
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/linting_service.py
RENAMED
|
@@ -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()
|
|
@@ -8,7 +8,7 @@ dbt packages, project profiles, and deployment settings in real-time.
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
12
12
|
|
|
13
13
|
from datasecops_mcp.connection import get_snowflake_service
|
|
14
14
|
from datasecops_cli.models.project_config import DEFAULT_BRANCH_FORMAT
|
|
@@ -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
|
|
|
@@ -399,7 +429,7 @@ def get_deployment_workflow() -> str:
|
|
|
399
429
|
|
|
400
430
|
|
|
401
431
|
@mcp.tool()
|
|
402
|
-
def lint_sql(file_path: str) -> str:
|
|
432
|
+
async def lint_sql(file_path: str, ctx: Context) -> str:
|
|
403
433
|
"""Lint a SQL file using SQLFluff with the project's configured rules.
|
|
404
434
|
|
|
405
435
|
Args:
|
|
@@ -408,10 +438,15 @@ def lint_sql(file_path: str) -> str:
|
|
|
408
438
|
Returns the linting results including rule violations, line numbers,
|
|
409
439
|
and descriptions. Uses the project's .sqlfluff config from the framework.
|
|
410
440
|
"""
|
|
441
|
+
import asyncio
|
|
411
442
|
import subprocess
|
|
412
443
|
from pathlib import Path
|
|
413
444
|
|
|
445
|
+
await ctx.report_progress(0, 3)
|
|
446
|
+
await ctx.info(f"Finding dbt project for: {file_path}")
|
|
414
447
|
project_dir = _find_project_dir()
|
|
448
|
+
_ensure_dbt_packages(project_dir)
|
|
449
|
+
|
|
415
450
|
target = Path(file_path)
|
|
416
451
|
if not target.is_absolute():
|
|
417
452
|
target = project_dir / target
|
|
@@ -419,12 +454,20 @@ def lint_sql(file_path: str) -> str:
|
|
|
419
454
|
if not target.exists():
|
|
420
455
|
return json.dumps({"error": f"File not found: {target}"})
|
|
421
456
|
|
|
457
|
+
await ctx.report_progress(1, 3)
|
|
458
|
+
await ctx.info(f"Running SQLFluff lint on {target.name}...")
|
|
459
|
+
|
|
422
460
|
cmd = ["sqlfluff", "lint", str(target), "--format", "json"]
|
|
423
461
|
config_path = project_dir / ".sqlfluff"
|
|
424
462
|
if config_path.exists():
|
|
425
463
|
cmd += ["--config", str(config_path)]
|
|
426
464
|
|
|
427
|
-
result =
|
|
465
|
+
result = await asyncio.to_thread(
|
|
466
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
await ctx.report_progress(2, 3)
|
|
470
|
+
await ctx.info("Processing lint results...")
|
|
428
471
|
|
|
429
472
|
# SQLFluff returns exit code 1 when violations are found (not an error)
|
|
430
473
|
if result.returncode > 1:
|
|
@@ -433,14 +476,17 @@ def lint_sql(file_path: str) -> str:
|
|
|
433
476
|
if result.stdout.strip():
|
|
434
477
|
try:
|
|
435
478
|
violations = json.loads(result.stdout)
|
|
479
|
+
await ctx.report_progress(3, 3)
|
|
436
480
|
return json.dumps(violations, indent=2)
|
|
437
481
|
except json.JSONDecodeError:
|
|
482
|
+
await ctx.report_progress(3, 3)
|
|
438
483
|
return result.stdout
|
|
484
|
+
await ctx.report_progress(3, 3)
|
|
439
485
|
return json.dumps({"status": "clean", "message": "No linting issues found"})
|
|
440
486
|
|
|
441
487
|
|
|
442
488
|
@mcp.tool()
|
|
443
|
-
def fix_sql(file_path: str) -> str:
|
|
489
|
+
async def fix_sql(file_path: str, ctx: Context) -> str:
|
|
444
490
|
"""Auto-fix linting issues in a SQL file using SQLFluff.
|
|
445
491
|
|
|
446
492
|
Args:
|
|
@@ -449,10 +495,15 @@ def fix_sql(file_path: str) -> str:
|
|
|
449
495
|
Applies automatic fixes using the project's .sqlfluff config.
|
|
450
496
|
Returns the fix results and what was changed.
|
|
451
497
|
"""
|
|
498
|
+
import asyncio
|
|
452
499
|
import subprocess
|
|
453
500
|
from pathlib import Path
|
|
454
501
|
|
|
502
|
+
await ctx.report_progress(0, 3)
|
|
503
|
+
await ctx.info(f"Finding dbt project for: {file_path}")
|
|
455
504
|
project_dir = _find_project_dir()
|
|
505
|
+
_ensure_dbt_packages(project_dir)
|
|
506
|
+
|
|
456
507
|
target = Path(file_path)
|
|
457
508
|
if not target.is_absolute():
|
|
458
509
|
target = project_dir / target
|
|
@@ -460,12 +511,20 @@ def fix_sql(file_path: str) -> str:
|
|
|
460
511
|
if not target.exists():
|
|
461
512
|
return json.dumps({"error": f"File not found: {target}"})
|
|
462
513
|
|
|
514
|
+
await ctx.report_progress(1, 3)
|
|
515
|
+
await ctx.info(f"Running SQLFluff fix on {target.name}...")
|
|
516
|
+
|
|
463
517
|
cmd = ["sqlfluff", "fix", str(target), "--force", "--format", "json"]
|
|
464
518
|
config_path = project_dir / ".sqlfluff"
|
|
465
519
|
if config_path.exists():
|
|
466
520
|
cmd += ["--config", str(config_path)]
|
|
467
521
|
|
|
468
|
-
result =
|
|
522
|
+
result = await asyncio.to_thread(
|
|
523
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
await ctx.report_progress(2, 3)
|
|
527
|
+
await ctx.info("Processing fix results...")
|
|
469
528
|
|
|
470
529
|
if result.returncode > 1:
|
|
471
530
|
return json.dumps({"error": f"SQLFluff fix failed: {result.stderr.strip()}"})
|
|
@@ -473,14 +532,17 @@ def fix_sql(file_path: str) -> str:
|
|
|
473
532
|
if result.stdout.strip():
|
|
474
533
|
try:
|
|
475
534
|
fix_results = json.loads(result.stdout)
|
|
535
|
+
await ctx.report_progress(3, 3)
|
|
476
536
|
return json.dumps(fix_results, indent=2)
|
|
477
537
|
except json.JSONDecodeError:
|
|
538
|
+
await ctx.report_progress(3, 3)
|
|
478
539
|
return result.stdout
|
|
540
|
+
await ctx.report_progress(3, 3)
|
|
479
541
|
return json.dumps({"status": "fixed", "message": f"Fixes applied to {target.name}"})
|
|
480
542
|
|
|
481
543
|
|
|
482
544
|
@mcp.tool()
|
|
483
|
-
def lint_project(fix: bool
|
|
545
|
+
async def lint_project(fix: bool, ctx: Context) -> str:
|
|
484
546
|
"""Lint all SQL models in the dbt project.
|
|
485
547
|
|
|
486
548
|
Args:
|
|
@@ -489,15 +551,23 @@ def lint_project(fix: bool = False) -> str:
|
|
|
489
551
|
Lints the models/ directory using the project's .sqlfluff config.
|
|
490
552
|
Returns a summary of violations or fixes applied.
|
|
491
553
|
"""
|
|
554
|
+
import asyncio
|
|
492
555
|
import subprocess
|
|
493
556
|
from pathlib import Path
|
|
494
557
|
|
|
558
|
+
action = "fix" if fix else "lint"
|
|
559
|
+
|
|
560
|
+
await ctx.report_progress(0, 3)
|
|
561
|
+
await ctx.info("Finding dbt project...")
|
|
495
562
|
project_dir = _find_project_dir()
|
|
563
|
+
_ensure_dbt_packages(project_dir)
|
|
496
564
|
models_dir = project_dir / "models"
|
|
497
565
|
if not models_dir.exists():
|
|
498
566
|
return json.dumps({"error": f"No models directory found at {models_dir}"})
|
|
499
567
|
|
|
500
|
-
|
|
568
|
+
await ctx.report_progress(1, 3)
|
|
569
|
+
await ctx.info(f"Running SQLFluff {action} on models/...")
|
|
570
|
+
|
|
501
571
|
cmd = ["sqlfluff", action, str(models_dir), "--format", "json"]
|
|
502
572
|
if fix:
|
|
503
573
|
cmd.append("--force")
|
|
@@ -506,7 +576,12 @@ def lint_project(fix: bool = False) -> str:
|
|
|
506
576
|
if config_path.exists():
|
|
507
577
|
cmd += ["--config", str(config_path)]
|
|
508
578
|
|
|
509
|
-
result =
|
|
579
|
+
result = await asyncio.to_thread(
|
|
580
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
await ctx.report_progress(2, 3)
|
|
584
|
+
await ctx.info("Processing results...")
|
|
510
585
|
|
|
511
586
|
if result.returncode > 1:
|
|
512
587
|
return json.dumps({"error": f"SQLFluff {action} failed: {result.stderr.strip()}"})
|
|
@@ -527,10 +602,14 @@ def lint_project(fix: bool = False) -> str:
|
|
|
527
602
|
"truncated": True,
|
|
528
603
|
"message": f"Showing first 5 of {len(output)} files",
|
|
529
604
|
}
|
|
605
|
+
await ctx.report_progress(3, 3)
|
|
530
606
|
return json.dumps(summary, indent=2)
|
|
607
|
+
await ctx.report_progress(3, 3)
|
|
531
608
|
return json.dumps(output, indent=2)
|
|
532
609
|
except json.JSONDecodeError:
|
|
610
|
+
await ctx.report_progress(3, 3)
|
|
533
611
|
return result.stdout
|
|
612
|
+
await ctx.report_progress(3, 3)
|
|
534
613
|
return json.dumps({"status": "clean", "action": action, "message": "No issues found"})
|
|
535
614
|
|
|
536
615
|
|
|
@@ -546,6 +625,48 @@ def _find_project_dir() -> "Path":
|
|
|
546
625
|
return cwd
|
|
547
626
|
|
|
548
627
|
|
|
628
|
+
def _ensure_dbt_packages(project_dir: "Path") -> None:
|
|
629
|
+
"""Ensure dbt-snowflake and dbt packages are available for sqlfluff dbt templater.
|
|
630
|
+
|
|
631
|
+
Skips expensive dbt deps if packages are already present, but always
|
|
632
|
+
ensures the dbt-snowflake Python package is importable.
|
|
633
|
+
"""
|
|
634
|
+
import subprocess
|
|
635
|
+
from pathlib import Path
|
|
636
|
+
|
|
637
|
+
# Always ensure dbt-snowflake Python package is available (required by sqlfluff-templater-dbt)
|
|
638
|
+
try:
|
|
639
|
+
import dbt.adapters.snowflake # noqa: F401
|
|
640
|
+
except ImportError:
|
|
641
|
+
subprocess.run(
|
|
642
|
+
["uv", "pip", "install", "dbt-snowflake"],
|
|
643
|
+
capture_output=True, text=True,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Ensure dbt project packages are installed (refs to macros in packages)
|
|
647
|
+
packages_dir = project_dir / "dbt_packages"
|
|
648
|
+
if packages_dir.exists() and any(packages_dir.iterdir()):
|
|
649
|
+
return # Already installed
|
|
650
|
+
|
|
651
|
+
# Also check legacy dbt_modules directory
|
|
652
|
+
modules_dir = project_dir / "dbt_modules"
|
|
653
|
+
if modules_dir.exists() and any(modules_dir.iterdir()):
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
# Check if packages.yml exists before running deps
|
|
657
|
+
packages_yml = project_dir / "packages.yml"
|
|
658
|
+
if not packages_yml.exists():
|
|
659
|
+
return # No packages to install
|
|
660
|
+
|
|
661
|
+
# Packages are missing — run dbt deps to install them
|
|
662
|
+
import shutil
|
|
663
|
+
|
|
664
|
+
for cmd in [["dbtf", "deps"], ["dbt", "deps"]]:
|
|
665
|
+
if shutil.which(cmd[0]):
|
|
666
|
+
subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
|
|
667
|
+
return
|
|
668
|
+
|
|
669
|
+
|
|
549
670
|
def main():
|
|
550
671
|
"""Run the MCP server via stdio transport."""
|
|
551
672
|
import argparse
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.1 → datasecops_cli-0.5.3}/src/datasecops_cli/services/upstream_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|