agent_config/spec/mcp/
builder.rs1use std::collections::BTreeMap;
4
5use crate::error::AgentConfigError;
6
7use super::transport::McpTransport;
8use super::{McpSpec, SecretPolicy};
9
10#[derive(Debug, Clone)]
12pub struct McpSpecBuilder {
13 pub(super) name: String,
14 pub(super) owner_tag: Option<String>,
15 pub(super) transport: Option<McpTransport>,
16 pub(super) friendly_name: Option<String>,
17 pub(super) secret_policy: SecretPolicy,
18 pub(super) adopt_unowned: bool,
19 pub(super) builder_error: Option<String>,
20}
21
22impl McpSpecBuilder {
23 pub fn owner(mut self, tag: impl Into<String>) -> Self {
25 self.owner_tag = Some(tag.into());
26 self
27 }
28
29 pub fn adopt_unowned(mut self, adopt: bool) -> Self {
36 self.adopt_unowned = adopt;
37 self
38 }
39
40 pub fn stdio<I, S>(mut self, command: impl Into<String>, args: I) -> Self
42 where
43 I: IntoIterator<Item = S>,
44 S: Into<String>,
45 {
46 self.transport = Some(McpTransport::Stdio {
47 command: command.into(),
48 args: args.into_iter().map(Into::into).collect(),
49 env: BTreeMap::new(),
50 });
51 self
52 }
53
54 pub fn http(mut self, url: impl Into<String>) -> Self {
56 self.transport = Some(McpTransport::Http {
57 url: url.into(),
58 headers: BTreeMap::new(),
59 });
60 self
61 }
62
63 pub fn sse(mut self, url: impl Into<String>) -> Self {
65 self.transport = Some(McpTransport::Sse {
66 url: url.into(),
67 headers: BTreeMap::new(),
68 });
69 self
70 }
71
72 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
78 match &mut self.transport {
79 Some(McpTransport::Stdio { env, .. }) => {
80 env.insert(key.into(), value.into());
81 }
82 Some(McpTransport::Http { .. }) | Some(McpTransport::Sse { .. }) => {
83 self.record_builder_error("env() can only be used with stdio MCP transports");
84 }
85 None => {
86 self.record_builder_error("env() called before stdio transport was configured");
87 }
88 }
89 self
90 }
91
92 pub fn env_from_host(mut self, key: impl Into<String>) -> Self {
98 let key = key.into();
99 let placeholder = format!("${{{key}}}");
100 self = self.env(key, placeholder);
101 self
102 }
103
104 pub fn env_placeholder(
109 mut self,
110 key: impl Into<String>,
111 placeholder: impl Into<String>,
112 ) -> Self {
113 self = self.env(key, placeholder);
114 self
115 }
116
117 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
122 match &mut self.transport {
123 Some(McpTransport::Http { headers, .. }) | Some(McpTransport::Sse { headers, .. }) => {
124 headers.insert(key.into(), value.into());
125 }
126 Some(McpTransport::Stdio { .. }) => {
127 self.record_builder_error("header() can only be used with HTTP/SSE MCP transports");
128 }
129 None => {
130 self.record_builder_error(
131 "header() called before HTTP/SSE transport was configured",
132 );
133 }
134 }
135 self
136 }
137
138 pub fn friendly_name(mut self, name: impl Into<String>) -> Self {
140 self.friendly_name = Some(name.into());
141 self
142 }
143
144 pub fn allow_local_inline_secrets(mut self) -> Self {
149 self.secret_policy = SecretPolicy::AllowInlineSecretsInLocalScope;
150 self
151 }
152
153 pub fn build(self) -> McpSpec {
163 self.try_build().expect("McpSpec missing required field")
164 }
165
166 pub fn try_build(self) -> Result<McpSpec, AgentConfigError> {
181 if let Some(error) = self.builder_error {
182 return Err(AgentConfigError::Other(anyhow::anyhow!(error)));
183 }
184 let owner_tag = self.owner_tag.ok_or(AgentConfigError::MissingSpecField {
185 id: "<mcp builder>",
186 field: "owner",
187 })?;
188 let transport = self.transport.ok_or(AgentConfigError::MissingSpecField {
189 id: "<mcp builder>",
190 field: "transport",
191 })?;
192 let spec = McpSpec {
193 name: self.name,
194 owner_tag,
195 transport,
196 friendly_name: self.friendly_name,
197 secret_policy: self.secret_policy,
198 adopt_unowned: self.adopt_unowned,
199 };
200 spec.validate()?;
201 Ok(spec)
202 }
203
204 fn record_builder_error(&mut self, message: &'static str) {
205 if self.builder_error.is_none() {
206 self.builder_error = Some(message.to_string());
207 }
208 }
209}