use axum::{
extract::State,
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use serde_json::Value;
use crate::{
handlers::{check_auth_token, AppState},
mcp,
};
pub async fn handle_mcp_post(
State(state): State<AppState>,
headers: HeaderMap,
body: axum::body::Bytes,
) -> Response {
let resource_metadata_url = {
let base = state.config.base_url.trim_end_matches('/');
format!("{base}/.well-known/oauth-protected-resource")
};
let www_auth_value = format!("Bearer resource_metadata=\"{resource_metadata_url}\"");
let www_auth_header: axum::http::HeaderValue = www_auth_value
.parse()
.unwrap_or_else(|_| axum::http::HeaderValue::from_static("Bearer"));
let provided = headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "));
let token = match provided {
Some(t) => t.to_string(),
None => {
return (
StatusCode::UNAUTHORIZED,
[(axum::http::header::WWW_AUTHENTICATE, www_auth_header.clone())],
axum::Json(serde_json::json!({
"error": "unauthorized",
"error_description": "Bearer token required"
})),
)
.into_response();
}
};
if let Err(_) = check_auth_token(&state, &token).await {
return (
StatusCode::UNAUTHORIZED,
[(axum::http::header::WWW_AUTHENTICATE, www_auth_header)],
axum::Json(serde_json::json!({
"error": "unauthorized",
"error_description": "Invalid or expired token"
})),
)
.into_response();
}
let request: mcp::Request = match serde_json::from_slice(&body) {
Ok(r) => r,
Err(e) => {
let resp = mcp::Response::err(
Value::Null,
-32700,
format!("Parse error: {e}"),
);
return json_response(StatusCode::OK, &resp);
}
};
let id = match request.id.clone() {
Some(id) => id,
None => {
return StatusCode::ACCEPTED.into_response();
}
};
let server_url = std::env::var("TWOFOLD_MCP_SERVER")
.unwrap_or_else(|_| "http://localhost:3000".to_string());
let server_url = server_url.trim_end_matches('/').to_string();
let token = std::env::var("TWOFOLD_MCP_TOKEN")
.or_else(|_| std::env::var("TWOFOLD_TOKEN"))
.unwrap_or_default();
let result = tokio::task::spawn_blocking(move || {
let client = mcp::build_client();
mcp::handle_request(&client, &server_url, &token, id, &request)
})
.await;
match result {
Ok(resp) => json_response(StatusCode::OK, &resp),
Err(e) => {
tracing::error!(error = %e, "MCP spawn_blocking task panicked");
let resp = mcp::Response::err(
Value::Null,
-32603,
"Internal error: handler panicked".to_string(),
);
json_response(StatusCode::INTERNAL_SERVER_ERROR, &resp)
}
}
}
fn json_response(status: StatusCode, resp: &mcp::Response) -> Response {
match serde_json::to_vec(resp) {
Ok(body) => (
status,
[(
axum::http::header::CONTENT_TYPE,
"application/json",
)],
body,
)
.into_response(),
Err(e) => {
tracing::error!(error = %e, "Failed to serialize MCP response");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}