rmcp_bluetooth/
lib.rs

1use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
2use btleplug::platform::Manager;
3use rmcp::{
4    handler::server::{router::tool::ToolRouter, ServerHandler},
5    model::*,
6    ErrorData as McpError,
7};
8use std::time::Duration;
9
10#[derive(Debug)]
11pub struct BluetoothServer {
12    pub tool_router: ToolRouter<Self>,
13}
14
15impl Default for BluetoothServer {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl BluetoothServer {
22    pub fn new() -> Self {
23        Self {
24            tool_router: Self::tool_router(),
25        }
26    }
27}
28
29#[rmcp::tool_router]
30impl BluetoothServer {
31    #[rmcp::tool(description = "Scan for nearby Bluetooth Low Energy (BLE) devices")]
32    pub async fn scan_ble_devices(&self) -> Result<CallToolResult, McpError> {
33        let manager = Manager::new().await
34            .map_err(|e| McpError::internal_error(format!("Failed to create BT manager: {}", e), None))?;
35
36        let adapters = manager.adapters().await
37            .map_err(|e| McpError::internal_error(format!("Failed to get adapters: {}", e), None))?;
38
39        if adapters.is_empty() {
40            return Ok(CallToolResult::success(vec![Content::text(
41                "Bluetooth Status:\n\nNo Bluetooth adapters found.\n"
42            )]));
43        }
44
45        let mut result = String::from("Bluetooth Devices:\n\n");
46
47        for adapter in adapters {
48            let adapter_info = adapter.adapter_info().await
49                .unwrap_or_else(|_| "Unknown adapter".to_string());
50            result.push_str(&format!("Adapter: {}\n\n", adapter_info));
51
52            // Start scanning
53            if let Err(e) = adapter.start_scan(ScanFilter::default()).await {
54                result.push_str(&format!("  Could not scan: {}\n", e));
55                continue;
56            }
57
58            // Wait a bit for devices to be discovered
59            tokio::time::sleep(Duration::from_secs(3)).await;
60
61            // Stop scanning
62            let _ = adapter.stop_scan().await;
63
64            // Get discovered peripherals
65            let peripherals = adapter.peripherals().await
66                .map_err(|e| McpError::internal_error(format!("Failed to get peripherals: {}", e), None))?;
67
68            if peripherals.is_empty() {
69                result.push_str("  No BLE devices found nearby.\n");
70            } else {
71                let mut count = 0;
72                for peripheral in peripherals {
73                    count += 1;
74
75                    let properties = peripheral.properties().await
76                        .ok()
77                        .flatten();
78
79                    let name = properties.as_ref()
80                        .and_then(|p| p.local_name.clone())
81                        .unwrap_or_else(|| "Unknown".to_string());
82
83                    let address = properties.as_ref()
84                        .map(|p| p.address.to_string())
85                        .unwrap_or_else(|| "??:??:??:??:??:??".to_string());
86
87                    let rssi = properties.as_ref()
88                        .and_then(|p| p.rssi)
89                        .map(|r| format!(" ({}dBm)", r))
90                        .unwrap_or_default();
91
92                    result.push_str(&format!("  {}. {}{}\n", count, name, rssi));
93                    result.push_str(&format!("     Address: {}\n", address));
94                }
95                result.push_str(&format!("\n  Total: {} BLE devices\n", count));
96            }
97        }
98
99        Ok(CallToolResult::success(vec![Content::text(result)]))
100    }
101}
102
103#[rmcp::tool_handler]
104impl ServerHandler for BluetoothServer {
105    fn get_info(&self) -> ServerInfo {
106        ServerInfo {
107            protocol_version: ProtocolVersion::V_2024_11_05,
108            capabilities: ServerCapabilities::builder()
109                .enable_tools()
110                .build(),
111            server_info: Implementation::from_build_env(),
112            instructions: Some("Cross-platform Bluetooth Low Energy device scanner".into()),
113        }
114    }
115}