rust_mcp_sdk/auth/
metadata.rs

1use std::borrow::Cow;
2
3use crate::{
4    auth::{AuthorizationServerMetadata, OauthProtectedResourceMetadata},
5    error::McpSdkError,
6    utils::join_url,
7};
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use thiserror::Error;
12use url::Url;
13
14pub const WELL_KNOWN_OAUTH_AUTHORIZATION_SERVER: &str = "/.well-known/oauth-authorization-server";
15pub const OAUTH_PROTECTED_RESOURCE_BASE: &str = "/.well-known/oauth-protected-resource";
16
17#[allow(unused)]
18#[derive(Hash, Eq, PartialEq, Clone)]
19pub enum OauthEndpoint {
20    AuthorizationEndpoint,
21    TokenEndpoint,
22    RegistrationEndpoint,
23    RevocationEndpoint,
24    IntrospectionEndpoint,
25    AuthorizationServerMetadata,
26    ProtectedResourceMetadata,
27}
28
29#[derive(Debug, Error)]
30pub enum AuthMetadateError {
31    #[error("Url Parse Error: {0}")]
32    Transport(#[from] url::ParseError),
33}
34
35pub struct AuthMetadataEndpoints {
36    pub protected_resource_endpoint: String,
37    pub authorization_server_endpoint: String,
38}
39
40// Builder struct to construct both OAuthMetadata and OAuthProtectedResourceMetadata
41
42#[derive(Default)]
43pub struct AuthMetadataBuilder<'a> {
44    // OAuthMetadata-specific fields
45    issuer: Option<Cow<'a, str>>,
46    authorization_endpoint: Option<Cow<'a, str>>,
47    token_endpoint: Option<Cow<'a, str>>,
48    registration_endpoint: Option<Cow<'a, str>>,
49    revocation_endpoint: Option<Cow<'a, str>>,
50    introspection_endpoint: Option<Cow<'a, str>>,
51    scopes_supported: Option<Vec<Cow<'a, str>>>,
52
53    response_types_supported: Option<Vec<Cow<'a, str>>>,
54    response_modes_supported: Option<Vec<Cow<'a, str>>>,
55    grant_types_supported: Option<Vec<Cow<'a, str>>>,
56    token_endpoint_auth_methods_supported: Option<Vec<Cow<'a, str>>>,
57    token_endpoint_auth_signing_alg_values_supported: Option<Vec<Cow<'a, str>>>,
58    revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<Cow<'a, str>>>,
59    revocation_endpoint_auth_methods_supported: Option<Vec<Cow<'a, str>>>,
60    introspection_endpoint_auth_methods_supported: Option<Vec<Cow<'a, str>>>,
61    introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<Cow<'a, str>>>,
62    code_challenge_methods_supported: Option<Vec<Cow<'a, str>>>,
63    service_documentation: Option<Cow<'a, str>>,
64
65    // OAuthProtectedResourceMetadata-specific fields
66    resource: Option<Cow<'a, str>>,
67    authorization_servers: Option<Vec<Cow<'a, str>>>,
68    required_scopes: Option<Vec<Cow<'a, str>>>,
69
70    jwks_uri: Option<Cow<'a, str>>,
71    bearer_methods_supported: Option<Vec<Cow<'a, str>>>,
72    resource_signing_alg_values_supported: Option<Vec<Cow<'a, str>>>,
73    resource_name: Option<Cow<'a, str>>,
74    resource_documentation: Option<Cow<'a, str>>,
75    resource_policy_uri: Option<Cow<'a, str>>,
76    resource_tos_uri: Option<Cow<'a, str>>,
77    tls_client_certificate_bound_access_tokens: Option<bool>,
78    authorization_details_types_supported: Option<Vec<Cow<'a, str>>>,
79    dpop_signing_alg_values_supported: Option<Vec<Cow<'a, str>>>,
80    dpop_bound_access_tokens_required: Option<bool>,
81
82    // none-standard
83    userinfo_endpoint: Option<Cow<'a, str>>,
84}
85
86// Result struct to hold both metadata types
87#[derive(Debug, Serialize, Deserialize, Clone)]
88pub struct OauthMetadata {
89    authorization_server_metadata: AuthorizationServerMetadata,
90    protected_resource_metadata: OauthProtectedResourceMetadata,
91}
92
93impl OauthMetadata {
94    pub fn protected_resource_metadata(&self) -> &OauthProtectedResourceMetadata {
95        &self.protected_resource_metadata
96    }
97
98    pub fn authorization_server_metadata(&self) -> &AuthorizationServerMetadata {
99        &self.authorization_server_metadata
100    }
101
102    pub fn endpoints(&self) -> AuthMetadataEndpoints {
103        AuthMetadataEndpoints {
104            authorization_server_endpoint: WELL_KNOWN_OAUTH_AUTHORIZATION_SERVER.to_string(),
105            protected_resource_endpoint: format!(
106                "{OAUTH_PROTECTED_RESOURCE_BASE}{}",
107                match self.protected_resource_metadata.resource.path() {
108                    "/" => "",
109                    other => other,
110                }
111            ),
112        }
113    }
114}
115
116impl<'a> AuthMetadataBuilder<'a> {
117    fn with_defaults(protected_resource: &'a str) -> Self {
118        Self {
119            response_types_supported: Some(vec!["code".into()]),
120            code_challenge_methods_supported: Some(vec!["S256".into()]),
121            token_endpoint_auth_methods_supported: Some(vec!["client_secret_post".into()]),
122            grant_types_supported: Some(vec!["authorization_code".into(), "refresh_token".into()]),
123            resource: Some(protected_resource.into()),
124            ..Default::default()
125        }
126    }
127
128    /// Creates a new instance of the builder for the given protected resource.
129    /// The `protected_resource` parameter must specify the full URL of the MCP server.
130    pub fn new(protected_resource_url: &'a str) -> Self {
131        Self::with_defaults(protected_resource_url)
132    }
133
134    pub async fn from_discovery_url<S>(
135        discovery_url: &str,
136        protected_resource: S,
137        required_scopes: Vec<S>,
138    ) -> Result<Self, McpSdkError>
139    where
140        S: Into<Cow<'a, str>>,
141    {
142        let client = Client::new();
143        let json: Value = client
144            .get(discovery_url)
145            .send()
146            .await
147            .map_err(|e| McpSdkError::Internal {
148                description: format!(
149                    "Failed to fetch discovery document : \"{discovery_url}\": {e}"
150                ),
151            })?
152            .error_for_status()
153            .map_err(|e| McpSdkError::Internal {
154                description: format!("Discovery endpoint returned error: {e}"),
155            })?
156            .json()
157            .await
158            .map_err(|e| McpSdkError::Internal {
159                description: format!("Failed to parse JSON from discovery document: {e}"),
160            })?;
161
162        // Helper to extract string field safely
163        let get_str = |key: &str| {
164            json.get(key)
165                .and_then(|v| v.as_str())
166                .map(|s| Cow::<str>::Owned(s.to_string()))
167        };
168        // Helper for optional array of strings
169        let get_str_array = |key: &str| {
170            json.get(key).and_then(|v| v.as_array()).map(|arr| {
171                arr.iter()
172                    .filter_map(|item| item.as_str())
173                    .filter(|v| !v.is_empty())
174                    .map(|s| Cow::<str>::Owned(s.to_string()))
175                    .collect::<Vec<_>>()
176            })
177        };
178
179        let issuer = get_str("issuer").ok_or_else(|| McpSdkError::Internal {
180            description: "Missing 'issuer' in discovery document".to_string(),
181        })?;
182
183        Ok(Self {
184            issuer: Some(issuer.clone()),
185            authorization_endpoint: get_str("authorization_endpoint"),
186            scopes_supported: get_str_array("scopes_supported"),
187            required_scopes: Some(required_scopes.into_iter().map(|s| s.into()).collect()),
188            token_endpoint: get_str("token_endpoint"),
189            jwks_uri: get_str("jwks_uri"),
190
191            userinfo_endpoint: get_str("userinfo_endpoint"),
192
193            registration_endpoint: get_str("registration_endpoint"),
194            revocation_endpoint: get_str("revocation_endpoint"),
195            introspection_endpoint: get_str("introspection_endpoint"),
196            response_types_supported: get_str_array("response_types_supported"),
197            response_modes_supported: get_str_array("response_modes_supported"),
198            grant_types_supported: get_str_array("grant_types_supported"),
199            token_endpoint_auth_methods_supported: get_str_array(
200                "token_endpoint_auth_methods_supported",
201            ),
202            token_endpoint_auth_signing_alg_values_supported: get_str_array(
203                "token_endpoint_auth_signing_alg_values_supported",
204            ),
205            revocation_endpoint_auth_signing_alg_values_supported: get_str_array(
206                "revocation_endpoint_auth_signing_alg_values_supported",
207            ),
208            revocation_endpoint_auth_methods_supported: get_str_array(
209                "revocation_endpoint_auth_methods_supported",
210            ),
211            introspection_endpoint_auth_methods_supported: get_str_array(
212                "introspection_endpoint_auth_methods_supported",
213            ),
214            introspection_endpoint_auth_signing_alg_values_supported: get_str_array(
215                "introspection_endpoint_auth_signing_alg_values_supported",
216            ),
217            code_challenge_methods_supported: get_str_array("code_challenge_methods_supported"),
218            service_documentation: get_str("service_documentation"),
219            resource: Some(protected_resource.into()),
220            authorization_servers: Some(vec![issuer]),
221            bearer_methods_supported: None,
222            resource_signing_alg_values_supported: None,
223            resource_name: None,
224            resource_documentation: None,
225            resource_policy_uri: None,
226            resource_tos_uri: None,
227            tls_client_certificate_bound_access_tokens: None,
228            authorization_details_types_supported: None,
229            dpop_signing_alg_values_supported: None,
230            dpop_bound_access_tokens_required: None,
231        })
232    }
233
234    fn parse_url_field<S>(
235        field_name: &str,
236        value: Option<S>,
237        base_url: Option<&Url>,
238    ) -> Result<Url, McpSdkError>
239    where
240        S: Into<Cow<'a, str>>,
241    {
242        let value = value
243            .ok_or(McpSdkError::Internal {
244                description: format!("Error: '{field_name}' is missing."),
245            })?
246            .into();
247
248        let url = if value.contains("://") {
249            // Absolute URL
250            Url::parse(&value)
251        } else if let Some(base_url) = base_url {
252            // Relative URL, join with base_url
253            join_url(base_url, &value)
254        } else {
255            // No base_url provided, try to parse as absolute URL anyway
256            Url::parse(&value)
257        };
258
259        url.map_err(|e| McpSdkError::Internal {
260            description: format!("Error: '{field_name}' is not a valid URL: {e}"),
261        })
262    }
263
264    fn parse_optional_url_field<S>(
265        field_name: &str,
266        value: Option<S>,
267        base_url: Option<&Url>,
268    ) -> Result<Option<Url>, McpSdkError>
269    where
270        S: Into<Cow<'a, str>>,
271    {
272        value
273            .map(|v| {
274                let value = v.into();
275                if value.contains("://") {
276                    // Absolute URL
277                    Url::parse(&value)
278                } else if let Some(base_url) = base_url {
279                    // Relative URL, join with base_url
280                    join_url(base_url, &value)
281                } else {
282                    // No base_url provided, try to parse as absolute URL anyway
283                    Url::parse(&value)
284                }
285            })
286            .transpose()
287            .map_err(|e| McpSdkError::Internal {
288                description: format!("Error: '{field_name}' is not a valid URL: {e}"),
289            })
290    }
291
292    pub fn scopes_supported<S>(mut self, scopes: Vec<S>) -> Self
293    where
294        S: Into<Cow<'a, str>>,
295    {
296        self.scopes_supported = Some(scopes.into_iter().map(|s| s.into()).collect());
297        self
298    }
299
300    // OAuthMetadata setters
301    pub fn issuer<S>(mut self, issuer: S) -> Self
302    where
303        S: Into<Cow<'a, str>>,
304    {
305        self.issuer = Some(issuer.into());
306        self
307    }
308
309    pub fn service_documentation<S>(mut self, url: S) -> Self
310    where
311        S: Into<Cow<'a, str>>,
312    {
313        self.service_documentation = Some(url.into());
314        self
315    }
316
317    pub fn authorization_endpoint<S>(mut self, url: S) -> Self
318    where
319        S: Into<Cow<'a, str>>,
320    {
321        self.authorization_endpoint = Some(url.into());
322        self
323    }
324
325    pub fn token_endpoint<S>(mut self, url: S) -> Self
326    where
327        S: Into<Cow<'a, str>>,
328    {
329        self.token_endpoint = Some(url.into());
330        self
331    }
332
333    pub fn response_types_supported<S>(mut self, types: Vec<S>) -> Self
334    where
335        S: Into<Cow<'a, str>>,
336    {
337        self.response_types_supported = Some(types.into_iter().map(|s| s.into()).collect());
338        self
339    }
340
341    pub fn response_modes_supported<S>(mut self, modes: Vec<S>) -> Self
342    where
343        S: Into<Cow<'a, str>>,
344    {
345        self.response_modes_supported = Some(modes.into_iter().map(|s| s.into()).collect());
346        self
347    }
348
349    pub fn registration_endpoint(mut self, url: &'a str) -> Self {
350        self.registration_endpoint = Some(url.into());
351        self
352    }
353
354    pub fn userinfo_endpoint(mut self, url: &'a str) -> Self {
355        self.userinfo_endpoint = Some(url.into());
356        self
357    }
358
359    pub fn grant_types_supported<S>(mut self, types: Vec<S>) -> Self
360    where
361        S: Into<Cow<'a, str>>,
362    {
363        self.grant_types_supported = Some(types.into_iter().map(|s| s.into()).collect());
364        self
365    }
366
367    pub fn token_endpoint_auth_methods_supported<S>(mut self, methods: Vec<S>) -> Self
368    where
369        S: Into<Cow<'a, str>>,
370    {
371        self.token_endpoint_auth_methods_supported =
372            Some(methods.into_iter().map(|s| s.into()).collect());
373        self
374    }
375
376    pub fn token_endpoint_auth_signing_alg_values_supported<S>(mut self, algs: Vec<S>) -> Self
377    where
378        S: Into<Cow<'a, str>>,
379    {
380        self.token_endpoint_auth_signing_alg_values_supported =
381            Some(algs.into_iter().map(|s| s.into()).collect());
382        self
383    }
384
385    pub fn revocation_endpoint(mut self, url: &'a str) -> Self {
386        self.revocation_endpoint = Some(url.into());
387        self
388    }
389
390    pub fn revocation_endpoint_auth_methods_supported<S>(mut self, methods: Vec<S>) -> Self
391    where
392        S: Into<Cow<'a, str>>,
393    {
394        self.revocation_endpoint_auth_methods_supported =
395            Some(methods.into_iter().map(|s| s.into()).collect());
396        self
397    }
398
399    pub fn revocation_endpoint_auth_signing_alg_values_supported<S>(mut self, algs: Vec<S>) -> Self
400    where
401        S: Into<Cow<'a, str>>,
402    {
403        self.revocation_endpoint_auth_signing_alg_values_supported =
404            Some(algs.into_iter().map(|s| s.into()).collect());
405        self
406    }
407
408    pub fn introspection_endpoint(mut self, endpoint: &'a str) -> Self {
409        self.introspection_endpoint = Some(endpoint.into());
410        self
411    }
412
413    pub fn introspection_endpoint_auth_methods_supported<S>(mut self, methods: Vec<S>) -> Self
414    where
415        S: Into<Cow<'a, str>>,
416    {
417        self.introspection_endpoint_auth_methods_supported =
418            Some(methods.into_iter().map(|s| s.into()).collect());
419        self
420    }
421
422    pub fn introspection_endpoint_auth_signing_alg_values_supported<S>(
423        mut self,
424        algs: Vec<String>,
425    ) -> Self
426    where
427        S: Into<Cow<'a, str>>,
428    {
429        self.introspection_endpoint_auth_signing_alg_values_supported =
430            Some(algs.into_iter().map(|s| s.into()).collect());
431        self
432    }
433
434    pub fn code_challenge_methods_supported<S>(mut self, methods: Vec<S>) -> Self
435    where
436        S: Into<Cow<'a, str>>,
437    {
438        self.code_challenge_methods_supported =
439            Some(methods.into_iter().map(|s| s.into()).collect());
440        self
441    }
442
443    // OAuthProtectedResourceMetadata setters
444    pub fn resource(mut self, url: &'a str) -> Self {
445        self.resource = Some(url.into());
446        self
447    }
448
449    pub fn authorization_servers(mut self, servers: Vec<&'a str>) -> Self {
450        self.authorization_servers = Some(servers.into_iter().map(|s| s.into()).collect());
451        self
452    }
453
454    pub fn reqquired_scopes<S>(mut self, scopes: Vec<S>) -> Self
455    where
456        S: Into<Cow<'a, str>>,
457    {
458        self.required_scopes = Some(scopes.into_iter().map(|s| s.into()).collect());
459        self
460    }
461
462    pub fn resource_documentation<S>(mut self, doc: String) -> Self
463    where
464        S: Into<Cow<'a, str>>,
465    {
466        self.resource_documentation = Some(doc.into());
467        self
468    }
469
470    pub fn jwks_uri(mut self, url: &'a str) -> Self {
471        self.jwks_uri = Some(url.into());
472        self
473    }
474
475    pub fn bearer_methods_supported<S>(mut self, methods: Vec<S>) -> Self
476    where
477        S: Into<Cow<'a, str>>,
478    {
479        self.bearer_methods_supported = Some(methods.into_iter().map(|s| s.into()).collect());
480        self
481    }
482
483    pub fn resource_signing_alg_values_supported<S>(mut self, algs: Vec<S>) -> Self
484    where
485        S: Into<Cow<'a, str>>,
486    {
487        self.resource_signing_alg_values_supported =
488            Some(algs.into_iter().map(|s| s.into()).collect());
489        self
490    }
491
492    pub fn resource_name<S>(mut self, name: S) -> Self
493    where
494        S: Into<Cow<'a, str>>,
495    {
496        self.resource_name = Some(name.into());
497        self
498    }
499
500    pub fn resource_policy_uri(mut self, url: &'a str) -> Self {
501        self.resource_policy_uri = Some(url.into());
502        self
503    }
504
505    pub fn resource_tos_uri(mut self, url: &'a str) -> Self {
506        self.resource_tos_uri = Some(url.into());
507        self
508    }
509
510    pub fn tls_client_certificate_bound_access_tokens(mut self, value: bool) -> Self {
511        self.tls_client_certificate_bound_access_tokens = Some(value);
512        self
513    }
514
515    pub fn authorization_details_types_supported<S>(mut self, types: Vec<S>) -> Self
516    where
517        S: Into<Cow<'a, str>>,
518    {
519        self.authorization_details_types_supported =
520            Some(types.into_iter().map(|s| s.into()).collect());
521        self
522    }
523
524    pub fn dpop_signing_alg_values_supported<S>(mut self, algs: Vec<S>) -> Self
525    where
526        S: Into<Cow<'a, str>>,
527    {
528        self.dpop_signing_alg_values_supported = Some(algs.into_iter().map(|s| s.into()).collect());
529        self
530    }
531
532    pub fn dpop_bound_access_tokens_required(mut self, value: bool) -> Self {
533        self.dpop_bound_access_tokens_required = Some(value);
534        self
535    }
536
537    // Build method to construct OauthMetadata
538    pub fn build(
539        self,
540    ) -> Result<(AuthorizationServerMetadata, OauthProtectedResourceMetadata), McpSdkError> {
541        let issuer = Self::parse_url_field("issuer", self.issuer, None)?;
542
543        let authorization_endpoint = Self::parse_url_field(
544            "authorization_endpoint",
545            self.authorization_endpoint,
546            Some(&issuer),
547        )?;
548
549        let token_endpoint =
550            Self::parse_url_field("token_endpoint", self.token_endpoint, Some(&issuer))?;
551
552        let registration_endpoint = Self::parse_optional_url_field(
553            "registration_endpoint",
554            self.registration_endpoint,
555            Some(&issuer),
556        )?;
557
558        let revocation_endpoint = Self::parse_optional_url_field(
559            "revocation_endpoint",
560            self.revocation_endpoint,
561            Some(&issuer),
562        )?;
563
564        let introspection_endpoint = Self::parse_optional_url_field(
565            "introspection_endpoint",
566            self.introspection_endpoint,
567            Some(&issuer),
568        )?;
569
570        let service_documentation = Self::parse_optional_url_field(
571            "service_documentation",
572            self.service_documentation,
573            None,
574        )?;
575
576        let jwks_uri = Self::parse_optional_url_field("jwks_uri", self.jwks_uri, Some(&issuer))?;
577
578        let authorization_server_metadata = AuthorizationServerMetadata {
579            issuer,
580            authorization_endpoint,
581            token_endpoint,
582            registration_endpoint,
583            service_documentation,
584            revocation_endpoint,
585            introspection_endpoint,
586            userinfo_endpoint: self.userinfo_endpoint.map(|v| v.into()),
587            response_types_supported: self
588                .response_types_supported
589                .unwrap_or_default()
590                .into_iter() // iterate over Cow<'a, str>
591                .map(|c| c.into_owned())
592                .collect(),
593            response_modes_supported: self
594                .response_modes_supported
595                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
596            scopes_supported: self
597                .scopes_supported
598                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
599            grant_types_supported: self
600                .grant_types_supported
601                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
602            token_endpoint_auth_methods_supported: self
603                .token_endpoint_auth_methods_supported
604                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
605            token_endpoint_auth_signing_alg_values_supported: self
606                .token_endpoint_auth_signing_alg_values_supported
607                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
608            revocation_endpoint_auth_signing_alg_values_supported: self
609                .revocation_endpoint_auth_signing_alg_values_supported
610                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
611            revocation_endpoint_auth_methods_supported: self
612                .revocation_endpoint_auth_methods_supported
613                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
614            introspection_endpoint_auth_methods_supported: self
615                .introspection_endpoint_auth_methods_supported
616                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
617            introspection_endpoint_auth_signing_alg_values_supported: self
618                .introspection_endpoint_auth_signing_alg_values_supported
619                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
620            code_challenge_methods_supported: self
621                .code_challenge_methods_supported
622                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
623            jwks_uri: jwks_uri.clone(),
624        };
625
626        let resource = Self::parse_url_field("resource", self.resource, None)?;
627        let resource_policy_uri =
628            Self::parse_optional_url_field("resource_policy_uri", self.resource_policy_uri, None)?;
629        let resource_tos_uri =
630            Self::parse_optional_url_field("resource_tos_uri", self.resource_tos_uri, None)?;
631
632        // Validate mandatory authorization_servers
633        let authorization_servers =
634            self.authorization_servers
635                .ok_or_else(|| McpSdkError::Internal {
636                    description: "Error: 'authorization_servers' is missing".to_string(),
637                })?;
638        if authorization_servers.is_empty() {
639            return Err(McpSdkError::Internal {
640                description: "Error: 'authorization_servers' must contain at least one URL"
641                    .to_string(),
642            });
643        }
644        let authorization_servers = authorization_servers
645            .iter()
646            .map(|url| {
647                Url::parse(url).map_err(|err| McpSdkError::Internal {
648                    description: format!(
649                        "Error: 'authorization_servers' contains invalid URL '{url}': {err}",
650                    ),
651                })
652            })
653            .collect::<Result<Vec<_>, _>>()?;
654
655        let protected_resource_metadata = OauthProtectedResourceMetadata {
656            resource,
657            authorization_servers,
658            jwks_uri,
659            scopes_supported: self
660                .required_scopes
661                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
662            bearer_methods_supported: self
663                .bearer_methods_supported
664                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
665            resource_signing_alg_values_supported: self
666                .resource_signing_alg_values_supported
667                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
668            resource_name: self.resource_name.map(|s| s.into()),
669            resource_documentation: self.resource_documentation.map(|s| s.into()),
670            resource_policy_uri,
671            resource_tos_uri,
672            tls_client_certificate_bound_access_tokens: self
673                .tls_client_certificate_bound_access_tokens,
674            authorization_details_types_supported: self
675                .authorization_details_types_supported
676                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
677            dpop_signing_alg_values_supported: self
678                .dpop_signing_alg_values_supported
679                .map(|v| v.into_iter().map(|c| c.into_owned()).collect()),
680            dpop_bound_access_tokens_required: self.dpop_bound_access_tokens_required,
681        };
682
683        Ok((authorization_server_metadata, protected_resource_metadata))
684    }
685}