Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions rust/crates/sift_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### What's New

## [v0.5.0] - August 26, 2026

### What's New

- Added MCP tools for managing calculated channels: `list_calculated_channels`,
`list_calculated_channel_versions`, `create_calculated_channel`,
`update_calculated_channel`, `archive_calculated_channel`, and
`unarchive_calculated_channel`.
- `get_data` now serves saved calculated channels. A name in `channel_names`
with no raw-channel match resolves as an active saved calculated channel for
the asset and run; unresolvable names are reported explicitly.
- `get_data` now accepts `asset_id` as an alternative to `asset_name`; exactly
one must be set.
- Added `preview_rule`, which dry-runs a saved rule or an ad-hoc draft rule
config against a run without persisting anything.
- Added MCP tools for managing user-defined functions:
`list_user_defined_functions`, `list_user_defined_function_versions`,
`create_user_defined_function`, `update_user_defined_function`,
`archive_user_defined_function`, and `unarchive_user_defined_function`.
- `update_annotation` now requires `annotation_ids` instead of `annotation_id`,
a breaking change for existing callers; pass a one-element list for one
annotation. It updates 1 to 1000 annotations per call with per-ID failure
reporting, and its new `is_archived` parameter archives or unarchives
annotations.
- Refreshed the bundled Sift agent skill to cover the expanded MCP tool surface.

## [v0.4.4] - August 24, 2026

### What's New
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/sift_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ test-reports = ["sift_mcp/test-reports"]

[package]
name = "sift_cli"
version = "0.4.4"
version = "0.5.0"
authors.workspace = true
edition.workspace = true
categories.workspace = true
Expand Down
93 changes: 72 additions & 21 deletions rust/crates/sift_cli/assets/skills/sift/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
---
name: sift
description: >-
Use when working with Sift: ingesting or importing time-series data,
querying assets/runs/channels/users, exporting data, decimating or running
SQL over data, opening a view in the Sift Explore web app, writing code that
integrates with Sift, installing, updating, or diagnosing the Sift agent
integration, or looking up how Sift works in its product and API
documentation. Covers the Sift MCP server (started by `sift-cli mcp`), the
`sift-cli` itself, the Sift REST API over cURL, the Sift Python library
(`sift_client`), and the Sift Rust streaming library (`sift_stream`).
Use for Sift tasks: ingesting or importing time-series data, querying
assets/runs/channels/users, managing calculated channels, rules, and
user-defined functions, exporting or decimating data, running SQL over data,
opening a view in Sift Explore, writing code that integrates with Sift,
installing, updating, or diagnosing the Sift agent integration, or looking
up how Sift works in its product and API documentation. Covers the Sift MCP
server (started by `sift-cli mcp`), `sift-cli`, the Sift REST API over cURL,
the Sift Python library (`sift_client`), and the Sift Rust streaming library
(`sift_stream`).
Triggers include phrases like "import this file into Sift", "stream data to
Sift", "list assets/runs/channels", "runs I created", "runs a teammate
created", "export a run", "query Sift", "graph", "plot", "visualize", "open
in Explore", "write code to integrate with Sift", "how does X work in Sift",
"what does this endpoint do", or "look up the Sift API reference".
created", "export a run", "query Sift", "graph", "plot", "visualize", "open in
Explore", "write code to integrate with Sift", "how does X work in Sift",
"what does this endpoint do", "list calculated channels", "preview a rule",
or "look up the Sift API reference".
---

<!--
Expand Down Expand Up @@ -48,11 +50,18 @@ exists.

- **Setup:** When available, `check_for_updates` reports the installed sift-cli
version, the latest stable version, and the exact installer command. Servers
started with `--disable-update-check` omit this tool.
started with `--disable-update-check` omit this tool. `ping` is a
connectivity check; when it fails, expect every other Sift tool to fail too.
- **Discovery:** `list_assets`, `list_runs`, `list_channels`, `list_reports`,
`list_report_templates`, `list_rules`, `list_rule_versions`, `list_annotations`.
- **Derived channels:** `list_calculated_channels`,
`list_calculated_channel_versions`, `list_user_defined_functions`,
`list_user_defined_function_versions`.
- **People:** `list_users`.
- **Report detail:** `list_report_rule_summaries`.
- **Rollups:** `list_report_rule_summaries` for a report's rule progress.
- **Rule dry runs:** `preview_rule` returns the annotations a rule would create
without persisting anything. It is read-only whatever the server's access
mode, so it needs no write flag.
- **Test results:** `list_test_reports`, `list_test_steps`,
`list_test_measurements`, `count_test_steps`, `count_test_measurements`.
These, along with the `create_test_report` and `append_test_measurements`
Expand All @@ -64,8 +73,13 @@ exists.
- **Docs:** `search_docs`.
- **Writes:** `create_rule`, `update_rule`, `archive_rule`, `unarchive_rule`,
`create_annotation`, `update_annotation`, `create_report`, `update_report`,
`create_report_template`, `update_report_template`, `create_test_report`,
`append_test_measurements`, `update_asset`, `update_run`.
`create_report_template`, `update_report_template`,
`create_calculated_channel`, `update_calculated_channel`,
`archive_calculated_channel`, `unarchive_calculated_channel`,
`create_user_defined_function`, `update_user_defined_function`,
`archive_user_defined_function`, `unarchive_user_defined_function`,
`create_test_report`, `append_test_measurements`, `update_asset`,
`update_run`.

## Workflows that span tools

Expand All @@ -81,11 +95,43 @@ exists.
- **Attribute something to a person.** Resolve the person with `list_users`,
then filter another list on `created_by_user_id`. For "runs I created", pass
`me: true`. Never guess which listed user is the caller.
- **Update annotations.** `update_annotation` takes a required
`annotation_ids` list of 1 to 1000 ids and applies the same changes to every
target. Set `is_archived: true` to archive or `is_archived: false` to
unarchive; there is no separate archive tool. Check its per-id `failures`,
`not_attempted` ids, and archive outcome before reporting success; a partial
failure sets `isError`.
- **Produce numbers.** `get_data` writes a Parquet file. `sql` then queries it.
Add `upload_dataset` when the result belongs back in Sift. A successful
`get_data` does not mean every requested channel is in the file: check
`unmatched_channel_names` and `empty_channels` in the result and name any
missing channel to the user before reporting numbers derived from it.
- **Query a derived channel.** `get_data` serves saved calculated channels as
well as raw ones: name the calculated channel in `channel_names` and it is
evaluated for the requested asset and run. Confirm the name with
`list_calculated_channels` filtered on the asset first. A raw channel wins a
name it shares with a calculated channel, and `channel_regex` matches raw
channels only, so name calculated channels explicitly. When the result
carries `unresolved_calculated_channels`, the file is missing those columns:
tell the user which channels did not resolve rather than reporting the file as
complete.
- **Author or change a calculated channel.** A calculated channel is a CEL
expression plus an asset scope. `create_calculated_channel` and
`update_calculated_channel` take the expression with `$1`, `$2`, …
placeholders plus `expression_channel_references_json`, a JSON string array
mapping each placeholder to a channel. Resolve those channel names with
`list_channels` first. An update creates a new version and leaves earlier
versions intact; `list_calculated_channel_versions` shows that history.
Archiving sets an archived state, which `unarchive_calculated_channel` clears;
it is not a versioned, history-visible change.
- **Use or change a user-defined function.** Call
`list_user_defined_functions` before writing an expression that calls a UDF:
it gives the exact name, input order, and output type the expression must
match. An update creates a new version and leaves earlier ones intact, so
`list_user_defined_function_versions` gives history and pinned version ids.
Send a rename on its own. The API applies a `name` change by itself and
ignores every other field, so `update_user_defined_function` rejects `name`
combined with anything else.
- **Produce a chart.** Build a link with `explore_url`. When the user wants a
chart and numbers, do both and give the user both.
- **Answer a question about how Sift works.** Call `search_docs`. Do not answer
Expand All @@ -99,7 +145,11 @@ exists.
gated tool and the exact `sift-cli agent update --allow-create` command, and
wait for the user to widen access.
- **Evaluate rules against a run.** Find rules with `list_rules` and author rules
with `create_rule`. To reuse the same rule set across many runs, bundle
with `create_rule`. Dry-run first: `preview_rule` returns the annotations a
rule would generate for one run and persists nothing. It takes either a saved
rule (`rule_id` or `rule_name`) or a fully ad-hoc `draft_rule_config` JSON
string, so a rule need not be saved to be tested. Use `create_report` when the
evaluation should persist. To reuse the same rule set across many runs, bundle
standard rules (`is_external: false`) into a template with `create_report_template`,
then call `create_report` with `report_template_id`. For a one-off — or for
ad-hoc rules (`is_external: true`, which the API also calls "external" but
Expand Down Expand Up @@ -133,11 +183,12 @@ exists.
Several creates happen as side effects of other MCP tools — an asset is
created when `upload_dataset` names one that doesn't exist, a run is
created when a `create_report`/`create_test_report`/`upload_dataset` names
one. When the user asks for a create with no matching `create_*` tool,
look for the tool that creates it as a side effect before falling out of
MCP. If that side-effect tool is gated and blocked, that IS the block —
surface it, do not treat "no `create_asset` tool" as license to shell out
to REST/gRPC/`sift-cli import`.
one, and a tag is created when `create_annotation` includes a name that
doesn't exist (there is no `create_tag` tool). When the user asks for a create
with no matching `create_*` tool, look for the tool that creates it as a side
effect before falling out of MCP. If that side-effect tool is gated and
blocked, that IS the block — surface it, do not treat "no `create_asset` tool"
as license to shell out to REST/gRPC/`sift-cli import`.
- **Choose one profile for the session and keep it.** Never switch profiles to
recover from a failure. Surface the failure and ask the user.

Expand Down
1 change: 1 addition & 0 deletions rust/crates/sift_mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tonic.workspace = true
anyhow.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["cargo"] }
futures.workspace = true
pbjson-types.workspace = true
percent-encoding.workspace = true
prost.workspace = true
Expand Down
15 changes: 13 additions & 2 deletions rust/crates/sift_mcp/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ use crate::service::{
annotations::AnnotationService, assets::AssetService,
calculated_channels::CalculatedChannelService, channels::ChannelService, data::DataService,
docs::DocsService, ingest::IngestService, ping::PingService,
report_templates::ReportTemplateService, reports::ReportService, rules::RuleService,
runs::RunService, url::UrlService, users::UserService,
report_templates::ReportTemplateService, reports::ReportService,
rule_evaluation::RuleEvaluationService, rules::RuleService, runs::RunService, url::UrlService,
user_defined_functions::UserDefinedFunctionService, users::UserService,
};

#[derive(Clone)]
Expand All @@ -63,9 +64,11 @@ pub struct SiftMcpServer {
pub report_service: ReportService,
pub report_template_service: ReportTemplateService,
pub rule_service: RuleService,
pub rule_evaluation_service: RuleEvaluationService,
#[cfg(feature = "test-reports")]
pub test_report_service: TestReportService,
pub docs_service: DocsService,
pub user_defined_function_service: UserDefinedFunctionService,
pub user_service: UserService,

pub allow_create: bool,
Expand Down Expand Up @@ -188,10 +191,12 @@ impl SiftMcpServer {
tool_router.merge(Self::explore_router());
tool_router.merge(Self::ping_router());
tool_router.merge(Self::rules_router());
tool_router.merge(Self::rule_evaluation_router());
tool_router.merge(Self::annotations_router());
#[cfg(feature = "test-reports")]
tool_router.merge(Self::test_reports_router());
tool_router.merge(Self::docs_router());
tool_router.merge(Self::user_defined_functions_router());
tool_router.merge(Self::users_router());
if update_check.is_some() {
tool_router.merge(Self::update_router());
Expand All @@ -215,9 +220,13 @@ impl SiftMcpServer {
let report_template_service =
ReportTemplateService::new(channel.clone(), retry_policy.clone());
let rule_service = RuleService::new(channel.clone(), retry_policy.clone());
let rule_evaluation_service =
RuleEvaluationService::new(channel.clone(), retry_policy.clone());
#[cfg(feature = "test-reports")]
let test_report_service = TestReportService::new(channel.clone(), retry_policy.clone());
let docs_service = DocsService::new(channel.clone(), retry_policy.clone());
let user_defined_function_service =
UserDefinedFunctionService::new(channel.clone(), retry_policy.clone());
let user_service = UserService::new(channel.clone(), retry_policy);

Self {
Expand All @@ -233,9 +242,11 @@ impl SiftMcpServer {
report_service,
report_template_service,
rule_service,
rule_evaluation_service,
#[cfg(feature = "test-reports")]
test_report_service,
docs_service,
user_defined_function_service,
user_service,
tool_router,
prompt_router,
Expand Down
Loading
Loading