detectkit 0.61.0__tar.gz → 0.62.0__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.
- {detectkit-0.61.0/detectkit.egg-info → detectkit-0.62.0}/PKG-INFO +18 -1
- {detectkit-0.61.0 → detectkit-0.62.0}/README.md +14 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/__init__.py +1 -1
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/cli.md +35 -0
- detectkit-0.62.0/detectkit/cli/commands/mcp.py +31 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/main.py +56 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/profile.py +22 -3
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/_sql_manager.py +9 -2
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/clickhouse_manager.py +11 -5
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/duckdb_manager.py +25 -1
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/mysql_manager.py +5 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/postgres_manager.py +5 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/loaders/metric_loader.py +7 -1
- detectkit-0.62.0/detectkit/mcp/__init__.py +47 -0
- detectkit-0.62.0/detectkit/mcp/context.py +329 -0
- detectkit-0.62.0/detectkit/mcp/errors.py +27 -0
- detectkit-0.62.0/detectkit/mcp/serialize.py +90 -0
- detectkit-0.62.0/detectkit/mcp/server.py +263 -0
- detectkit-0.62.0/detectkit/mcp/tools.py +502 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/utils/datetime_utils.py +11 -2
- {detectkit-0.61.0 → detectkit-0.62.0/detectkit.egg-info}/PKG-INFO +18 -1
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit.egg-info/SOURCES.txt +7 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit.egg-info/requires.txt +4 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/pyproject.toml +7 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/LICENSE +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/MANIFEST.in +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/discord.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/googlechat.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/ntfy.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/teams.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/axis_spec.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/assets/claude/skills/dtk-tune/SKILL.md +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/osi.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/ui.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/metric_io.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/core/models.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/autoreg.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/loaders/errors.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/errors.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/exporter.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/importer.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/osi_model.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/semantic/query_gen.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/assets/tune.js +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/html.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/tuning/server.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/assets/ui.js +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/html.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/jobs.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/metric_files.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/overview.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/ui/server.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/requirements.txt +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/setup.cfg +0 -0
- {detectkit-0.61.0 → detectkit-0.62.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.62.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -54,6 +54,8 @@ Requires-Dist: prophet>=1.1.0; extra == "advanced-detectors"
|
|
|
54
54
|
Requires-Dist: timesfm>=0.1.0; extra == "advanced-detectors"
|
|
55
55
|
Provides-Extra: osi
|
|
56
56
|
Requires-Dist: sqlglot>=20.0; extra == "osi"
|
|
57
|
+
Provides-Extra: mcp
|
|
58
|
+
Requires-Dist: mcp<2,>=1.27; extra == "mcp"
|
|
57
59
|
Provides-Extra: all
|
|
58
60
|
Requires-Dist: clickhouse-driver>=0.2.0; extra == "all"
|
|
59
61
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
@@ -62,6 +64,7 @@ Requires-Dist: duckdb>=1.1; extra == "all"
|
|
|
62
64
|
Requires-Dist: prophet>=1.1.0; extra == "all"
|
|
63
65
|
Requires-Dist: timesfm>=0.1.0; extra == "all"
|
|
64
66
|
Requires-Dist: sqlglot>=20.0; extra == "all"
|
|
67
|
+
Requires-Dist: mcp<2,>=1.27; extra == "all"
|
|
65
68
|
Provides-Extra: dev
|
|
66
69
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
67
70
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -199,6 +202,18 @@ for r in results:
|
|
|
199
202
|
print(f"Anomaly at {r.timestamp}: {r.value}")
|
|
200
203
|
```
|
|
201
204
|
|
|
205
|
+
## AI & automation
|
|
206
|
+
|
|
207
|
+
- **[MCP server](docs/guides/mcp.md)** (`dtk mcp`) — a read-only [Model Context
|
|
208
|
+
Protocol](https://modelcontextprotocol.io) server so an AI assistant (Claude
|
|
209
|
+
Code, Claude Desktop, any MCP client) can query a project's metric configs,
|
|
210
|
+
loaded data, detector results, replayed alert history and autotune runs
|
|
211
|
+
directly — no write access to the pipeline.
|
|
212
|
+
- **[GitHub Action](docs/guides/github-action.md)** — a composite action that
|
|
213
|
+
runs `dtk run`/`autotune`/`clean` as a CI check or scheduled job and gates
|
|
214
|
+
on detectkit's own exit-code contract, with the `--json` run summary wired
|
|
215
|
+
to step outputs.
|
|
216
|
+
|
|
202
217
|
## Documentation
|
|
203
218
|
|
|
204
219
|
- **[dtk.pipelab.dev](https://dtk.pipelab.dev)** — full docs site, guides, reference, and a live in-browser playground
|
|
@@ -207,6 +222,8 @@ for r in results:
|
|
|
207
222
|
- [Detectors Guide](docs/guides/detectors.md) — choosing the right detector
|
|
208
223
|
- [Alerting Guide](docs/guides/alerting.md) — channels, mentions, cooldown, recovery
|
|
209
224
|
- [Reading Alerts](docs/guides/reading-alerts.md) — what a received alert means (for stakeholders)
|
|
225
|
+
- [MCP Guide](docs/guides/mcp.md) — read-only AI-assistant access with `dtk mcp`
|
|
226
|
+
- [GitHub Action Guide](docs/guides/github-action.md) — run the pipeline in CI
|
|
210
227
|
- [CLI Reference](docs/reference/cli.md) — command-line documentation
|
|
211
228
|
- [Examples](docs/examples/) — real-world monitoring scenarios
|
|
212
229
|
- [Changelog](CHANGELOG.md) — version history
|
|
@@ -120,6 +120,18 @@ for r in results:
|
|
|
120
120
|
print(f"Anomaly at {r.timestamp}: {r.value}")
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
## AI & automation
|
|
124
|
+
|
|
125
|
+
- **[MCP server](docs/guides/mcp.md)** (`dtk mcp`) — a read-only [Model Context
|
|
126
|
+
Protocol](https://modelcontextprotocol.io) server so an AI assistant (Claude
|
|
127
|
+
Code, Claude Desktop, any MCP client) can query a project's metric configs,
|
|
128
|
+
loaded data, detector results, replayed alert history and autotune runs
|
|
129
|
+
directly — no write access to the pipeline.
|
|
130
|
+
- **[GitHub Action](docs/guides/github-action.md)** — a composite action that
|
|
131
|
+
runs `dtk run`/`autotune`/`clean` as a CI check or scheduled job and gates
|
|
132
|
+
on detectkit's own exit-code contract, with the `--json` run summary wired
|
|
133
|
+
to step outputs.
|
|
134
|
+
|
|
123
135
|
## Documentation
|
|
124
136
|
|
|
125
137
|
- **[dtk.pipelab.dev](https://dtk.pipelab.dev)** — full docs site, guides, reference, and a live in-browser playground
|
|
@@ -128,6 +140,8 @@ for r in results:
|
|
|
128
140
|
- [Detectors Guide](docs/guides/detectors.md) — choosing the right detector
|
|
129
141
|
- [Alerting Guide](docs/guides/alerting.md) — channels, mentions, cooldown, recovery
|
|
130
142
|
- [Reading Alerts](docs/guides/reading-alerts.md) — what a received alert means (for stakeholders)
|
|
143
|
+
- [MCP Guide](docs/guides/mcp.md) — read-only AI-assistant access with `dtk mcp`
|
|
144
|
+
- [GitHub Action Guide](docs/guides/github-action.md) — run the pipeline in CI
|
|
131
145
|
- [CLI Reference](docs/reference/cli.md) — command-line documentation
|
|
132
146
|
- [Examples](docs/examples/) — real-world monitoring scenarios
|
|
133
147
|
- [Changelog](CHANGELOG.md) — version history
|
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.62.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -18,6 +18,7 @@ Run all commands from a project directory (the one containing
|
|
|
18
18
|
| `dtk clean --select <sel>` | Prune internal data that no longer matches the config |
|
|
19
19
|
| `dtk osi import <model>` | Scaffold a native metric from an OSI semantic-model metric (see "OSI interop") |
|
|
20
20
|
| `dtk osi export` | Publish metrics into an OSI fragment (config in `custom_extensions[detectkit]`) |
|
|
21
|
+
| `dtk mcp` | Serve a **read-only** MCP server for an AI assistant (metrics, data, alerts, autotune history — no writes) |
|
|
21
22
|
| `dtk --version` | Show installed detectkit version |
|
|
22
23
|
|
|
23
24
|
## Selectors (`--select` / `-s`)
|
|
@@ -376,6 +377,40 @@ The `dtk ui` metric Builder's **From OSI** sub-tab does the import
|
|
|
376
377
|
interactively (paste a model, pick a metric/target, Compile) through the
|
|
377
378
|
same code path — see "Managing metrics from the UI" above.
|
|
378
379
|
|
|
380
|
+
## `dtk mcp`
|
|
381
|
+
|
|
382
|
+
A **read-only** [Model Context Protocol](https://modelcontextprotocol.io)
|
|
383
|
+
server, exposing a project to *another* AI assistant (an MCP client — Claude
|
|
384
|
+
Code, Claude Desktop, an IDE extension) rather than being one itself. It is a
|
|
385
|
+
separate, additive command (needs `pip install 'detectkit[mcp]'`) that
|
|
386
|
+
contains **zero write paths**: no config edits, no label writes, no pipeline
|
|
387
|
+
runs, no schema creation. Ten tools: `list_metrics`, `get_metric`,
|
|
388
|
+
`get_metric_status`, `get_project_status`, `query_datapoints`,
|
|
389
|
+
`query_detections`, `replay_alerts` (reconstructs the alert timeline the same
|
|
390
|
+
way `dtk run --report` does — never reads `_dtk_alert_states`, never
|
|
391
|
+
dispatches), `get_autotune_history`, `get_incidents`, `get_server_info`.
|
|
392
|
+
Channel connection details/secrets in `profiles.yml` are never exposed —
|
|
393
|
+
`get_metric` lists alerting channels by name only.
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
dtk mcp --project-dir /abs/path/to/project [--select <sel>] [--profile NAME]
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
- `--project-dir` — resolved before `$DETECTKIT_PROJECT_DIR` before the
|
|
400
|
+
current directory (an MCP client passes no cwd, so at least one must
|
|
401
|
+
resolve; each mechanism searches upward for `detectkit_project.yml`).
|
|
402
|
+
- `--select` — scopes the whole session: every tool that names a metric
|
|
403
|
+
refuses one outside this set (an access-control boundary, not just a
|
|
404
|
+
default).
|
|
405
|
+
|
|
406
|
+
This is a **different thing** from the generic database MCP CLAUDE.md
|
|
407
|
+
recommends you (the assistant working in this repo) optionally connect to for
|
|
408
|
+
your own read access to the project's DB — `dtk mcp` answers in detectkit's
|
|
409
|
+
own terms (replayed alert decisions, detector identity, labeled incidents),
|
|
410
|
+
not raw SQL, and it's for exposing *this* project to a *separate* assistant
|
|
411
|
+
session/client (e.g. Claude Desktop), not something you need to launch or
|
|
412
|
+
connect to yourself. `dtk mcp --help` has the full flag reference.
|
|
413
|
+
|
|
379
414
|
## Common workflows
|
|
380
415
|
|
|
381
416
|
```bash
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Implementation of 'dtk mcp' — the read-only Model Context Protocol server.
|
|
2
|
+
|
|
3
|
+
Isolated from the load/detect/alert pipeline: this module imports only
|
|
4
|
+
``click`` at load time. Everything else — :mod:`detectkit.mcp.server` and the
|
|
5
|
+
optional ``mcp`` SDK it lazily guards — is imported inside :func:`run_mcp`'s
|
|
6
|
+
body, so `import detectkit.cli.commands.mcp` never pulls in the SDK either.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_mcp(*, project_dir: str | None, select: str, profile: str | None) -> None:
|
|
15
|
+
"""Resolve the project and serve the read-only MCP server over stdio.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
project_dir: Explicit project directory (searched upward for
|
|
19
|
+
``detectkit_project.yml``). Falls back to ``$DETECTKIT_PROJECT_DIR``,
|
|
20
|
+
then to searching upward from the current directory.
|
|
21
|
+
select: Selector scoping which metrics the server exposes.
|
|
22
|
+
profile: Profile name to use (defaults to the project's
|
|
23
|
+
``default_profile``).
|
|
24
|
+
"""
|
|
25
|
+
from detectkit.mcp.errors import McpError
|
|
26
|
+
from detectkit.mcp.server import run_server
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
run_server(project_dir=project_dir, selector=select, profile=profile)
|
|
30
|
+
except McpError as exc:
|
|
31
|
+
raise click.ClickException(str(exc)) from exc
|
|
@@ -592,5 +592,61 @@ from detectkit.cli.commands.osi import osi as _osi_group # noqa: E402
|
|
|
592
592
|
cli.add_command(_osi_group)
|
|
593
593
|
|
|
594
594
|
|
|
595
|
+
# MCP (Model Context Protocol) server — a self-contained, READ-ONLY command
|
|
596
|
+
# (`dtk mcp`), unlike `dtk osi` a single command rather than a group. Isolated
|
|
597
|
+
# from the load/detect/alert pipeline: nothing in load/detect/alert imports
|
|
598
|
+
# detectkit.mcp, and this module only imports click at load time — the mcp SDK
|
|
599
|
+
# (the optional `[mcp]` extra) and detectkit.mcp.server are imported lazily,
|
|
600
|
+
# inside the command body.
|
|
601
|
+
@cli.command()
|
|
602
|
+
@click.option(
|
|
603
|
+
"--project-dir",
|
|
604
|
+
default=None,
|
|
605
|
+
help=(
|
|
606
|
+
"Project directory (searched upward for detectkit_project.yml). "
|
|
607
|
+
"Default: $DETECTKIT_PROJECT_DIR, else search upward from the current "
|
|
608
|
+
"directory. An MCP client passes no cwd, so at least one of these must "
|
|
609
|
+
"resolve."
|
|
610
|
+
),
|
|
611
|
+
)
|
|
612
|
+
@click.option(
|
|
613
|
+
"--select",
|
|
614
|
+
"-s",
|
|
615
|
+
default="*",
|
|
616
|
+
show_default=True,
|
|
617
|
+
help="Selector scoping which metrics the server exposes (metric name, path, or tag)",
|
|
618
|
+
)
|
|
619
|
+
@click.option(
|
|
620
|
+
"--profile",
|
|
621
|
+
help="Profile to use (default: from project config)",
|
|
622
|
+
)
|
|
623
|
+
def mcp(project_dir: str | None, select: str, profile: str | None) -> None:
|
|
624
|
+
"""
|
|
625
|
+
Serve a read-only Model Context Protocol (MCP) server over stdio.
|
|
626
|
+
|
|
627
|
+
Exposes a detectkit project to an MCP client (an IDE assistant, a desktop
|
|
628
|
+
app, a cloud agent): metric configs, loaded datapoints, detector results,
|
|
629
|
+
replayed alert history, autotune runs, and labeled incidents. Nothing in
|
|
630
|
+
this server writes to the database or the filesystem — no config edits,
|
|
631
|
+
no label writes, no pipeline runs, no schema creation. It never calls
|
|
632
|
+
`ensure_tables()`; a project with no data yet answers tool calls with a
|
|
633
|
+
clear "run `dtk run` first" error instead of creating tables.
|
|
634
|
+
|
|
635
|
+
`--select` scopes the whole session: every tool that names a metric
|
|
636
|
+
refuses one outside this set, and list/status tools intersect their own
|
|
637
|
+
selector with it. Needs the `[mcp]` extra: pip install 'detectkit[mcp]'.
|
|
638
|
+
|
|
639
|
+
Examples:
|
|
640
|
+
# Expose every metric in the project the current directory is inside
|
|
641
|
+
dtk mcp
|
|
642
|
+
|
|
643
|
+
# Scope to one tag, from a project the client doesn't cd into
|
|
644
|
+
dtk mcp --project-dir /opt/monitoring --select "tag:critical"
|
|
645
|
+
"""
|
|
646
|
+
from detectkit.cli.commands.mcp import run_mcp
|
|
647
|
+
|
|
648
|
+
run_mcp(project_dir=project_dir, select=select, profile=profile)
|
|
649
|
+
|
|
650
|
+
|
|
595
651
|
if __name__ == "__main__":
|
|
596
652
|
cli()
|
|
@@ -176,10 +176,19 @@ class ProfileConfig(BaseModel):
|
|
|
176
176
|
else:
|
|
177
177
|
raise ValueError(f"Unsupported database type: {self.type}")
|
|
178
178
|
|
|
179
|
-
def create_manager(self) -> BaseDatabaseManager:
|
|
179
|
+
def create_manager(self, ensure_locations: bool = True) -> BaseDatabaseManager:
|
|
180
180
|
"""
|
|
181
181
|
Create database manager from profile configuration.
|
|
182
182
|
|
|
183
|
+
Args:
|
|
184
|
+
ensure_locations: When False, skip creating the internal/data
|
|
185
|
+
database(s)/schema(s) as a side effect of connecting — a
|
|
186
|
+
strict read-only probe. For DuckDB this additionally forces
|
|
187
|
+
a read-only attach (see :class:`DuckDBDatabaseManager`), so
|
|
188
|
+
the connect itself can never create a missing state file.
|
|
189
|
+
Used by the read-only MCP server, which must never run DDL
|
|
190
|
+
at startup.
|
|
191
|
+
|
|
183
192
|
Returns:
|
|
184
193
|
Database manager instance
|
|
185
194
|
|
|
@@ -201,6 +210,7 @@ class ProfileConfig(BaseModel):
|
|
|
201
210
|
internal_database=self.get_internal_location(),
|
|
202
211
|
data_database=self.get_data_location(),
|
|
203
212
|
settings=self.settings,
|
|
213
|
+
ensure_locations=ensure_locations,
|
|
204
214
|
)
|
|
205
215
|
elif self.type == "postgres":
|
|
206
216
|
from detectkit.database.postgres_manager import PostgresDatabaseManager
|
|
@@ -220,6 +230,7 @@ class ProfileConfig(BaseModel):
|
|
|
220
230
|
internal_schema=self.get_internal_location(),
|
|
221
231
|
data_schema=self.get_data_location(),
|
|
222
232
|
settings=self.settings,
|
|
233
|
+
ensure_locations=ensure_locations,
|
|
223
234
|
)
|
|
224
235
|
elif self.type in ("mysql", "mariadb"):
|
|
225
236
|
# MariaDB is served by the same manager; it detects the vendor at
|
|
@@ -236,6 +247,7 @@ class ProfileConfig(BaseModel):
|
|
|
236
247
|
internal_database=self.get_internal_location(),
|
|
237
248
|
data_database=self.get_data_location(),
|
|
238
249
|
settings=self.settings,
|
|
250
|
+
ensure_locations=ensure_locations,
|
|
239
251
|
)
|
|
240
252
|
elif self.type == "duckdb":
|
|
241
253
|
# In-process, file-backed database: host/port/user/password/database
|
|
@@ -256,6 +268,7 @@ class ProfileConfig(BaseModel):
|
|
|
256
268
|
data_schema=self.get_data_location(),
|
|
257
269
|
read_only=self.read_only,
|
|
258
270
|
settings=self.settings,
|
|
271
|
+
ensure_locations=ensure_locations,
|
|
259
272
|
)
|
|
260
273
|
else:
|
|
261
274
|
raise ValueError(f"Unsupported database type: {self.type}")
|
|
@@ -351,18 +364,24 @@ class ProfilesConfig(BaseModel):
|
|
|
351
364
|
|
|
352
365
|
return self.profiles[name]
|
|
353
366
|
|
|
354
|
-
def create_manager(
|
|
367
|
+
def create_manager(
|
|
368
|
+
self, profile_name: str | None = None, ensure_locations: bool = True
|
|
369
|
+
) -> BaseDatabaseManager:
|
|
355
370
|
"""
|
|
356
371
|
Create database manager for a profile.
|
|
357
372
|
|
|
358
373
|
Args:
|
|
359
374
|
profile_name: Profile name (if None, use default)
|
|
375
|
+
ensure_locations: Passed through to
|
|
376
|
+
:meth:`ProfileConfig.create_manager` — False for a strict
|
|
377
|
+
read-only probe that never runs DDL as a side effect of
|
|
378
|
+
connecting.
|
|
360
379
|
|
|
361
380
|
Returns:
|
|
362
381
|
Database manager instance
|
|
363
382
|
"""
|
|
364
383
|
profile = self.get_profile(profile_name)
|
|
365
|
-
return profile.create_manager()
|
|
384
|
+
return profile.create_manager(ensure_locations=ensure_locations)
|
|
366
385
|
|
|
367
386
|
def get_alert_channel_config(self, channel_name: str) -> dict[str, Any]:
|
|
368
387
|
"""
|
|
@@ -51,6 +51,11 @@ class SQLDatabaseManager(BaseDatabaseManager):
|
|
|
51
51
|
database to connect to, inside which the schemas live); optional for
|
|
52
52
|
MySQL (which addresses tables as ``database.table``).
|
|
53
53
|
settings: Extra driver-specific connection options.
|
|
54
|
+
ensure_locations: When False, skip the ``_ensure_locations()`` call
|
|
55
|
+
that otherwise runs DDL (``CREATE SCHEMA``/``CREATE DATABASE``)
|
|
56
|
+
as a side effect of connecting — a strict read-only probe (used
|
|
57
|
+
by the read-only MCP server, which must never run DDL at
|
|
58
|
+
startup).
|
|
54
59
|
"""
|
|
55
60
|
|
|
56
61
|
#: Maps the canonical column kind to a native SQL type. Subclasses override.
|
|
@@ -69,6 +74,7 @@ class SQLDatabaseManager(BaseDatabaseManager):
|
|
|
69
74
|
data_location: str = "public",
|
|
70
75
|
database: str | None = None,
|
|
71
76
|
settings: dict[str, Any] | None = None,
|
|
77
|
+
ensure_locations: bool = True,
|
|
72
78
|
) -> None:
|
|
73
79
|
self._host = host
|
|
74
80
|
self._port = port
|
|
@@ -83,7 +89,8 @@ class SQLDatabaseManager(BaseDatabaseManager):
|
|
|
83
89
|
self._table_meta: dict[str, tuple[list[str], str | None]] = {}
|
|
84
90
|
|
|
85
91
|
self._conn: Any = self._connect()
|
|
86
|
-
|
|
92
|
+
if ensure_locations:
|
|
93
|
+
self._ensure_locations()
|
|
87
94
|
|
|
88
95
|
# ── dialect hooks (subclasses override) ─────────────────────────────────
|
|
89
96
|
|
|
@@ -153,7 +160,7 @@ class SQLDatabaseManager(BaseDatabaseManager):
|
|
|
153
160
|
if col.default is None:
|
|
154
161
|
return ""
|
|
155
162
|
d = col.default
|
|
156
|
-
if isinstance(d,
|
|
163
|
+
if isinstance(d, int | float):
|
|
157
164
|
return f" DEFAULT {d}"
|
|
158
165
|
if isinstance(d, str) and d.lstrip("-").isdigit():
|
|
159
166
|
return f" DEFAULT {d}"
|
|
@@ -36,6 +36,10 @@ class ClickHouseDatabaseManager(BaseDatabaseManager):
|
|
|
36
36
|
internal_database: Database for internal tables (_dtk_*)
|
|
37
37
|
data_database: Database for user data tables
|
|
38
38
|
settings: Optional ClickHouse settings
|
|
39
|
+
ensure_locations: When False, skip creating the internal/data
|
|
40
|
+
databases as a side effect of connecting — a strict read-only
|
|
41
|
+
probe (used by the read-only MCP server, which must never run
|
|
42
|
+
DDL at startup).
|
|
39
43
|
"""
|
|
40
44
|
|
|
41
45
|
def __init__(
|
|
@@ -47,6 +51,7 @@ class ClickHouseDatabaseManager(BaseDatabaseManager):
|
|
|
47
51
|
internal_database: str = "detectk_internal",
|
|
48
52
|
data_database: str = "default",
|
|
49
53
|
settings: dict[str, Any] | None = None,
|
|
54
|
+
ensure_locations: bool = True,
|
|
50
55
|
):
|
|
51
56
|
"""Initialize ClickHouse manager."""
|
|
52
57
|
if not CLICKHOUSE_AVAILABLE:
|
|
@@ -67,8 +72,9 @@ class ClickHouseDatabaseManager(BaseDatabaseManager):
|
|
|
67
72
|
settings=settings or {},
|
|
68
73
|
)
|
|
69
74
|
|
|
70
|
-
# Ensure databases exist
|
|
71
|
-
|
|
75
|
+
# Ensure databases exist (skipped for a strict read-only probe).
|
|
76
|
+
if ensure_locations:
|
|
77
|
+
self._ensure_databases()
|
|
72
78
|
|
|
73
79
|
def _ensure_databases(self) -> None:
|
|
74
80
|
"""Create internal and data databases if they don't exist."""
|
|
@@ -154,7 +160,7 @@ class ClickHouseDatabaseManager(BaseDatabaseManager):
|
|
|
154
160
|
"""Format default value for SQL."""
|
|
155
161
|
if isinstance(value, str):
|
|
156
162
|
return f"'{value}'"
|
|
157
|
-
elif isinstance(value,
|
|
163
|
+
elif isinstance(value, int | float):
|
|
158
164
|
return str(value)
|
|
159
165
|
elif value is None:
|
|
160
166
|
return "NULL"
|
|
@@ -228,12 +234,12 @@ class ClickHouseDatabaseManager(BaseDatabaseManager):
|
|
|
228
234
|
value = data[col_name][i]
|
|
229
235
|
|
|
230
236
|
# Convert numpy types to Python types
|
|
231
|
-
if isinstance(value,
|
|
237
|
+
if isinstance(value, np.datetime64 | np.timedelta64):
|
|
232
238
|
# Convert numpy datetime64 to Python datetime
|
|
233
239
|
value = self._convert_numpy_datetime(value)
|
|
234
240
|
elif isinstance(value, np.ndarray):
|
|
235
241
|
value = value.tolist()
|
|
236
|
-
elif isinstance(value,
|
|
242
|
+
elif isinstance(value, np.integer | np.floating):
|
|
237
243
|
value = value.item()
|
|
238
244
|
elif value is None or (isinstance(value, float) and np.isnan(value)):
|
|
239
245
|
value = None
|
|
@@ -203,6 +203,18 @@ class DuckDBDatabaseManager(SQLDatabaseManager):
|
|
|
203
203
|
internal/data schemas already exist.
|
|
204
204
|
settings: Extra ``duckdb.connect`` ``config`` options (e.g.
|
|
205
205
|
``{"memory_limit": "512MB"}``).
|
|
206
|
+
ensure_locations: When False, skip creating the internal/data
|
|
207
|
+
schemas as a side effect of connecting (a strict read-only
|
|
208
|
+
probe — see
|
|
209
|
+
:class:`~detectkit.database._sql_manager.SQLDatabaseManager`)
|
|
210
|
+
**and** force a read-only attach regardless of ``read_only``,
|
|
211
|
+
for a real file path. Skipping schema creation alone is not
|
|
212
|
+
enough for DuckDB: a plain read-write ``duckdb.connect`` against
|
|
213
|
+
a *missing* file path creates that file as a side effect of
|
|
214
|
+
connecting, before any DDL runs — forcing ``read_only=True``
|
|
215
|
+
is what actually prevents that. ``":memory:"`` is exempted from
|
|
216
|
+
the forced read-only attach (it has no file to create, and
|
|
217
|
+
DuckDB rejects a read-only in-memory connection outright).
|
|
206
218
|
|
|
207
219
|
Raises:
|
|
208
220
|
ImportError: If the ``duckdb`` package is not installed.
|
|
@@ -224,6 +236,7 @@ class DuckDBDatabaseManager(SQLDatabaseManager):
|
|
|
224
236
|
data_schema: str = "main",
|
|
225
237
|
read_only: bool = False,
|
|
226
238
|
settings: dict[str, Any] | None = None,
|
|
239
|
+
ensure_locations: bool = True,
|
|
227
240
|
) -> None:
|
|
228
241
|
if not DUCKDB_AVAILABLE:
|
|
229
242
|
raise ImportError(
|
|
@@ -235,7 +248,17 @@ class DuckDBDatabaseManager(SQLDatabaseManager):
|
|
|
235
248
|
"path, or ':memory:' for a transient, tests/preview-only in-process "
|
|
236
249
|
"database whose state is lost between runs)."
|
|
237
250
|
)
|
|
238
|
-
|
|
251
|
+
# `ensure_locations=False` is a strict read-only PROBE (see
|
|
252
|
+
# `SQLDatabaseManager.__init__`): skipping `_ensure_locations()` is
|
|
253
|
+
# not enough on its own for DuckDB, because a plain read-write
|
|
254
|
+
# `duckdb.connect` against a MISSING file creates that file as a
|
|
255
|
+
# side effect of connecting, before any DDL runs. Force a read-only
|
|
256
|
+
# attach so the connect itself can never create the file/schemas.
|
|
257
|
+
# ":memory:" is exempted — it has no file to create in the first
|
|
258
|
+
# place, and DuckDB rejects a read-only in-memory connection outright
|
|
259
|
+
# (`CatalogException: Cannot launch in-memory database in read-only
|
|
260
|
+
# mode!`), so forcing it there would break rather than protect.
|
|
261
|
+
self._read_only = True if (not ensure_locations and path != ":memory:") else read_only
|
|
239
262
|
# Kept separately (and typed `str`, not `str | None`) from the base
|
|
240
263
|
# class's `self._database` so `_connect()` doesn't need to narrow an
|
|
241
264
|
# Optional it knows — by the ValueError check above — can't be None.
|
|
@@ -248,6 +271,7 @@ class DuckDBDatabaseManager(SQLDatabaseManager):
|
|
|
248
271
|
data_location=data_schema,
|
|
249
272
|
database=path,
|
|
250
273
|
settings=settings,
|
|
274
|
+
ensure_locations=ensure_locations,
|
|
251
275
|
)
|
|
252
276
|
|
|
253
277
|
def _connect(self) -> Any:
|
|
@@ -44,6 +44,9 @@ class MySQLDatabaseManager(SQLDatabaseManager):
|
|
|
44
44
|
data_database: Database for user data tables.
|
|
45
45
|
database: Optional default database for the connection.
|
|
46
46
|
settings: Extra ``pymysql.connect`` keyword arguments.
|
|
47
|
+
ensure_locations: When False, skip creating the internal/data
|
|
48
|
+
databases as a side effect of connecting — a strict read-only
|
|
49
|
+
probe (see :class:`~detectkit.database._sql_manager.SQLDatabaseManager`).
|
|
47
50
|
"""
|
|
48
51
|
|
|
49
52
|
_TYPE_MAP = {
|
|
@@ -72,6 +75,7 @@ class MySQLDatabaseManager(SQLDatabaseManager):
|
|
|
72
75
|
data_database: str = "analytics",
|
|
73
76
|
database: str | None = None,
|
|
74
77
|
settings: dict[str, Any] | None = None,
|
|
78
|
+
ensure_locations: bool = True,
|
|
75
79
|
) -> None:
|
|
76
80
|
if not PYMYSQL_AVAILABLE:
|
|
77
81
|
raise ImportError(
|
|
@@ -86,6 +90,7 @@ class MySQLDatabaseManager(SQLDatabaseManager):
|
|
|
86
90
|
data_location=data_database,
|
|
87
91
|
database=database,
|
|
88
92
|
settings=settings,
|
|
93
|
+
ensure_locations=ensure_locations,
|
|
89
94
|
)
|
|
90
95
|
|
|
91
96
|
def _connect(self) -> Any:
|
|
@@ -34,6 +34,9 @@ class PostgresDatabaseManager(SQLDatabaseManager):
|
|
|
34
34
|
internal_schema: Schema for internal ``_dtk_*`` tables.
|
|
35
35
|
data_schema: Schema for user data tables.
|
|
36
36
|
settings: Extra ``psycopg2.connect`` keyword arguments.
|
|
37
|
+
ensure_locations: When False, skip creating the internal/data
|
|
38
|
+
schemas as a side effect of connecting — a strict read-only
|
|
39
|
+
probe (see :class:`~detectkit.database._sql_manager.SQLDatabaseManager`).
|
|
37
40
|
"""
|
|
38
41
|
|
|
39
42
|
_TYPE_MAP = {
|
|
@@ -54,6 +57,7 @@ class PostgresDatabaseManager(SQLDatabaseManager):
|
|
|
54
57
|
internal_schema: str = "detectkit",
|
|
55
58
|
data_schema: str = "public",
|
|
56
59
|
settings: dict[str, Any] | None = None,
|
|
60
|
+
ensure_locations: bool = True,
|
|
57
61
|
) -> None:
|
|
58
62
|
if not PSYCOPG2_AVAILABLE:
|
|
59
63
|
raise ImportError(
|
|
@@ -68,6 +72,7 @@ class PostgresDatabaseManager(SQLDatabaseManager):
|
|
|
68
72
|
data_location=data_schema,
|
|
69
73
|
database=database,
|
|
70
74
|
settings=settings,
|
|
75
|
+
ensure_locations=ensure_locations,
|
|
71
76
|
)
|
|
72
77
|
|
|
73
78
|
def _connect(self) -> Any:
|
|
@@ -172,7 +172,13 @@ class MetricLoader:
|
|
|
172
172
|
# Filter by timestamp
|
|
173
173
|
row_ts = row[timestamp_col]
|
|
174
174
|
if isinstance(row_ts, datetime):
|
|
175
|
-
#
|
|
175
|
+
# Naive-UTC convention: a tz-aware timestamp from the source
|
|
176
|
+
# (DuckDB now()/TIMESTAMPTZ, PostgreSQL timestamptz) is
|
|
177
|
+
# converted — not rejected — so it compares cleanly with the
|
|
178
|
+
# naive-UTC window bounds and stores consistently downstream.
|
|
179
|
+
if row_ts.tzinfo is not None:
|
|
180
|
+
row_ts = to_naive_utc(row_ts)
|
|
181
|
+
row[timestamp_col] = row_ts
|
|
176
182
|
if row_ts >= to_date:
|
|
177
183
|
continue
|
|
178
184
|
else:
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Read-only Model Context Protocol (MCP) server for detectkit.
|
|
2
|
+
|
|
3
|
+
An **isolated, additive** layer — mirroring ``detectkit/semantic/``'s design:
|
|
4
|
+
nothing in the load/detect/alert pipeline imports it, so it can never affect
|
|
5
|
+
a running project. It powers only the ``dtk mcp`` command
|
|
6
|
+
(:mod:`detectkit.cli.commands.mcp`), which exposes a project's metrics,
|
|
7
|
+
loaded data, detector results, replayed alert history, autotune runs and
|
|
8
|
+
labeled incidents to an MCP client (an IDE assistant, a desktop app, a cloud
|
|
9
|
+
agent) over stdio.
|
|
10
|
+
|
|
11
|
+
The ``mcp`` SDK (the ``[mcp]`` extra, ``pip install 'detectkit[mcp]'``) is a
|
|
12
|
+
guarded, lazy import — see :mod:`detectkit.mcp.server` — so this package (and
|
|
13
|
+
every module in it) imports cleanly even when the extra isn't installed;
|
|
14
|
+
only building/running the actual server needs it.
|
|
15
|
+
|
|
16
|
+
Module map:
|
|
17
|
+
|
|
18
|
+
- ``errors.py`` — :class:`McpError` / :class:`McpProjectError` /
|
|
19
|
+
:class:`McpDependencyMissing`.
|
|
20
|
+
- ``context.py`` — project resolution (``--project-dir`` ->
|
|
21
|
+
``$DETECTKIT_PROJECT_DIR`` -> cwd search) and the long-lived
|
|
22
|
+
:class:`McpContext` (mirrors ``dtk run``'s build order, minus
|
|
23
|
+
``ensure_tables()`` — read-only never runs DDL).
|
|
24
|
+
- ``serialize.py`` — ISO-8601 / numpy-free JSON conversion helpers shared by
|
|
25
|
+
every tool.
|
|
26
|
+
- ``tools.py`` — the 10 read-only tool implementations, as plain functions
|
|
27
|
+
(no SDK dependency; directly unit-testable).
|
|
28
|
+
- ``server.py`` — the guarded ``FastMCP`` wiring + ``dtk mcp``'s entry point
|
|
29
|
+
(:func:`~detectkit.mcp.server.run_server`).
|
|
30
|
+
|
|
31
|
+
See :mod:`detectkit.mcp.tools` for the full tool list and the read-only
|
|
32
|
+
exclusion contract (no config/label writes, no pipeline runs, no DDL).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from detectkit.mcp.context import McpContext, build_context, resolve_project_dir
|
|
38
|
+
from detectkit.mcp.errors import McpDependencyMissing, McpError, McpProjectError
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"McpContext",
|
|
42
|
+
"McpDependencyMissing",
|
|
43
|
+
"McpError",
|
|
44
|
+
"McpProjectError",
|
|
45
|
+
"build_context",
|
|
46
|
+
"resolve_project_dir",
|
|
47
|
+
]
|