diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 34e0908dd..dc600b869 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -138,16 +138,16 @@ type ProgressParams struct { // This notification MAY be associated with any channel — for example, an // agent advertised on the root channel, or a per-session resource. The // `channel` field identifies the subscription the auth requirement belongs -// to; the `resource` field carries the OAuth-protected resource identifier -// (per RFC 9728). +// to; the `resource` field carries the complete OAuth protected resource +// metadata (per RFC 9728). // // Clients should obtain a fresh token and push it via the `authenticate` // command. type AuthRequiredParams struct { // Channel URI this notification belongs to Channel URI `json:"channel"` - // The protected resource identifier that requires authentication - Resource string `json:"resource"` + // Complete RFC 9728 metadata for the protected resource that requires authentication + Resource ProtectedResourceMetadata `json:"resource"` // Why authentication is required Reason *AuthRequiredReason `json:"reason,omitempty"` } diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt index 64e93a151..8797fcdc3 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt @@ -120,9 +120,9 @@ data class AuthRequiredParams( */ val channel: String, /** - * The protected resource identifier that requires authentication + * Complete RFC 9728 metadata for the protected resource that requires authentication */ - val resource: String, + val resource: ProtectedResourceMetadata, /** * Why authentication is required */ diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/GeneratedStructsTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/GeneratedStructsTest.kt index 224e1c133..f792a4ff2 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/GeneratedStructsTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/GeneratedStructsTest.kt @@ -169,12 +169,15 @@ class GeneratedStructsTest { val authWire = """{ "channel": "ahp-root://", - "resource": "https://api.github.com", + "resource": { + "resource": "https://api.github.com", + "authorization_servers": ["https://github.com/login/oauth"] + }, "reason": "expired" }""".trimIndent() val auth = json.decodeFromString(AuthRequiredParams.serializer(), authWire) assertEquals("ahp-root://", auth.channel) - assertEquals("https://api.github.com", auth.resource) + assertEquals("https://api.github.com", auth.resource.resource) } @Test diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index e54e93769..fa17881b1 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -14,7 +14,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; #[allow(unused_imports)] use crate::state::{ AgentSelection, AnnotationsSummary, ChangesSummary, Changeset, FileEdit, ModelSelection, - ProjectInfo, SessionStatus, SessionSummary, + ProjectInfo, ProtectedResourceMetadata, SessionStatus, SessionSummary, }; // ─── Enums ──────────────────────────────────────────────────────────── @@ -153,8 +153,8 @@ pub struct ProgressParams { /// This notification MAY be associated with any channel — for example, an /// agent advertised on the root channel, or a per-session resource. The /// `channel` field identifies the subscription the auth requirement belongs -/// to; the `resource` field carries the OAuth-protected resource identifier -/// (per RFC 9728). +/// to; the `resource` field carries the complete OAuth protected resource +/// metadata (per RFC 9728). /// /// Clients should obtain a fresh token and push it via the `authenticate` /// command. @@ -163,8 +163,8 @@ pub struct ProgressParams { pub struct AuthRequiredParams { /// Channel URI this notification belongs to pub channel: Uri, - /// The protected resource identifier that requires authentication - pub resource: String, + /// Complete RFC 9728 metadata for the protected resource that requires authentication + pub resource: ProtectedResourceMetadata, /// Why authentication is required #[serde(default, skip_serializing_if = "Option::is_none")] pub reason: Option, diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index 4ffbb95c7..44147e2c6 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -102,14 +102,14 @@ public struct ProgressParams: Codable, Sendable { public struct AuthRequiredParams: Codable, Sendable { /// Channel URI this notification belongs to public var channel: String - /// The protected resource identifier that requires authentication - public var resource: String + /// Complete RFC 9728 metadata for the protected resource that requires authentication + public var resource: ProtectedResourceMetadata /// Why authentication is required public var reason: AuthRequiredReason? public init( channel: String, - resource: String, + resource: ProtectedResourceMetadata, reason: AuthRequiredReason? = nil ) { self.channel = channel diff --git a/docs/.changes/20260812-auth-required-resource-metadata.json b/docs/.changes/20260812-auth-required-resource-metadata.json new file mode 100644 index 000000000..288e4b751 --- /dev/null +++ b/docs/.changes/20260812-auth-required-resource-metadata.json @@ -0,0 +1,4 @@ +{ + "type": "changed", + "message": "`auth/required` now carries complete `ProtectedResourceMetadata` in its required `resource` field." +} diff --git a/docs/specification/authentication.md b/docs/specification/authentication.md index 9396dfb0c..294ec13f0 100644 --- a/docs/specification/authentication.md +++ b/docs/specification/authentication.md @@ -174,13 +174,18 @@ The server MAY send an [`auth/required`](/reference/common#authrequired) notific "method": "auth/required", "params": { "channel": "ahp-root://", - "resource": "https://api.github.com", + "resource": { + "resource": "https://api.github.com", + "resource_name": "GitHub Copilot", + "authorization_servers": ["https://github.com/login/oauth"], + "scopes_supported": ["read:user", "user:email"] + }, "reason": "expired" } } ``` -The `reason` field indicates why authentication is required: +The `resource` field carries the complete [`ProtectedResourceMetadata`](/reference/common#protectedresourcemetadata) object for the resource that requires authentication. The `reason` field indicates why authentication is required: | Value | Description | |---|---| diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index ab95e4b4c..1c977584a 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -7,15 +7,15 @@ "$defs": { "AuthRequiredParams": { "type": "object", - "description": "Sent by the server when a protected resource requires (re-)authentication.\n\nThis notification MAY be associated with any channel — for example, an\nagent advertised on the root channel, or a per-session resource. The\n`channel` field identifies the subscription the auth requirement belongs\nto; the `resource` field carries the OAuth-protected resource identifier\n(per RFC 9728).\n\nClients should obtain a fresh token and push it via the `authenticate`\ncommand.", + "description": "Sent by the server when a protected resource requires (re-)authentication.\n\nThis notification MAY be associated with any channel — for example, an\nagent advertised on the root channel, or a per-session resource. The\n`channel` field identifies the subscription the auth requirement belongs\nto; the `resource` field carries the complete OAuth protected resource\nmetadata (per RFC 9728).\n\nClients should obtain a fresh token and push it via the `authenticate`\ncommand.", "properties": { "channel": { "$ref": "#/$defs/URI", "description": "Channel URI this notification belongs to" }, "resource": { - "type": "string", - "description": "The protected resource identifier that requires authentication" + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "Complete RFC 9728 metadata for the protected resource that requires authentication" }, "reason": { "$ref": "#/$defs/AuthRequiredReason", diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 58a3bb9e7..11d46c18e 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -1611,7 +1611,7 @@ const NOTIFICATION_STRUCTS = [ function generateNotificationsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, AnnotationsSummary, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); + lines.push('use crate::state::{AgentSelection, AnnotationsSummary, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, ProtectedResourceMetadata, SessionStatus, SessionSummary};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/types/common/notifications.ts b/types/common/notifications.ts index 4be1f1c8a..8afe55988 100644 --- a/types/common/notifications.ts +++ b/types/common/notifications.ts @@ -5,7 +5,7 @@ * @module common/notifications */ -import type { URI } from './state.js'; +import type { ProtectedResourceMetadata, URI } from './state.js'; /** * Reason why authentication is required. @@ -27,8 +27,8 @@ export const enum AuthRequiredReason { * This notification MAY be associated with any channel — for example, an * agent advertised on the root channel, or a per-session resource. The * `channel` field identifies the subscription the auth requirement belongs - * to; the `resource` field carries the OAuth-protected resource identifier - * (per RFC 9728). + * to; the `resource` field carries the complete OAuth protected resource + * metadata (per RFC 9728). * * Clients should obtain a fresh token and push it via the `authenticate` * command. @@ -46,7 +46,11 @@ export const enum AuthRequiredReason { * "method": "auth/required", * "params": { * "channel": "ahp-root://", - * "resource": "https://api.github.com", + * "resource": { + * "resource": "https://api.github.com", + * "resource_name": "GitHub API", + * "authorization_servers": ["https://github.com/login/oauth"] + * }, * "reason": "expired" * } * } @@ -55,8 +59,8 @@ export const enum AuthRequiredReason { export interface AuthRequiredParams { /** Channel URI this notification belongs to */ channel: URI; - /** The protected resource identifier that requires authentication */ - resource: string; + /** Complete RFC 9728 metadata for the protected resource that requires authentication */ + resource: ProtectedResourceMetadata; /** Why authentication is required */ reason?: AuthRequiredReason; }