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
3 changes: 3 additions & 0 deletions rust/crates/sift_mcp/src/tool/channels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ impl SiftMcpServer {
Guidance:
- Always scope with `asset_id == \"...\"` when the asset is known — channel namespaces are per-asset
and unscoped queries return cross-asset results.
- Unscoped name searches scan every channel in the org and can time out in large orgs. Resolve
candidate assets with `list_assets` first, then search each with `asset_id == \"...\" && name.matches(...)`.
A timeout means the filter was too broad, not that the parameters were wrong; narrow it before retrying.
- To enumerate channels recorded by a specific run, filter on `run_id` rather than joining client-side.
",
annotations(title = "channels/list_channels", read_only_hint = true)
Expand Down
29 changes: 29 additions & 0 deletions rust/crates/sift_mcp/src/tool/channels/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,35 @@ async fn list_channels_reports_a_field_that_matched_nothing() {
assert_eq!(body["unmatched_fields"], serde_json::json!(["nmae"]));
}

#[test]
fn tool_description_warns_that_unscoped_name_searches_can_time_out() {
let tools = SiftMcpServer::channels_router().list_all();
let tool = tools
.iter()
.find(|tool| tool.name == "list_channels")
.expect("list_channels is routed");
let description = tool
.description
.as_deref()
.expect("list_channels has a description");

assert!(
description
.contains("Unscoped name searches scan every channel in the org and can time out"),
"the description must warn that unscoped name searches can time out"
);
assert!(
description.contains("candidate assets with `list_assets` first"),
"the description must tell the agent to resolve assets before searching"
);
assert!(
description.contains(
"A timeout means the filter was too broad, not that the parameters were wrong"
),
"the description must say a timeout calls for a narrower filter"
);
}

#[tokio::test]
async fn list_channels_projects_asset_id_and_unit_id_but_not_asset_name() {
let mut channel_mock = MockChannelServiceImpl::new();
Expand Down
Loading