use auths_verifier::PublicKeyHex;
use auths_verifier::core::Attestation;
use serde::{Deserialize, Serialize};
use crate::error::McpAuthError;
#[derive(Serialize)]
struct McpExchangeRequest {
attestation_chain: Vec<Attestation>,
root_public_key: PublicKeyHex,
#[serde(skip_serializing_if = "Option::is_none")]
requested_capabilities: Option<Vec<String>>,
}
#[derive(Deserialize)]
struct McpTokenResponse {
access_token: String,
#[allow(dead_code)]
token_type: String,
#[allow(dead_code)]
expires_in: u64,
#[allow(dead_code)]
subject: String,
}
pub async fn exchange_token(
client: &reqwest::Client,
bridge_url: &str,
chain: &[Attestation],
root_public_key_hex: &PublicKeyHex,
requested_capabilities: &[&str],
) -> Result<String, McpAuthError> {
let url = format!("{}/token", bridge_url.trim_end_matches('/'));
let request_body = McpExchangeRequest {
attestation_chain: chain.to_vec(),
root_public_key: root_public_key_hex.clone(),
requested_capabilities: if requested_capabilities.is_empty() {
None
} else {
Some(
requested_capabilities
.iter()
.map(|s| s.to_string())
.collect(),
)
},
};
let response = client
.post(&url)
.json(&request_body)
.send()
.await
.map_err(|e| McpAuthError::BridgeUnreachable(e.to_string()))?;
let status = response.status().as_u16();
if status == 403 {
let body = response
.text()
.await
.unwrap_or_else(|_| "unknown".to_string());
return Err(McpAuthError::InsufficientCapabilities {
requested: requested_capabilities
.iter()
.map(|s| s.to_string())
.collect(),
detail: body,
});
}
if !response.status().is_success() {
let body = response
.text()
.await
.unwrap_or_else(|_| "unknown".to_string());
return Err(McpAuthError::TokenExchangeFailed { status, body });
}
let token_response: McpTokenResponse = response
.json()
.await
.map_err(|e| McpAuthError::InvalidResponse(e.to_string()))?;
Ok(token_response.access_token)
}