Skip to main content

rmcp_network/
lib.rs

1use network_interface::{NetworkInterface, NetworkInterfaceConfig, Addr};
2use rmcp::{
3    handler::server::{router::tool::ToolRouter, ServerHandler},
4    model::*,
5    ErrorData as McpError,
6};
7
8#[derive(Debug)]
9pub struct NetworkServer {
10    pub tool_router: ToolRouter<Self>,
11}
12
13impl Default for NetworkServer {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl NetworkServer {
20    pub fn new() -> Self {
21        Self {
22            tool_router: Self::tool_router(),
23        }
24    }
25
26    fn format_interfaces(interfaces: &[NetworkInterface]) -> String {
27        let mut result = String::from("Network Interfaces:\n\n");
28
29        if interfaces.is_empty() {
30            result.push_str("No network interfaces found.\n");
31            return result;
32        }
33
34        for iface in interfaces {
35            // Skip loopback for cleaner output (optional)
36            let is_loopback = iface.addr.iter().any(|a| match a {
37                Addr::V4(v4) => v4.ip.is_loopback(),
38                Addr::V6(v6) => v6.ip.is_loopback(),
39            });
40
41            result.push_str(&format!("{}", iface.name));
42            if is_loopback {
43                result.push_str(" (loopback)");
44            }
45            result.push('\n');
46
47            // MAC address if available
48            if let Some(ref mac) = iface.mac_addr {
49                if !mac.is_empty() && mac != "00:00:00:00:00:00" {
50                    result.push_str(&format!("  MAC: {}\n", mac));
51                }
52            }
53
54            // IP addresses
55            for addr in &iface.addr {
56                match addr {
57                    Addr::V4(v4) => {
58                        result.push_str(&format!("  IPv4: {}", v4.ip));
59                        if let Some(netmask) = &v4.netmask {
60                            result.push_str(&format!(" / {}", netmask));
61                        }
62                        result.push('\n');
63                    }
64                    Addr::V6(v6) => {
65                        // Skip link-local IPv6 for cleaner output
66                        if !v6.ip.to_string().starts_with("fe80") {
67                            result.push_str(&format!("  IPv6: {}\n", v6.ip));
68                        }
69                    }
70                }
71            }
72
73            result.push('\n');
74        }
75
76        // Summary
77        let active_count = interfaces.iter()
78            .filter(|i| !i.addr.is_empty())
79            .count();
80        result.push_str(&format!("Total interfaces: {} ({} with addresses)\n",
81            interfaces.len(), active_count));
82
83        result
84    }
85}
86
87#[rmcp::tool_router]
88impl NetworkServer {
89    #[rmcp::tool(description = "List all network interfaces with their IP addresses and MAC addresses")]
90    pub async fn get_interfaces(&self) -> Result<CallToolResult, McpError> {
91        let interfaces = NetworkInterface::show()
92            .map_err(|e| McpError::internal_error(format!("Failed to get network interfaces: {}", e), None))?;
93
94        let formatted = Self::format_interfaces(&interfaces);
95
96        Ok(CallToolResult::success(vec![Content::text(formatted)]))
97    }
98}
99
100#[rmcp::tool_handler]
101impl ServerHandler for NetworkServer {
102    fn get_info(&self) -> ServerInfo {
103        ServerInfo {
104            protocol_version: ProtocolVersion::V_2024_11_05,
105            capabilities: ServerCapabilities::builder()
106                .enable_tools()
107                .build(),
108            server_info: Implementation::from_build_env(),
109            instructions: Some("Cross-platform network interface information server".into()),
110        }
111    }
112}