1use crate::{
2 auth::{OauthEndpoint, OAUTH_PROTECTED_RESOURCE_BASE, WELL_KNOWN_OAUTH_AUTHORIZATION_SERVER},
3 error::McpSdkError,
4 mcp_http::url_base,
5};
6use reqwest::Client;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use url::Url;
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
12pub struct AuthorizationServerMetadata {
13 pub issuer: Url,
15
16 pub authorization_endpoint: Url,
18
19 pub token_endpoint: Url,
21
22 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
24 pub jwks_uri: Option<Url>,
25
26 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
28 pub registration_endpoint: Option<Url>,
29
30 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
32 pub scopes_supported: Option<Vec<String>>,
33
34 #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
37 pub response_types_supported: Vec<String>,
38
39 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
42 pub response_modes_supported: Option<Vec<String>>,
43
44 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
50 pub grant_types_supported: Option<Vec<String>>,
51
52 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
54 pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
55
56 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
58 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
59
60 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
63 pub service_documentation: Option<Url>,
64
65 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
67 pub revocation_endpoint: Option<Url>,
68
69 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
71 pub revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
72
73 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
80 pub revocation_endpoint_auth_methods_supported: Option<Vec<String>>,
81
82 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
84 pub introspection_endpoint: Option<Url>,
85
86 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
88 pub introspection_endpoint_auth_methods_supported: Option<Vec<String>>,
89
90 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
92 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
93
94 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
97 pub code_challenge_methods_supported: Option<Vec<String>>,
98
99 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
100 pub userinfo_endpoint: Option<String>,
101}
102
103impl AuthorizationServerMetadata {
104 pub fn new(
114 issuer: &str,
115 authorization_endpoint: &str,
116 token_endpoint: &str,
117 ) -> Result<Self, url::ParseError> {
118 let issuer = Url::parse(issuer)?;
119 let authorization_endpoint = Url::parse(authorization_endpoint)?;
120 let token_endpoint = Url::parse(token_endpoint)?;
121
122 Ok(Self {
123 issuer,
124 authorization_endpoint,
125 token_endpoint,
126 jwks_uri: Default::default(),
127 registration_endpoint: Default::default(),
128 scopes_supported: Default::default(),
129 response_types_supported: Default::default(),
130 response_modes_supported: Default::default(),
131 grant_types_supported: Default::default(),
132 token_endpoint_auth_methods_supported: Default::default(),
133 token_endpoint_auth_signing_alg_values_supported: Default::default(),
134 service_documentation: Default::default(),
135 revocation_endpoint: Default::default(),
136 revocation_endpoint_auth_signing_alg_values_supported: Default::default(),
137 revocation_endpoint_auth_methods_supported: Default::default(),
138 introspection_endpoint: Default::default(),
139 introspection_endpoint_auth_methods_supported: Default::default(),
140 introspection_endpoint_auth_signing_alg_values_supported: Default::default(),
141 code_challenge_methods_supported: Default::default(),
142 userinfo_endpoint: Default::default(),
143 })
144 }
145
146 pub async fn from_discovery_url(discovery_url: &str) -> Result<Self, McpSdkError> {
154 let client = Client::new();
155 let metadata = client
156 .get(discovery_url)
157 .send()
158 .await
159 .map_err(|err| McpSdkError::Internal {
160 description: err.to_string(),
161 })?
162 .json::<AuthorizationServerMetadata>()
163 .await
164 .map_err(|err| McpSdkError::Internal {
165 description: err.to_string(),
166 })?;
167 Ok(metadata)
168 }
169}
170
171#[derive(Debug, Serialize, Deserialize, Clone)]
176pub struct OauthProtectedResourceMetadata {
177 pub resource: Url,
180
181 #[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
184 pub authorization_servers: Vec<Url>,
185
186 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
190 pub jwks_uri: Option<Url>,
191
192 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
195 pub scopes_supported: Option<Vec<String>>,
196
197 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
202 pub bearer_methods_supported: Option<Vec<String>>,
203
204 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
207 pub resource_signing_alg_values_supported: Option<Vec<String>>,
208
209 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
212 pub resource_name: Option<String>,
213
214 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
216 pub resource_documentation: Option<String>,
217
218 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
220 pub resource_policy_uri: Option<Url>,
221
222 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
224 pub resource_tos_uri: Option<Url>,
225
226 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
229 pub tls_client_certificate_bound_access_tokens: Option<bool>,
230
231 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
234 pub authorization_details_types_supported: Option<Vec<String>>,
235
236 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
239 pub dpop_signing_alg_values_supported: Option<Vec<String>>,
240
241 #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
244 pub dpop_bound_access_tokens_required: Option<bool>,
245}
246
247impl OauthProtectedResourceMetadata {
248 pub fn new<S>(
255 resource: S,
256 authorization_servers: Vec<S>,
257 scopes_supported: Option<Vec<String>>,
258 ) -> Result<Self, url::ParseError>
259 where
260 S: AsRef<str>,
261 {
262 let resource = Url::parse(resource.as_ref())?;
263 let authorization_servers: Vec<_> = authorization_servers
264 .iter()
265 .map(|s| Url::parse(s.as_ref()))
266 .collect::<Result<_, _>>()?;
267
268 Ok(Self {
269 resource,
270 authorization_servers,
271 jwks_uri: Default::default(),
272 scopes_supported,
273 bearer_methods_supported: Default::default(),
274 resource_signing_alg_values_supported: Default::default(),
275 resource_name: Default::default(),
276 resource_documentation: Default::default(),
277 resource_policy_uri: Default::default(),
278 resource_tos_uri: Default::default(),
279 tls_client_certificate_bound_access_tokens: Default::default(),
280 authorization_details_types_supported: Default::default(),
281 dpop_signing_alg_values_supported: Default::default(),
282 dpop_bound_access_tokens_required: Default::default(),
283 })
284 }
285}
286
287pub fn create_protected_resource_metadata_url(path: &str) -> String {
288 format!(
289 "{OAUTH_PROTECTED_RESOURCE_BASE}{}",
290 if path == "/" { "" } else { path }
291 )
292}
293
294pub fn create_discovery_endpoints(
295 mcp_server_url: &str,
296) -> Result<(HashMap<String, OauthEndpoint>, String), McpSdkError> {
297 let mut endpoint_map = HashMap::new();
298 endpoint_map.insert(
299 WELL_KNOWN_OAUTH_AUTHORIZATION_SERVER.to_string(),
300 OauthEndpoint::AuthorizationServerMetadata,
301 );
302
303 let resource_url = Url::parse(mcp_server_url).map_err(|err| McpSdkError::Internal {
304 description: err.to_string(),
305 })?;
306
307 let relative_url = create_protected_resource_metadata_url(resource_url.path());
308 let base_url = url_base(&resource_url);
309 let protected_resource_metadata_url =
310 format!("{}{relative_url}", base_url.trim_end_matches('/'));
311
312 endpoint_map.insert(relative_url, OauthEndpoint::ProtectedResourceMetadata);
313
314 Ok((endpoint_map, protected_resource_metadata_url))
315}