use std::sync::Arc;
use axum::Router;
use axum::extract::State;
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use crate::domain::mcp::ResourceMetadata;
const CONTENT_TYPE: &str = "application/json";
pub fn serve_protected_resource(metadata: &ResourceMetadata) -> Router {
Router::new()
.route(&metadata.resource().metadata_path(), get(get_metadata))
.with_state(Arc::new(metadata.clone()))
}
async fn get_metadata(State(metadata): State<Arc<ResourceMetadata>>) -> Response {
let Ok(body) = serde_json::to_vec(&*metadata) else {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
};
(StatusCode::OK, [(header::CONTENT_TYPE, CONTENT_TYPE)], body).into_response()
}
#[cfg(test)]
mod tests {
use axum::body::Body;
use axum::http::{Request, StatusCode, header};
use tower::ServiceExt;
use super::{ResourceMetadata, serve_protected_resource};
use crate::domain::mcp::ResourceUri;
#[derive(Debug)]
enum Failure {
Uri(crate::domain::mcp::ResourceUriError),
Fixture(&'static str),
}
impl core::fmt::Display for Failure {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Uri(e) => write!(f, "not a legal resource identifier: {e}"),
Self::Fixture(what) => write!(f, "fixture did not hold: {what}"),
}
}
}
impl std::error::Error for Failure {}
fn mounted() -> Result<ResourceMetadata, Failure> {
Ok(ResourceMetadata::new(
ResourceUri::new("https://example.com/mcp").map_err(Failure::Uri)?,
)
.with_authorization_server("https://login.example.com")
.with_scope("files:read"))
}
fn request(method: &str, uri: &str) -> Result<Request<Body>, Failure> {
Request::builder()
.method(method)
.uri(uri)
.body(Body::empty())
.map_err(|_| Failure::Fixture("request builder rejected the method or uri"))
}
async fn send(
metadata: &ResourceMetadata,
request: Request<Body>,
) -> Result<axum::response::Response, Failure> {
serve_protected_resource(metadata)
.oneshot(request)
.await
.map_err(|_| Failure::Fixture("the router did not answer"))
}
#[tokio::test]
async fn serves_the_document_with_the_content_type_the_rfc_requires() -> Result<(), Failure> {
let metadata = mounted()?;
let response = send(
&metadata,
request("GET", "/.well-known/oauth-protected-resource/mcp")?,
)
.await?;
assert_eq!(response.status(), StatusCode::OK);
let content_type = response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok());
assert_eq!(content_type, Some("application/json"));
let body = axum::body::to_bytes(response.into_body(), 64 * 1024)
.await
.map_err(|_| Failure::Fixture("could not read the response body"))?;
let document: serde_json::Value = serde_json::from_slice(&body)
.map_err(|_| Failure::Fixture("the response body was not JSON"))?;
assert_eq!(
document.get("resource"),
Some(&serde_json::json!("https://example.com/mcp"))
);
assert_eq!(
document
.get("authorization_servers")
.and_then(serde_json::Value::as_array)
.and_then(|values| values.first()),
Some(&serde_json::json!("https://login.example.com"))
);
assert_eq!(
document
.get("scopes_supported")
.and_then(serde_json::Value::as_array)
.and_then(|values| values.first()),
Some(&serde_json::json!("files:read"))
);
assert_eq!(
document
.get("bearer_methods_supported")
.and_then(serde_json::Value::as_array)
.and_then(|values| values.first()),
Some(&serde_json::json!("header"))
);
Ok(())
}
#[tokio::test]
async fn a_post_is_refused_rather_than_answered() -> Result<(), Failure> {
let metadata = mounted()?;
let response = send(
&metadata,
request("POST", "/.well-known/oauth-protected-resource/mcp")?,
)
.await?;
assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
Ok(())
}
#[tokio::test]
async fn a_root_resource_publishes_at_the_root_form() -> Result<(), Failure> {
let metadata =
ResourceMetadata::new(ResourceUri::new("https://example.com").map_err(Failure::Uri)?);
let response = send(
&metadata,
request("GET", "/.well-known/oauth-protected-resource")?,
)
.await?;
assert_eq!(response.status(), StatusCode::OK);
Ok(())
}
#[tokio::test]
async fn the_root_form_is_not_served_by_a_server_mounted_below_it() -> Result<(), Failure> {
let metadata = mounted()?;
let response = send(
&metadata,
request("GET", "/.well-known/oauth-protected-resource")?,
)
.await?;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
Ok(())
}
#[tokio::test]
async fn the_router_is_clonable_and_stays_mounted_where_it_was() -> Result<(), Failure> {
let metadata = mounted()?;
let router = serve_protected_resource(&metadata);
let clone = router.clone();
let original = request("GET", "/.well-known/oauth-protected-resource/mcp")?;
assert_eq!(send(&metadata, original).await?.status(), StatusCode::OK);
let cloned_response = clone
.oneshot(request("GET", "/.well-known/oauth-protected-resource/mcp")?)
.await
.map_err(|_| Failure::Fixture("the cloned router did not answer"))?;
assert_eq!(cloned_response.status(), StatusCode::OK);
Ok(())
}
}