dnslib/mcp/tools/
resolve.rs1use rmcp::{ErrorData as McpError, model::*};
8
9use crate::{
10 cli::query::{QueryArgs, execute_query},
11 control_plane::{
12 config::AppConfig,
13 policy::{Policy, PolicyRule},
14 },
15 core::error::Error,
16 mcp::{helpers::mcp_err, params::ResolveParams},
17};
18
19pub async fn handle_resolve(
20 config: &AppConfig,
21 cli_access: &[PolicyRule],
22 cli_allow_zone: &[String],
23 p: ResolveParams,
24) -> Result<CallToolResult, McpError> {
25 tracing::info!(tool = "dns_resolve", "MCP tool invoked");
26
27 if let Some(ref server_id) = p.server_id
32 && let Ok(server) = config.selected_server(Some(server_id))
33 {
34 let policy = Policy::for_server(server, cli_access, cli_allow_zone).map_err(mcp_err)?;
35 policy.check_read().map_err(mcp_err)?;
36 }
37
38 let args = params_to_args(p).map_err(mcp_err)?;
39 let outcome = execute_query(Some(config.clone()), args)
40 .await
41 .map_err(mcp_err)?;
42
43 Ok(crate::mcp::helpers::json_result(outcome.to_json()))
44}
45
46fn params_to_args(p: ResolveParams) -> Result<QueryArgs, Error> {
47 let transports = p.transports.unwrap_or_default();
48 let mut args = QueryArgs {
49 targets: vec![p.domain],
50 r#type: p.types.unwrap_or_default(),
51 server: p.server_id,
52 at: p.at,
53 port: p.port,
54 tls_server_name: p.tls_server_name,
55 timeout: p.timeout_ms,
56 all: p.all_transports.unwrap_or(false),
57 json: true,
58 ..Default::default()
59 };
60 for transport in transports {
61 match transport.to_ascii_lowercase().as_str() {
62 "dns" => args.dns = true,
63 "dot" => args.dot = true,
64 "doh" => args.doh = true,
65 "doq" => args.doq = true,
66 other => {
67 return Err(Error::parse(format!(
68 "unknown transport '{other}' in `transports`; expected one of dns/dot/doh/doq",
69 )));
70 }
71 }
72 }
73 Ok(args)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn params_to_args_maps_transport_strings() {
82 let p = ResolveParams {
83 domain: "example.com".into(),
84 types: Some(vec!["A".into(), "AAAA".into()]),
85 server_id: Some("dns1".into()),
86 at: None,
87 transports: Some(vec!["dot".into(), "doh".into()]),
88 all_transports: None,
89 port: None,
90 tls_server_name: None,
91 timeout_ms: Some(1500),
92 };
93 let args = params_to_args(p).unwrap();
94 assert_eq!(args.targets, vec!["example.com".to_string()]);
95 assert_eq!(args.r#type, vec!["A".to_string(), "AAAA".to_string()]);
96 assert_eq!(args.server.as_deref(), Some("dns1"));
97 assert!(args.dot);
98 assert!(args.doh);
99 assert!(!args.dns);
100 assert!(!args.doq);
101 assert!(!args.all);
102 assert_eq!(args.timeout, Some(1500));
103 assert!(args.json);
105 }
106
107 #[test]
108 fn params_to_args_all_transports() {
109 let p = ResolveParams {
110 domain: "example.com".into(),
111 types: None,
112 server_id: Some("dns1".into()),
113 at: None,
114 transports: None,
115 all_transports: Some(true),
116 port: None,
117 tls_server_name: None,
118 timeout_ms: None,
119 };
120 let args = params_to_args(p).unwrap();
121 assert!(args.all);
122 }
123
124 #[test]
125 fn params_to_args_rejects_unknown_transport() {
126 let p = ResolveParams {
127 domain: "example.com".into(),
128 types: None,
129 server_id: None,
130 at: Some("1.1.1.1".into()),
131 transports: Some(vec!["smtp".into()]),
132 all_transports: None,
133 port: None,
134 tls_server_name: None,
135 timeout_ms: None,
136 };
137 assert!(params_to_args(p).is_err());
138 }
139}