systemprompt_models/oauth/protected_resource.rs
1//! RFC 9728 protected-resource metadata.
2//!
3//! One shape for both directions of the handshake: our routes serialise it to
4//! answer `/.well-known/oauth-protected-resource`, and our MCP client
5//! deserialises whatever a server points its `WWW-Authenticate` challenge at.
6//! Keeping a single declaration is what stops the advertised document and the
7//! parsed one drifting apart.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use serde::{Deserialize, Serialize};
13
14use crate::mcp::McpExtensionId;
15
16#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct ProtectedResourceMetadata {
18 pub resource: String,
19 #[serde(default)]
20 pub authorization_servers: Vec<String>,
21 #[serde(default)]
22 pub scopes_supported: Vec<String>,
23 #[serde(default)]
24 pub bearer_methods_supported: Vec<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub resource_documentation: Option<String>,
27 /// MCP extensions the resource requires of a client, by extension id.
28 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub mcp_extensions_supported: Vec<McpExtensionId>,
30}
31
32impl ProtectedResourceMetadata {
33 /// Whether the resource requires the Enterprise-Managed Authorization
34 /// flow — an IdP-issued ID-JAG rather than an interactive authorization
35 /// redirect the client may have no user present to complete.
36 #[must_use]
37 pub fn requires_enterprise_managed_auth(&self) -> bool {
38 self.mcp_extensions_supported
39 .contains(&McpExtensionId::EnterpriseManagedAuth)
40 }
41}