prism_mcp_rs/protocol/
subscriptions.rs1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct SubscriptionFilter {
15 #[serde(rename = "toolsListChanged", skip_serializing_if = "Option::is_none")]
16 pub tools_list_changed: Option<bool>,
17 #[serde(rename = "promptsListChanged", skip_serializing_if = "Option::is_none")]
18 pub prompts_list_changed: Option<bool>,
19 #[serde(
20 rename = "resourcesListChanged",
21 skip_serializing_if = "Option::is_none"
22 )]
23 pub resources_list_changed: Option<bool>,
24 #[serde(
25 rename = "resourceSubscriptions",
26 default,
27 skip_serializing_if = "Vec::is_empty"
28 )]
29 pub resource_subscriptions: Vec<String>,
30 #[serde(rename = "taskIds", default, skip_serializing_if = "Vec::is_empty")]
31 pub task_ids: Vec<String>,
32}
33
34impl SubscriptionFilter {
35 pub fn matches(&self, method: &str, params: Option<&Value>) -> bool {
37 match method {
38 "notifications/tools/list_changed" => self.tools_list_changed == Some(true),
39 "notifications/prompts/list_changed" => self.prompts_list_changed == Some(true),
40 "notifications/resources/list_changed" => self.resources_list_changed == Some(true),
41 "notifications/resources/updated" => params
42 .and_then(|value| value.get("uri"))
43 .and_then(Value::as_str)
44 .is_some_and(|uri| self.resource_subscriptions.iter().any(|item| item == uri)),
45 "notifications/tasks" => params
46 .and_then(|value| value.get("taskId"))
47 .and_then(Value::as_str)
48 .is_some_and(|task_id| self.task_ids.iter().any(|item| item == task_id)),
49 _ => false,
50 }
51 }
52
53 pub fn requests_tasks(&self) -> bool {
55 !self.task_ids.is_empty()
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct SubscriptionsListenParams {
62 pub notifications: SubscriptionFilter,
63 #[serde(rename = "_meta", default, skip_serializing_if = "HashMap::is_empty")]
64 pub meta: HashMap<String, Value>,
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct SubscriptionsAcknowledgedParams {
70 pub notifications: SubscriptionFilter,
71 #[serde(rename = "_meta")]
72 pub meta: HashMap<String, Value>,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct SubscriptionsListenResult {
78 #[serde(rename = "resultType")]
79 pub result_type: String,
80 #[serde(rename = "_meta")]
81 pub meta: HashMap<String, Value>,
82}
83
84pub const SUBSCRIPTION_ID_META_KEY: &str = "io.modelcontextprotocol/subscriptionId";
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn filters_are_strictly_opt_in() {
93 let filter = SubscriptionFilter {
94 tools_list_changed: Some(true),
95 resource_subscriptions: vec!["file:///a".to_string()],
96 task_ids: vec!["task-a".to_string()],
97 ..Default::default()
98 };
99 assert!(filter.matches("notifications/tools/list_changed", None));
100 assert!(!filter.matches("notifications/prompts/list_changed", None));
101 assert!(filter.matches(
102 "notifications/resources/updated",
103 Some(&serde_json::json!({"uri":"file:///a"}))
104 ));
105 assert!(!filter.matches(
106 "notifications/tasks",
107 Some(&serde_json::json!({"taskId":"task-b"}))
108 ));
109 }
110}