use async_trait::async_trait;
use reqwest::Client;
use crate::error::McpError;
use crate::protocol::{JsonRpcRequest, JsonRpcResponse};
use crate::transport::McpTransport;
pub struct HttpMcpTransport {
url: String,
client: Client,
bearer_token: Option<String>,
}
impl HttpMcpTransport {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
client: Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()
.expect("valid reqwest client"),
bearer_token: None,
}
}
pub fn with_bearer_token(mut self, token: impl Into<String>) -> Self {
self.bearer_token = Some(token.into());
self
}
}
#[async_trait]
impl McpTransport for HttpMcpTransport {
async fn send(&self, request: JsonRpcRequest) -> Result<JsonRpcResponse, McpError> {
let mut builder = self
.client
.post(&self.url)
.header("content-type", "application/json")
.json(&request);
if let Some(token) = &self.bearer_token {
builder = builder.header("authorization", format!("Bearer {token}"));
}
let resp = builder
.send()
.await
.map_err(|e| McpError::Transport(e.to_string()))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(McpError::Transport(format!("HTTP {status}: {body}")));
}
resp.json::<JsonRpcResponse>()
.await
.map_err(|e| McpError::Protocol(format!("JSON-RPC parse error: {e}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructs_with_url_and_token() {
let t = HttpMcpTransport::new("https://mcp.example.com").with_bearer_token("tok");
assert_eq!(t.url, "https://mcp.example.com");
assert_eq!(t.bearer_token.as_deref(), Some("tok"));
}
}