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 {
122 match self {
123 Self::Decision { decision, .. } => Self::Decision {
124 decision,
125 context: Some(context),
126 },
127 Self::NoResult => Self::NoResult,
128 }
129 }
130}
131
132impl From<PermissionDecision> for PermissionResult {
133 fn from(value: PermissionDecision) -> Self {
134 Self::Decision {
135 decision: value,
136 context: None,
137 }
138 }
139}
140
141pub(crate) fn permission_handler_failure(message: &str) -> PermissionResult {
142 tracing::error!(error = message, "permission handler failed");
143 PermissionResult::user_not_available()
144}
145
146#[derive(Debug, Clone)]
148pub struct UserInputResponse {
149 pub answer: String,
151 pub was_freeform: bool,
153}
154
155#[derive(Debug, Clone, Serialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ExitPlanModeResult {
159 pub approved: bool,
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub selected_action: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub feedback: Option<String>,
167}
168
169impl Default for ExitPlanModeResult {
170 fn default() -> Self {
171 Self {
172 approved: true,
173 selected_action: None,
174 feedback: None,
175 }
176 }
177}
178
179#[non_exhaustive]
181#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum AutoModeSwitchResponse {
184 Yes,
186 YesAlways,
189 No,
192}
193
194#[async_trait]
203pub trait PermissionHandler: Send + Sync + 'static {
204 async fn handle(
206 &self,
207 session_id: SessionId,
208 request_id: RequestId,
209 data: PermissionRequestData,
210 ) -> PermissionResult;
211}
212
213#[async_trait]
217pub trait ElicitationHandler: Send + Sync + 'static {
218 async fn handle(
220 &self,
221 session_id: SessionId,
222 request_id: RequestId,
223 request: ElicitationRequest,
224 ) -> ElicitationResult;
225}
226
227#[derive(Debug, Clone)]
229pub struct McpAuthRequest {
230 pub request_id: RequestId,
232 pub server_name: String,
234 pub server_url: String,
236 pub reason: McpOauthRequestReason,
238 pub www_authenticate_params: Option<McpOauthWWWAuthenticateParams>,
240 pub resource_metadata: Option<String>,
242 pub static_client_config: Option<McpOauthRequiredStaticClientConfig>,
244}
245
246#[derive(Debug, Clone)]
248pub enum McpAuthResult {
249 Token {
251 access_token: String,
253 token_type: Option<String>,
255 expires_in: Option<i64>,
257 },
258 Cancelled,
260}
261
262impl McpAuthResult {
263 pub(crate) fn into_wire(self) -> McpOauthPendingRequestResponse {
264 match self {
265 Self::Token {
266 access_token,
267 token_type,
268 expires_in,
269 } => McpOauthPendingRequestResponse::Token(McpOauthPendingRequestResponseToken {
270 access_token,
271 token_type,
272 expires_in,
273 kind: McpOauthPendingRequestResponseTokenKind::Token,
274 }),
275 Self::Cancelled => {
276 McpOauthPendingRequestResponse::Cancelled(McpOauthPendingRequestResponseCancelled {
277 kind: McpOauthPendingRequestResponseCancelledKind::Cancelled,
278 })
279 }
280 }
281 }
282}
283
284#[async_trait]
286pub trait McpAuthHandler: Send + Sync + 'static {
287 async fn handle(
289 &self,
290 session_id: SessionId,
291 request_id: RequestId,
292 request: McpAuthRequest,
293 ) -> McpAuthResult;
294}
295
296#[async_trait]
301pub trait UserInputHandler: Send + Sync + 'static {
302 async fn handle(
305 &self,
306 session_id: SessionId,
307 question: String,
308 choices: Option<Vec<String>>,
309 allow_freeform: Option<bool>,
310 ) -> Option<UserInputResponse>;
311}
312
313#[async_trait]
316pub trait ExitPlanModeHandler: Send + Sync + 'static {
317 async fn handle(&self, session_id: SessionId, data: ExitPlanModeData) -> ExitPlanModeResult;
319}
320
321#[async_trait]
324pub trait AutoModeSwitchHandler: Send + Sync + 'static {
325 async fn handle(
329 &self,
330 session_id: SessionId,
331 error_code: Option<String>,
332 retry_after_seconds: Option<f64>,
333 ) -> AutoModeSwitchResponse;
334}
335
336#[derive(Debug, Clone)]
343pub struct ApproveAllHandler;
344
345#[async_trait]
346impl PermissionHandler for ApproveAllHandler {
347 async fn handle(
348 &self,
349 _session_id: SessionId,
350 _request_id: RequestId,
351 data: PermissionRequestData,
352 ) -> PermissionResult {
353 if data.managed_settings_enabled {
354 permission_handler_failure(
355 "ApproveAllHandler cannot be used when managed settings are enabled",
356 )
357 } else if data.managed_approval_required == Some(true) {
358 PermissionResult::no_result()
359 } else {
360 PermissionResult::approve_once()
361 }
362 }
363}
364
365#[derive(Debug, Clone)]
367pub struct DenyAllHandler;
368
369#[async_trait]
370impl PermissionHandler for DenyAllHandler {
371 async fn handle(
372 &self,
373 _session_id: SessionId,
374 _request_id: RequestId,
375 _data: PermissionRequestData,
376 ) -> PermissionResult {
377 PermissionResult::reject(None)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[tokio::test]
386 async fn approve_all_handler_returns_approved() {
387 let result = ApproveAllHandler
388 .handle(
389 SessionId::from("s1"),
390 RequestId::new("1"),
391 PermissionRequestData::default(),
392 )
393 .await;
394 assert!(matches!(
395 result,
396 PermissionResult::Decision {
397 decision: PermissionDecision::ApproveOnce(_),
398 ..
399 }
400 ));
401 }
402
403 #[tokio::test]
404 async fn approve_all_handler_fails_when_managed_settings_enabled() {
405 let result = ApproveAllHandler
406 .handle(
407 SessionId::from("s1"),
408 RequestId::new("1"),
409 PermissionRequestData {
410 managed_settings_enabled: true,
411 ..Default::default()
412 },
413 )
414 .await;
415 assert!(matches!(
416 result,
417 PermissionResult::Decision {
418 decision: PermissionDecision::UserNotAvailable(_),
419 ..
420 }
421 ));
422 }
423
424 #[tokio::test]
425 async fn approve_all_handler_leaves_managed_approval_pending() {
426 let result = ApproveAllHandler
427 .handle(
428 SessionId::from("s1"),
429 RequestId::new("1"),
430 PermissionRequestData {
431 managed_approval_required: Some(true),
432 ..Default::default()
433 },
434 )
435 .await;
436 assert!(matches!(result, PermissionResult::NoResult));
437 }
438
439 #[tokio::test]
440 async fn deny_all_handler_returns_denied() {
441 let result = DenyAllHandler
442 .handle(
443 SessionId::from("s1"),
444 RequestId::new("1"),
445 PermissionRequestData::default(),
446 )
447 .await;
448 assert!(matches!(
449 result,
450 PermissionResult::Decision {
451 decision: PermissionDecision::Reject(_),
452 ..
453 }
454 ));
455 }
456
457 #[test]
458 fn mcp_auth_result_token_converts_to_wire_response() {
459 let wire = McpAuthResult::Token {
460 access_token: "host-token".to_string(),
461 token_type: Some("Bearer".to_string()),
462 expires_in: Some(3600),
463 }
464 .into_wire();
465
466 match wire {
467 McpOauthPendingRequestResponse::Token(token) => {
468 assert_eq!(token.access_token, "host-token");
469 assert_eq!(token.token_type.as_deref(), Some("Bearer"));
470 assert_eq!(token.expires_in, Some(3600));
471 }
472 McpOauthPendingRequestResponse::Cancelled(_) => panic!("expected token response"),
473 }
474 }
475}