use url::Url;
#[must_use]
pub fn protected_resource_metadata_urls(server: &Url) -> Vec<String> {
let mut urls = Vec::with_capacity(2);
let path = server.path().trim_end_matches('/');
if !path.is_empty() {
urls.push(format!(
"{}/.well-known/oauth-protected-resource{path}",
origin(server)
));
}
urls.push(format!(
"{}/.well-known/oauth-protected-resource",
origin(server)
));
urls
}
#[must_use]
pub fn authorization_server_metadata_urls(issuer: &Url) -> Vec<String> {
let base = origin(issuer);
let path = issuer.path().trim_end_matches('/');
if path.is_empty() {
return vec![
format!("{base}/.well-known/oauth-authorization-server"),
format!("{base}/.well-known/openid-configuration"),
];
}
vec![
format!("{base}/.well-known/oauth-authorization-server{path}"),
format!("{base}/.well-known/openid-configuration{path}"),
format!("{base}{path}/.well-known/openid-configuration"),
]
}
#[must_use]
pub fn canonical_resource(server: &Url) -> String {
let path = server.path();
let path = if path == "/" {
""
} else {
path.trim_end_matches('/')
};
format!("{}{path}", origin(server))
}
fn origin(url: &Url) -> String {
let host = url.host_str().unwrap_or_default();
let host = if host.contains(':') && !host.starts_with('[') {
format!("[{host}]")
} else {
host.to_owned()
};
url.port().map_or_else(
|| format!("{}://{host}", url.scheme()),
|port| format!("{}://{host}:{port}", url.scheme()),
)
}
#[cfg(test)]
mod tests {
use url::Url;
use super::{
authorization_server_metadata_urls, canonical_resource, protected_resource_metadata_urls,
};
type TestResult = Result<(), url::ParseError>;
#[test]
fn resource_metadata_tries_the_path_then_the_root() -> TestResult {
assert_eq!(
protected_resource_metadata_urls(&Url::parse("https://example.com/public/mcp")?),
[
"https://example.com/.well-known/oauth-protected-resource/public/mcp",
"https://example.com/.well-known/oauth-protected-resource",
]
);
assert_eq!(
protected_resource_metadata_urls(&Url::parse("https://example.com")?),
["https://example.com/.well-known/oauth-protected-resource"]
);
Ok(())
}
#[test]
fn an_issuer_with_a_path_tries_three_endpoints_in_order() -> TestResult {
assert_eq!(
authorization_server_metadata_urls(&Url::parse("https://auth.example.com/tenant1")?),
[
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1",
"https://auth.example.com/.well-known/openid-configuration/tenant1",
"https://auth.example.com/tenant1/.well-known/openid-configuration",
]
);
Ok(())
}
#[test]
fn an_issuer_without_a_path_tries_two() -> TestResult {
assert_eq!(
authorization_server_metadata_urls(&Url::parse("http://localhost:9000")?),
[
"http://localhost:9000/.well-known/oauth-authorization-server",
"http://localhost:9000/.well-known/openid-configuration",
]
);
Ok(())
}
#[test]
fn the_canonical_resource_drops_slash_query_and_fragment() -> TestResult {
assert_eq!(
canonical_resource(&Url::parse("https://MCP.example.com/mcp/?a=1#f")?),
"https://mcp.example.com/mcp"
);
assert_eq!(
canonical_resource(&Url::parse("https://mcp.example.com/")?),
"https://mcp.example.com"
);
assert_eq!(
canonical_resource(&Url::parse("http://127.0.0.1:8443/mcp")?),
"http://127.0.0.1:8443/mcp"
);
Ok(())
}
}