forge_core/config/
mcp_config.rs1use std::time::Duration;
4
5use crate::error::{ForgeError, Result};
6use serde::{Deserialize, Serialize};
7
8use super::default_true;
9use super::types::DurationStr;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct McpConfig {
15 #[serde(default)]
17 pub enabled: bool,
18
19 #[serde(default)]
24 pub oauth: bool,
25
26 #[serde(default = "default_mcp_path")]
28 pub path: String,
29
30 #[serde(default = "default_mcp_session_ttl")]
32 pub session_ttl: DurationStr,
33
34 #[serde(default)]
36 pub allowed_origins: Vec<String>,
37
38 #[serde(default = "default_true")]
40 pub require_protocol_version_header: bool,
41
42 #[serde(default = "default_max_mcp_sessions")]
44 pub max_sessions: usize,
45
46 #[serde(default = "default_max_sessions_per_user")]
48 pub max_sessions_per_user: usize,
49
50 #[serde(default)]
62 pub allow_unauthenticated_dcr: bool,
63}
64
65impl Default for McpConfig {
66 fn default() -> Self {
67 Self {
68 enabled: false,
69 oauth: false,
70 path: default_mcp_path(),
71 session_ttl: default_mcp_session_ttl(),
72 allowed_origins: Vec::new(),
73 max_sessions: default_max_mcp_sessions(),
74 max_sessions_per_user: default_max_sessions_per_user(),
75 require_protocol_version_header: default_true(),
76 allow_unauthenticated_dcr: false,
77 }
78 }
79}
80
81impl McpConfig {
82 pub(crate) const RESERVED_PATHS: &[&str] = &[
84 "/health",
85 "/ready",
86 "/rpc",
87 "/events",
88 "/subscribe",
89 "/unsubscribe",
90 "/subscribe-job",
91 "/subscribe-workflow",
92 "/metrics",
93 ];
94
95 pub fn validate(&self) -> Result<()> {
97 if self.path.is_empty() || !self.path.starts_with('/') {
98 return Err(ForgeError::config(
99 "mcp.path must start with '/' (example: /mcp)",
100 ));
101 }
102 if self.path.contains(' ') {
103 return Err(ForgeError::config("mcp.path cannot contain spaces"));
104 }
105 if Self::RESERVED_PATHS.contains(&self.path.as_str()) {
106 return Err(ForgeError::config(format!(
107 "mcp.path '{}' conflicts with a reserved gateway route",
108 self.path
109 )));
110 }
111 if self.session_ttl.as_secs() == 0 {
112 return Err(ForgeError::config("mcp.session_ttl must be greater than 0"));
113 }
114 Ok(())
115 }
116}
117
118fn default_max_mcp_sessions() -> usize {
119 10_000
120}
121
122fn default_max_sessions_per_user() -> usize {
123 100
124}
125
126fn default_mcp_path() -> String {
127 "/mcp".to_string()
128}
129
130fn default_mcp_session_ttl() -> DurationStr {
131 DurationStr::new(Duration::from_secs(3600))
132}