nix: extraMcpServers http entries can read request headers from one file via headersHelper
This commit is contained in:
parent
62ab45297c
commit
53eed2a757
3 changed files with 79 additions and 1 deletions
|
|
@ -39,6 +39,12 @@ pub enum ExtraMcpServer {
|
|||
},
|
||||
Http {
|
||||
url: String,
|
||||
/// Absolute path to a file holding a JSON object of request headers
|
||||
/// (`{"Authorization": "Bearer ..."}`). Rendered as claude's
|
||||
/// `headersHelper` so the secret stays in the file, never in the nix
|
||||
/// store or this config.
|
||||
#[serde(default, rename = "headersFile")]
|
||||
headers_file: Option<String>,
|
||||
#[serde(default = "default_allowed_tools", rename = "allowedTools")]
|
||||
allowed_tools: Vec<String>,
|
||||
#[serde(default, rename = "availableToSubagents")]
|
||||
|
|
@ -92,11 +98,30 @@ impl ExtraMcpServer {
|
|||
.or_insert_with(|| state_dir.display().to_string());
|
||||
serde_json::json!({ "command": command, "args": args, "env": env })
|
||||
}
|
||||
Self::Http { url, .. } => serde_json::json!({ "type": "http", "url": url }),
|
||||
Self::Http {
|
||||
url, headers_file, ..
|
||||
} => {
|
||||
let mut entry = serde_json::json!({ "type": "http", "url": url });
|
||||
if let Some(path) = headers_file {
|
||||
// claude runs this in a shell at session start, on
|
||||
// reconnect and after a 401/403, and reads a JSON object
|
||||
// of headers from its stdout - so `cat` of the file is the
|
||||
// whole helper, and a rotated token is picked up on the
|
||||
// next reconnect.
|
||||
entry["headersHelper"] =
|
||||
serde_json::Value::String(format!("cat {}", shell_single_quote(path)));
|
||||
}
|
||||
entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `s` as one single-quoted shell word.
|
||||
fn shell_single_quote(s: &str) -> String {
|
||||
format!("'{}'", s.replace('\'', "'\\''"))
|
||||
}
|
||||
|
||||
fn default_allowed_tools() -> Vec<String> {
|
||||
vec!["*".to_owned()]
|
||||
}
|
||||
|
|
@ -183,4 +208,31 @@ mod tests {
|
|||
assert_eq!(entry["url"], "http://127.0.0.1:1/mcp");
|
||||
assert!(entry.get("env").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_headers_file_renders_a_headers_helper() {
|
||||
let http: ExtraMcpServer = serde_json::from_value(serde_json::json!({
|
||||
"type": "http",
|
||||
"url": "https://x.example/mcp",
|
||||
"headersFile": "/run/secrets/it's a header file",
|
||||
}))
|
||||
.unwrap();
|
||||
let entry = http.to_json_entry(Path::new("/agents/x/state"));
|
||||
assert_eq!(entry["url"], "https://x.example/mcp");
|
||||
assert_eq!(
|
||||
entry["headersHelper"],
|
||||
"cat '/run/secrets/it'\\''s a header file'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_without_headers_file_has_no_helper() {
|
||||
let http: ExtraMcpServer = serde_json::from_value(serde_json::json!({
|
||||
"type": "http",
|
||||
"url": "http://127.0.0.1:1/mcp",
|
||||
}))
|
||||
.unwrap();
|
||||
let entry = http.to_json_entry(Path::new("/agents/x/state"));
|
||||
assert!(entry.get("headersHelper").is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue