use std::collections::HashMap;
use crate::services::analytics::log_event;
use crate::services::mcp::types::*;
use crate::utils::config::check_has_trust_dialog_accepted;
use crate::utils::cwd::get_cwd;
use crate::utils::debug::log_ant_error;
use crate::utils::exec_file_no_throw::exec_file_no_throw_with_cwd;
const FEEDBACK_CHANNEL: &str = "#briarpatch-cc";
fn is_non_interactive_session() -> bool {
std::env::var("AI_CODE_NON_INTERACTIVE")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
}
fn json_parse(s: &str) -> Option<serde_json::Value> {
serde_json::from_str(s).ok()
}
pub async fn get_mcp_headers_from_helper(
server_name: &str,
config: &McpSseServerConfig,
) -> Option<HashMap<String, String>> {
let headers_helper = config.headers_helper.as_ref()?;
if !is_non_interactive_session() {
if !check_has_trust_dialog_accepted() {
let error_msg = format!(
"Security: headersHelper for MCP server '{}' executed before workspace trust is confirmed. \
If you see this message, post in {}.",
server_name, FEEDBACK_CHANNEL
);
log_ant_error("MCP headersHelper invoked before trust check", &error_msg);
log_event("tengu_mcp_headersHelper_missing_trust", Default::default());
return None;
}
}
let cwd = get_cwd();
let exec_result = exec_file_no_throw_with_cwd(headers_helper, vec![], &cwd).await;
if exec_result.code != 0 || exec_result.stdout.is_empty() {
log::warn!(
"headersHelper for MCP server '{}' did not return a valid value (code: {})",
server_name,
exec_result.code
);
return None;
}
let result = exec_result.stdout.trim();
let headers = match json_parse(result) {
Some(serde_json::Value::Object(map)) => map,
_ => {
log::warn!(
"headersHelper for MCP server '{}' must return a JSON object",
server_name
);
return None;
}
};
for (key, value) in &headers {
if !value.is_string() {
log::warn!(
"headersHelper for MCP server '{}' returned non-string value for key \"{}\": {}",
server_name,
key,
value
);
return None;
}
}
let headers: HashMap<String, String> = headers
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string())))
.collect();
log::debug!(
"Successfully retrieved {} headers from headersHelper for server '{}'",
headers.len(),
server_name
);
Some(headers)
}
pub async fn get_mcp_server_headers(
server_name: &str,
config: &McpSseServerConfig,
) -> HashMap<String, String> {
let mut headers = config.headers.clone().unwrap_or_default();
if let Some(dynamic_headers) = get_mcp_headers_from_helper(server_name, config).await {
headers.extend(dynamic_headers);
}
headers
}
pub async fn get_mcp_http_server_headers(
server_name: &str,
config: &McpHttpServerConfig,
) -> HashMap<String, String> {
let mut headers = config.headers.clone().unwrap_or_default();
if let Some(headers_helper) = &config.headers_helper {
if !is_non_interactive_session() && !check_has_trust_dialog_accepted() {
log::debug!(
"Skipping headersHelper for MCP server '{}' due to untrusted workspace",
server_name
);
} else {
let cwd = get_cwd();
let exec_result = exec_file_no_throw_with_cwd(headers_helper, vec![], &cwd).await;
if exec_result.code == 0 && !exec_result.stdout.is_empty() {
let result = exec_result.stdout.trim();
if let Some(serde_json::Value::Object(map)) = json_parse(result) {
let valid = map.values().all(|v| v.is_string());
if valid {
let dynamic: HashMap<String, String> = map
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string())))
.collect();
headers.extend(dynamic);
}
}
}
}
}
headers
}
pub async fn get_mcp_ws_server_headers(
server_name: &str,
config: &McpWebSocketServerConfig,
) -> HashMap<String, String> {
let mut headers = config.headers.clone().unwrap_or_default();
if let Some(headers_helper) = &config.headers_helper {
if !is_non_interactive_session() && !check_has_trust_dialog_accepted() {
log::debug!(
"Skipping headersHelper for MCP server '{}' due to untrusted workspace",
server_name
);
} else {
let cwd = get_cwd();
let exec_result = exec_file_no_throw_with_cwd(headers_helper, vec![], &cwd).await;
if exec_result.code == 0 && !exec_result.stdout.is_empty() {
let result = exec_result.stdout.trim();
if let Some(serde_json::Value::Object(map)) = json_parse(result) {
let valid = map.values().all(|v| v.is_string());
if valid {
let dynamic: HashMap<String, String> = map
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string())))
.collect();
headers.extend(dynamic);
}
}
}
}
}
headers
}