1use std::collections::BTreeSet;
2
3use crate::McpSurfaceCard;
4
5#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct McpProfile {
12 allow_names: BTreeSet<String>,
13 deny_names: BTreeSet<String>,
14}
15
16impl McpProfile {
17 pub fn all() -> Self {
19 Self::default()
20 }
21
22 pub fn allow_names<I, S>(names: I) -> Self
24 where
25 I: IntoIterator<Item = S>,
26 S: Into<String>,
27 {
28 Self {
29 allow_names: names.into_iter().map(Into::into).collect(),
30 deny_names: BTreeSet::new(),
31 }
32 }
33
34 pub fn deny_names<I, S>(names: I) -> Self
36 where
37 I: IntoIterator<Item = S>,
38 S: Into<String>,
39 {
40 Self {
41 allow_names: BTreeSet::new(),
42 deny_names: names.into_iter().map(Into::into).collect(),
43 }
44 }
45
46 pub fn with_allowed_name(mut self, name: impl Into<String>) -> Self {
48 self.allow_names.insert(name.into());
49 self
50 }
51
52 pub fn with_denied_name(mut self, name: impl Into<String>) -> Self {
54 self.deny_names.insert(name.into());
55 self
56 }
57
58 pub fn allows_name(&self, name: &str) -> bool {
60 if matches_any(&self.deny_names, name) {
61 return false;
62 }
63 self.allow_names.is_empty() || matches_any(&self.allow_names, name)
64 }
65
66 pub fn allows(&self, row: &McpSurfaceCard) -> bool {
68 self.allows_name(&row.name)
69 }
70}
71
72fn matches_any(patterns: &BTreeSet<String>, name: &str) -> bool {
73 patterns.iter().any(|pattern| glob_matches(pattern, name))
74}
75
76fn glob_matches(pattern: &str, name: &str) -> bool {
77 if pattern == "*" || pattern == name {
78 return true;
79 }
80 if !pattern.contains('*') {
81 return false;
82 }
83 let mut rest = name;
84 let anchored_start = !pattern.starts_with('*');
85 let anchored_end = !pattern.ends_with('*');
86 let parts = pattern.split('*').filter(|part| !part.is_empty());
87 let mut first = true;
88 for part in parts {
89 if first && anchored_start {
90 let Some(tail) = rest.strip_prefix(part) else {
91 return false;
92 };
93 rest = tail;
94 } else {
95 let Some(index) = rest.find(part) else {
96 return false;
97 };
98 rest = &rest[index + part.len()..];
99 }
100 first = false;
101 }
102 !anchored_end || rest.is_empty()
103}