1use std::collections::BTreeMap;
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6use super::ToolPolicy;
7use crate::config::mcp::{McpAllowListConfig, McpAllowListRules};
8use crate::utils::tool_name_parsing::parse_canonical_mcp_tool_name;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct McpPolicyStore {
12 #[serde(default = "default_secure_mcp_allowlist")]
14 pub allowlist: McpAllowListConfig,
15 #[serde(default)]
17 pub providers: IndexMap<String, McpProviderPolicy>,
18}
19
20impl Default for McpPolicyStore {
21 fn default() -> Self {
22 Self {
23 allowlist: default_secure_mcp_allowlist(),
24 providers: IndexMap::new(),
25 }
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
31pub struct McpProviderPolicy {
32 #[serde(default)]
33 pub tools: IndexMap<String, ToolPolicy>,
34}
35
36const MCP_LOGGING_EVENTS: &[&str] = &[
38 "mcp.tool_execution",
39 "mcp.tool_failed",
40 "mcp.tool_denied",
41 "mcp.tool_filtered",
42 "mcp.provider_initialized",
43];
44
45const MCP_DEFAULT_LOGGING_EVENTS: &[&str] = &[
46 "mcp.provider_initialized",
47 "mcp.provider_initialization_failed",
48 "mcp.tool_filtered",
49 "mcp.tool_execution",
50 "mcp.tool_failed",
51 "mcp.tool_denied",
52];
53
54#[inline]
56pub(crate) fn mcp_standard_logging() -> Vec<String> {
57 MCP_LOGGING_EVENTS.iter().map(|s| (*s).into()).collect()
58}
59
60#[inline]
62fn mcp_provider_config_with(extra: (&str, Vec<&str>)) -> BTreeMap<String, Vec<String>> {
63 BTreeMap::from([
64 ("provider".into(), vec!["max_concurrent_requests".into()]),
65 (extra.0.into(), extra.1.into_iter().map(Into::into).collect()),
66 ])
67}
68
69fn default_secure_mcp_allowlist() -> McpAllowListConfig {
70 let default_logging = Some(MCP_DEFAULT_LOGGING_EVENTS.iter().map(|s| (*s).into()).collect());
71
72 let default_configuration = Some(BTreeMap::from([
73 (
74 "client".into(),
75 vec![
76 "max_concurrent_connections".into(),
77 "request_timeout_seconds".into(),
78 "retry_attempts".into(),
79 "startup_timeout_seconds".into(),
80 "tool_timeout_seconds".into(),
81 "experimental_use_rmcp_client".into(),
82 ],
83 ),
84 ("ui".into(), vec!["mode".into(), "max_events".into(), "show_provider_names".into()]),
85 (
86 "server".into(),
87 vec![
88 "enabled".into(),
89 "bind_address".into(),
90 "port".into(),
91 "transport".into(),
92 "name".into(),
93 "version".into(),
94 ],
95 ),
96 ]));
97
98 let time_rules = McpAllowListRules {
99 tools: Some(vec![
100 "get_*".into(),
101 "list_*".into(),
102 "convert_timezone".into(),
103 "describe_timezone".into(),
104 "time_*".into(),
105 ]),
106 resources: Some(vec!["timezone:*".into(), "location:*".into()]),
107 logging: Some(mcp_standard_logging()),
108 configuration: Some(mcp_provider_config_with(("time", vec!["local_timezone_override"]))),
109 ..Default::default()
110 };
111
112 let context_rules = McpAllowListRules {
113 tools: Some(vec![
114 "search_*".into(),
115 "fetch_*".into(),
116 "list_*".into(),
117 "context7_*".into(),
118 "get_*".into(),
119 ]),
120 resources: Some(vec![
121 "docs::*".into(),
122 "snippets::*".into(),
123 "repositories::*".into(),
124 "context7::*".into(),
125 ]),
126 prompts: Some(vec!["context7::*".into(), "support::*".into(), "docs::*".into()]),
127 logging: Some(mcp_standard_logging()),
128 configuration: Some(mcp_provider_config_with(("context7", vec!["workspace", "search_scope", "max_results"]))),
129 };
130
131 let seq_rules = McpAllowListRules {
132 tools: Some(vec![
133 "plan".into(),
134 "critique".into(),
135 "reflect".into(),
136 "decompose".into(),
137 "sequential_*".into(),
138 ]),
139 resources: None,
140 prompts: Some(vec![
141 "sequential-thinking::*".into(),
142 "plan".into(),
143 "reflect".into(),
144 "critique".into(),
145 ]),
146 logging: Some(mcp_standard_logging()),
147 configuration: Some(mcp_provider_config_with(("sequencing", vec!["max_depth", "max_branches"]))),
148 };
149
150 let mut allowlist = McpAllowListConfig {
151 enforce: true,
152 default: McpAllowListRules {
153 logging: default_logging,
154 configuration: default_configuration,
155 ..Default::default()
156 },
157 ..Default::default()
158 };
159
160 allowlist.providers.insert("time".into(), time_rules);
161 allowlist.providers.insert("context7".into(), context_rules);
162 allowlist.providers.insert("sequential-thinking".into(), seq_rules);
163
164 allowlist
165}
166
167pub(crate) fn parse_mcp_policy_key(tool_name: &str) -> Option<(String, String)> {
168 parse_canonical_mcp_tool_name(tool_name).map(|(provider, tool)| (provider.to_string(), tool.to_string()))
169}