1use display_info::DisplayInfo;
2use rmcp::{
3 handler::server::{router::tool::ToolRouter, ServerHandler, wrapper::Parameters},
4 model::*,
5 ErrorData as McpError,
6};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Serialize, Deserialize, JsonSchema)]
12pub struct PointParams {
13 #[schemars(description = "X coordinate on screen")]
14 pub x: i32,
15 #[schemars(description = "Y coordinate on screen")]
16 pub y: i32,
17}
18
19#[derive(Debug, Serialize, Deserialize, JsonSchema)]
21pub struct NameParams {
22 #[schemars(description = "Display name to search for")]
23 pub name: String,
24}
25
26#[derive(Debug)]
27pub struct DisplayServer {
28 pub tool_router: ToolRouter<Self>,
29}
30
31impl Default for DisplayServer {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl DisplayServer {
38 pub fn new() -> Self {
39 Self {
40 tool_router: Self::tool_router(),
41 }
42 }
43
44 fn format_single_display(d: &DisplayInfo) -> String {
45 let mut result = String::new();
46
47 let primary = if d.is_primary { " (primary)" } else { "" };
49 result.push_str(&format!(
50 "{}{}\n",
51 if d.friendly_name.is_empty() { &d.name } else { &d.friendly_name },
52 primary
53 ));
54
55 result.push_str(&format!(" Resolution: {}x{}\n", d.width, d.height));
57 result.push_str(&format!(" Position: ({}, {})\n", d.x, d.y));
58
59 if d.width_mm > 0 && d.height_mm > 0 {
61 let diag_mm = ((d.width_mm.pow(2) + d.height_mm.pow(2)) as f32).sqrt();
62 let diag_inches = diag_mm / 25.4;
63 result.push_str(&format!(
64 " Physical: {}mm x {}mm (~{:.1}\")\n",
65 d.width_mm, d.height_mm, diag_inches
66 ));
67 }
68
69 if d.frequency > 0.0 {
71 result.push_str(&format!(" Refresh: {:.0}Hz\n", d.frequency));
72 }
73
74 if d.scale_factor != 1.0 {
76 result.push_str(&format!(" Scale: {:.0}%\n", d.scale_factor * 100.0));
77 }
78
79 if d.rotation != 0.0 {
81 result.push_str(&format!(" Rotation: {}°\n", d.rotation as i32));
82 }
83
84 result
85 }
86
87 fn format_display_info(displays: &[DisplayInfo]) -> String {
88 let mut result = String::from("Display Information:\n\n");
89
90 if displays.is_empty() {
91 result.push_str("No displays detected.\n");
92 return result;
93 }
94
95 for (i, d) in displays.iter().enumerate() {
96 result.push_str(&format!("Display {}: ", i + 1));
97 result.push_str(&Self::format_single_display(d));
98 result.push('\n');
99 }
100
101 result.push_str(&format!("Total displays: {}\n", displays.len()));
102 result
103 }
104}
105
106#[rmcp::tool_router]
107impl DisplayServer {
108 #[rmcp::tool(description = "Get display/monitor information (connected displays, resolutions, physical sizes)")]
109 pub async fn get_display_info(&self) -> Result<CallToolResult, McpError> {
110 let displays = DisplayInfo::all()
111 .map_err(|e| McpError::internal_error(format!("Failed to get display info: {}", e), None))?;
112
113 let formatted = Self::format_display_info(&displays);
114
115 Ok(CallToolResult::success(vec![Content::text(formatted)]))
116 }
117
118 #[rmcp::tool(description = "Get display info at specific screen coordinates (useful for determining which monitor contains a point)")]
119 pub async fn get_display_at_point(
120 &self,
121 Parameters(params): Parameters<PointParams>,
122 ) -> Result<CallToolResult, McpError> {
123 let display = DisplayInfo::from_point(params.x, params.y)
124 .map_err(|e| McpError::internal_error(format!("Failed to get display at ({}, {}): {}", params.x, params.y, e), None))?;
125
126 let formatted = format!(
127 "Display at ({}, {}):\n{}",
128 params.x, params.y,
129 Self::format_single_display(&display)
130 );
131
132 Ok(CallToolResult::success(vec![Content::text(formatted)]))
133 }
134
135 #[rmcp::tool(description = "Get display info by name")]
136 pub async fn get_display_by_name(
137 &self,
138 Parameters(params): Parameters<NameParams>,
139 ) -> Result<CallToolResult, McpError> {
140 let display = DisplayInfo::from_name(¶ms.name)
141 .map_err(|e| McpError::internal_error(format!("Failed to get display '{}': {}", params.name, e), None))?;
142
143 let formatted = Self::format_single_display(&display);
144
145 Ok(CallToolResult::success(vec![Content::text(formatted)]))
146 }
147}
148
149#[rmcp::tool_handler]
150impl ServerHandler for DisplayServer {
151 fn get_info(&self) -> ServerInfo {
152 ServerInfo {
153 protocol_version: ProtocolVersion::V_2024_11_05,
154 capabilities: ServerCapabilities::builder()
155 .enable_tools()
156 .build(),
157 server_info: Implementation::from_build_env(),
158 instructions: Some("Cross-platform display/monitor information server".into()),
159 }
160 }
161}