1use async_trait::async_trait;
19use serde::{Deserialize, Serialize};
20
21use crate::generated::api_types::{
22 McpOauthPendingRequestResponse, McpOauthPendingRequestResponseCancelled,
23 McpOauthPendingRequestResponseCancelledKind, McpOauthPendingRequestResponseToken,
24 McpOauthPendingRequestResponseTokenKind, PermissionDecision, PermissionDecisionApproveOnce,
25 PermissionDecisionContext, PermissionDecisionReject, PermissionDecisionUserNotAvailable,
26};
27use crate::session_events::{
28 McpOauthRequestReason, McpOauthRequiredStaticClientConfig, McpOauthWWWAuthenticateParams,
29};
30use crate::types::{
31 ElicitationRequest, ElicitationResult, ExitPlanModeData, PermissionRequestData, RequestId,
32 SessionId,
33};
34
35#[derive(Debug, Clone)]
54pub enum PermissionResult {
55 Decision {
57 decision: PermissionDecision,
59 context: Option<PermissionDecisionContext>,
61 },
62 NoResult,
65}
66
67impl PermissionResult {
68 pub fn approve_once() -> Self {
70 Self::Decision {
71 decision: PermissionDecision::ApproveOnce(PermissionDecisionApproveOnce::default()),
72 context: None,
73 }
74 }
75
76 pub fn reject(feedback: impl Into<Option<String>>) -> Self {
78 Self::Decision {
79 decision: PermissionDecision::Reject(PermissionDecisionReject {
80 feedback: feedback.into(),
81 ..Default::default()
82 }),
83 context: None,
84 }
85 }
86
87 pub fn user_not_available() -> Self {
89 Self::Decision {
90 decision: PermissionDecision::UserNotAvailable(
91 PermissionDecisionUserNotAvailable::default(),
92 ),
93 context: None,
94 }
95 }
96
97 pub fn no_result() -> Self {
100 Self::NoResult
101 }
102
103 pub fn with_context(self, context: PermissionDecisionContext) -> Self {
123 match self {
124 Self::Decision { decision, .. } => Self::Decision {
125 decision,
126 context: Some(context),
127 },
128 Self::NoResult => Self::NoResult,
129 }
130 }
131}
132
133impl From<PermissionDecision> for PermissionResult {
134 fn from(value: PermissionDecision) -> Self {
135 Self::Decision {
136 decision: value,
137 context: None,
138 }
139 }
140}
141
142pub(crate) fn permission_handler_failure(message: &str) -> PermissionResult {
143 tracing::error!(error = message, "permission handler failed");
144 PermissionResult::user_not_available()
145}
146
147#[derive(Debug, Clone)]
149pub struct UserInputResponse {
150 pub answer: String,
152 pub was_freeform: bool,
154}
155
156#[derive(Debug, Clone, Serialize)]
158#[serde(rename_all = "camelCase")]
159pub struct ExitPlanModeResult {
160 pub approved: bool,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub selected_action: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub feedback: Option<String>,
168}
169
170impl Default for ExitPlanModeResult {
171 fn default() -> Self {
172 Self {
173 approved: true,
174 selected_action: None,
175 feedback: None,
176 }
177 }
178}
179
180#[non_exhaustive]
182#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
183#[serde(rename_all = "snake_case")]
184pub enum AutoModeSwitchResponse {
185 Yes,
187 YesAlways,
190 No,
193}
194
195#[async_trait]
204pub trait PermissionHandler: Send + Sync + 'static {
205 async fn handle(
207 &self,
208 session_id: SessionId,
209 request_id: RequestId,
210 data: PermissionRequestData,
211 ) -> PermissionResult;
212}
213
214#[async_trait]
218pub trait ElicitationHandler: Send + Sync + 'static {
219 async fn handle(
221 &self,
222 session_id: SessionId,
223 request_id: RequestId,
224 request: ElicitationRequest,
225 ) -> ElicitationResult;
226}
227
228#[derive(Debug, Clone)]
230pub struct McpAuthRequest {
231 pub request_id: RequestId,
233 pub server_name: String,
235 pub server_url: String,
237 pub reason: McpOauthRequestReason,
239 pub www_authenticate_params: Option<McpOauthWWWAuthenticateParams>,
241 pub resource_metadata: Option<String>,
243 pub static_client_config: Option<McpOauthRequiredStaticClientConfig>,
245}
246
247#[derive(Debug, Clone)]
249pub enum McpAuthResult {
250 Token {
252 access_token: String,
254 token_type: Option<String>,
256 expires_in: Option<i64>,
258 },
259 Cancelled,
261}
262
263impl McpAuthResult {
264 pub(crate) fn into_wire(self) -> McpOauthPendingRequestResponse {
265 match self {
266 Self::Token {
267 access_token,
268 token_type,
269 expires_in,
270 } => McpOauthPendingRequestResponse::Token(McpOauthPendingRequestResponseToken {
271 access_token,
272 token_type,
273 expires_in,
274 kind: McpOauthPendingRequestResponseTokenKind::Token,
275 }),
276 Self::Cancelled => {
277 McpOauthPendingRequestResponse::Cancelled(McpOauthPendingRequestResponseCancelled {
278 kind: McpOauthPendingRequestResponseCancelledKind::Cancelled,
279 })
280 }
281 }
282 }
283}
284
285#[async_trait]
287pub trait McpAuthHandler: Send + Sync + 'static {
288 async fn handle(
290 &self,
291 session_id: SessionId,
292 request_id: RequestId,
293 request: McpAuthRequest,
294 ) -> McpAuthResult;
295}
296
297#[async_trait]
302pub trait UserInputHandler: Send + Sync + 'static {
303 async fn handle(
306 &self,
307 session_id: SessionId,
308 question: String,
309 choices: Option<Vec<String>>,
310 allow_freeform: Option<bool>,
311 ) -> Option<UserInputResponse>;
312}
313
314#[async_trait]
317pub trait ExitPlanModeHandler: Send + Sync + 'static {
318 async fn handle(&self, session_id: SessionId, data: ExitPlanModeData) -> ExitPlanModeResult;
320}
321
322#[async_trait]
325pub trait AutoModeSwitchHandler: Send + Sync + 'static {
326 async fn handle(
330 &self,
331 session_id: SessionId,
332 error_code: Option<String>,
333 retry_after_seconds: Option<f64>,
334 ) -> AutoModeSwitchResponse;
335}
336
337#[derive(Debug, Clone)]
344pub struct ApproveAllHandler;
345
346#[async_trait]
347impl PermissionHandler for ApproveAllHandler {
348 async fn handle(
349 &self,
350 _session_id: SessionId,
351 _request_id: RequestId,
352 data: PermissionRequestData,
353 ) -> PermissionResult {
354 if data.managed_settings_enabled {
355 permission_handler_failure(
356 "ApproveAllHandler cannot be used when managed settings are enabled",
357 )
358 } else if data.managed_approval_required == Some(true) {
359 PermissionResult::no_result()
360 } else {
361 PermissionResult::approve_once()
362 }
363 }
364}
365
366#[derive(Debug, Clone)]
368pub struct DenyAllHandler;
369
370#[async_trait]
371impl PermissionHandler for DenyAllHandler {
372 async fn handle(
373 &self,
374 _session_id: SessionId,
375 _request_id: RequestId,
376 _data: PermissionRequestData,
377 ) -> PermissionResult {
378 PermissionResult::reject(None)
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[tokio::test]
387 async fn approve_all_handler_returns_approved() {
388 let result = ApproveAllHandler
389 .handle(
390 SessionId::from("s1"),
391 RequestId::new("1"),
392 PermissionRequestData::default(),
393 )
394 .await;
395 assert!(matches!(
396 result,
397 PermissionResult::Decision {
398 decision: PermissionDecision::ApproveOnce(_),
399 ..
400 }
401 ));
402 }
403
404 #[tokio::test]
405 async fn approve_all_handler_fails_when_managed_settings_enabled() {
406 let result = ApproveAllHandler
407 .handle(
408 SessionId::from("s1"),
409 RequestId::new("1"),
410 PermissionRequestData {
411 managed_settings_enabled: true,
412 ..Default::default()
413 },
414 )
415 .await;
416 assert!(matches!(
417 result,
418 PermissionResult::Decision {
419 decision: PermissionDecision::UserNotAvailable(_),
420 ..
421 }
422 ));
423 }
424
425 #[tokio::test]
426 async fn approve_all_handler_leaves_managed_approval_pending() {
427 let result = ApproveAllHandler
428 .handle(
429 SessionId::from("s1"),
430 RequestId::new("1"),
431 PermissionRequestData {
432 managed_approval_required: Some(true),
433 ..Default::default()
434 },
435 )
436 .await;
437 assert!(matches!(result, PermissionResult::NoResult));
438 }
439
440 #[tokio::test]
441 async fn deny_all_handler_returns_denied() {
442 let result = DenyAllHandler
443 .handle(
444 SessionId::from("s1"),
445 RequestId::new("1"),
446 PermissionRequestData::default(),
447 )
448 .await;
449 assert!(matches!(
450 result,
451 PermissionResult::Decision {
452 decision: PermissionDecision::Reject(_),
453 ..
454 }
455 ));
456 }
457
458 #[test]
459 fn mcp_auth_result_token_converts_to_wire_response() {
460 let wire = McpAuthResult::Token {
461 access_token: "host-token".to_string(),
462 token_type: Some("Bearer".to_string()),
463 expires_in: Some(3600),
464 }
465 .into_wire();
466
467 match wire {
468 McpOauthPendingRequestResponse::Token(token) => {
469 assert_eq!(token.access_token, "host-token");
470 assert_eq!(token.token_type.as_deref(), Some("Bearer"));
471 assert_eq!(token.expires_in, Some(3600));
472 }
473 McpOauthPendingRequestResponse::Cancelled(_) => panic!("expected token response"),
474 }
475 }
476}