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]
303pub trait UserInputHandler: Send + Sync + 'static {
304 async fn handle(
307 &self,
308 session_id: SessionId,
309 question: String,
310 choices: Option<Vec<String>>,
311 allow_freeform: Option<bool>,
312 ) -> Option<UserInputResponse>;
313}
314
315#[async_trait]
318pub trait ExitPlanModeHandler: Send + Sync + 'static {
319 async fn handle(&self, session_id: SessionId, data: ExitPlanModeData) -> ExitPlanModeResult;
321}
322
323#[async_trait]
326pub trait AutoModeSwitchHandler: Send + Sync + 'static {
327 async fn handle(
331 &self,
332 session_id: SessionId,
333 error_code: Option<String>,
334 retry_after_seconds: Option<f64>,
335 ) -> AutoModeSwitchResponse;
336}
337
338#[derive(Debug, Clone)]
345pub struct ApproveAllHandler;
346
347#[async_trait]
348impl PermissionHandler for ApproveAllHandler {
349 async fn handle(
350 &self,
351 _session_id: SessionId,
352 _request_id: RequestId,
353 data: PermissionRequestData,
354 ) -> PermissionResult {
355 if data.managed_settings_enabled {
356 permission_handler_failure(
357 "ApproveAllHandler cannot be used when managed settings are enabled",
358 )
359 } else if data.managed_approval_required == Some(true) {
360 PermissionResult::no_result()
361 } else {
362 PermissionResult::approve_once()
363 }
364 }
365}
366
367#[derive(Debug, Clone)]
369pub struct DenyAllHandler;
370
371#[async_trait]
372impl PermissionHandler for DenyAllHandler {
373 async fn handle(
374 &self,
375 _session_id: SessionId,
376 _request_id: RequestId,
377 _data: PermissionRequestData,
378 ) -> PermissionResult {
379 PermissionResult::reject(None)
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[tokio::test]
388 async fn approve_all_handler_returns_approved() {
389 let result = ApproveAllHandler
390 .handle(
391 SessionId::from("s1"),
392 RequestId::new("1"),
393 PermissionRequestData::default(),
394 )
395 .await;
396 assert!(matches!(
397 result,
398 PermissionResult::Decision {
399 decision: PermissionDecision::ApproveOnce(_),
400 ..
401 }
402 ));
403 }
404
405 #[tokio::test]
406 async fn approve_all_handler_fails_when_managed_settings_enabled() {
407 let result = ApproveAllHandler
408 .handle(
409 SessionId::from("s1"),
410 RequestId::new("1"),
411 PermissionRequestData {
412 managed_settings_enabled: true,
413 ..Default::default()
414 },
415 )
416 .await;
417 assert!(matches!(
418 result,
419 PermissionResult::Decision {
420 decision: PermissionDecision::UserNotAvailable(_),
421 ..
422 }
423 ));
424 }
425
426 #[tokio::test]
427 async fn approve_all_handler_leaves_managed_approval_pending() {
428 let result = ApproveAllHandler
429 .handle(
430 SessionId::from("s1"),
431 RequestId::new("1"),
432 PermissionRequestData {
433 managed_approval_required: Some(true),
434 ..Default::default()
435 },
436 )
437 .await;
438 assert!(matches!(result, PermissionResult::NoResult));
439 }
440
441 #[tokio::test]
442 async fn deny_all_handler_returns_denied() {
443 let result = DenyAllHandler
444 .handle(
445 SessionId::from("s1"),
446 RequestId::new("1"),
447 PermissionRequestData::default(),
448 )
449 .await;
450 assert!(matches!(
451 result,
452 PermissionResult::Decision {
453 decision: PermissionDecision::Reject(_),
454 ..
455 }
456 ));
457 }
458
459 #[test]
460 fn mcp_auth_result_token_converts_to_wire_response() {
461 let wire = McpAuthResult::Token {
462 access_token: "host-token".to_string(),
463 token_type: Some("Bearer".to_string()),
464 expires_in: Some(3600),
465 }
466 .into_wire();
467
468 match wire {
469 McpOauthPendingRequestResponse::Token(token) => {
470 assert_eq!(token.access_token, "host-token");
471 assert_eq!(token.token_type.as_deref(), Some("Bearer"));
472 assert_eq!(token.expires_in, Some(3600));
473 }
474 McpOauthPendingRequestResponse::Cancelled(_) => panic!("expected token response"),
475 }
476 }
477}