Skip to main content

fastmcp_console/
detection.rs

1//! Agent/human context detection
2//!
3//! Determines whether rich output should be enabled based on the execution context.
4
5/// Display context representing the environment
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum DisplayContext {
8    /// Agent context - plain output for machine parsing
9    Agent,
10    /// Human context - rich styled output
11    #[default]
12    Human,
13}
14
15impl DisplayContext {
16    /// Create an agent (plain output) context
17    #[must_use]
18    pub fn new_agent() -> Self {
19        Self::Agent
20    }
21
22    /// Create a human (rich output) context
23    #[must_use]
24    pub fn new_human() -> Self {
25        Self::Human
26    }
27
28    /// Auto-detect the display context from environment
29    #[must_use]
30    pub fn detect() -> Self {
31        if should_enable_rich() {
32            Self::Human
33        } else {
34            Self::Agent
35        }
36    }
37
38    /// Check if this is a human context (rich output enabled)
39    #[must_use]
40    pub fn is_human(&self) -> bool {
41        matches!(self, Self::Human)
42    }
43
44    /// Check if this is an agent context (plain output)
45    #[must_use]
46    pub fn is_agent(&self) -> bool {
47        matches!(self, Self::Agent)
48    }
49}
50
51/// Determine if we're running in an agent context
52#[must_use]
53pub fn is_agent_context() -> bool {
54    // MCP clients set these when spawning servers
55    std::env::var("MCP_CLIENT").is_ok()
56        || std::env::var("CLAUDE_CODE").is_ok()
57        || std::env::var("CODEX_CLI").is_ok()
58        || std::env::var("CURSOR_SESSION").is_ok()
59        // Generic agent indicators
60        || std::env::var("CI").is_ok()
61        || std::env::var("AGENT_MODE").is_ok()
62        // Explicit rich disable
63        || std::env::var("FASTMCP_PLAIN").is_ok()
64        || std::env::var("NO_COLOR").is_ok()
65}
66
67/// Determine if rich output should be enabled
68#[must_use]
69pub fn should_enable_rich() -> bool {
70    // Explicit enable always wins
71    if std::env::var("FASTMCP_RICH").is_ok() {
72        return true;
73    }
74
75    // In agent context, disable rich by default
76    if is_agent_context() {
77        return false;
78    }
79
80    // Check if stderr is a terminal (human watching)
81    // For now we assume true if not an agent, but ideally we check is_terminal
82    // rich_rust::console::Console handles this internally too, but we need to know upfront
83    // for detection.
84
85    // We'll leave it to Console to decide based on force_terminal=false default if not explicit
86    true
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_display_context_new_agent() {
95        let ctx = DisplayContext::new_agent();
96        assert!(ctx.is_agent());
97        assert!(!ctx.is_human());
98    }
99
100    #[test]
101    fn test_display_context_new_human() {
102        let ctx = DisplayContext::new_human();
103        assert!(ctx.is_human());
104        assert!(!ctx.is_agent());
105    }
106
107    #[test]
108    fn test_display_context_default_is_human() {
109        let ctx = DisplayContext::default();
110        assert!(ctx.is_human());
111    }
112
113    #[test]
114    fn test_display_context_equality() {
115        assert_eq!(DisplayContext::Agent, DisplayContext::Agent);
116        assert_eq!(DisplayContext::Human, DisplayContext::Human);
117        assert_ne!(DisplayContext::Agent, DisplayContext::Human);
118    }
119
120    #[test]
121    fn test_display_context_clone() {
122        let ctx = DisplayContext::Agent;
123        let cloned = ctx;
124        assert_eq!(ctx, cloned);
125    }
126
127    #[test]
128    fn test_display_context_debug() {
129        let ctx = DisplayContext::Agent;
130        let debug_str = format!("{:?}", ctx);
131        assert!(debug_str.contains("Agent"));
132    }
133}