1use std::sync::Arc;
30use std::time::Duration;
31
32use nostr_sdk::prelude::*;
33
34use crate::core::constants::*;
35use crate::core::error::{Error, Result};
36use crate::core::types::ServerInfo;
37
38#[derive(Debug, Clone)]
40pub struct ServerAnnouncement {
41 pub pubkey: String,
43 pub pubkey_parsed: PublicKey,
45 pub server_info: ServerInfo,
47 pub event_id: EventId,
49 pub created_at: Timestamp,
51}
52
53pub async fn discover_servers(
55 client: &Arc<Client>,
56 _relay_urls: &[String],
57) -> Result<Vec<ServerAnnouncement>> {
58 let filter = Filter::new().kind(Kind::Custom(SERVER_ANNOUNCEMENT_KIND));
59
60 let events = client
61 .fetch_events(filter, Duration::from_secs(10))
62 .await
63 .map_err(|e| Error::Transport(e.to_string()))?;
64
65 let mut announcements = Vec::new();
66 for event in events {
67 let server_info: ServerInfo = serde_json::from_str(&event.content).unwrap_or_default();
68 announcements.push(ServerAnnouncement {
69 pubkey: event.pubkey.to_hex(),
70 pubkey_parsed: event.pubkey,
71 server_info,
72 event_id: event.id,
73 created_at: event.created_at,
74 });
75 }
76
77 Ok(announcements)
78}
79
80pub async fn discover_tools(
82 client: &Arc<Client>,
83 server_pubkey: &PublicKey,
84 _relay_urls: &[String],
85) -> Result<Vec<serde_json::Value>> {
86 fetch_list(client, server_pubkey, TOOLS_LIST_KIND, "tools").await
87}
88
89pub async fn discover_resources(
91 client: &Arc<Client>,
92 server_pubkey: &PublicKey,
93 _relay_urls: &[String],
94) -> Result<Vec<serde_json::Value>> {
95 fetch_list(client, server_pubkey, RESOURCES_LIST_KIND, "resources").await
96}
97
98pub async fn discover_prompts(
100 client: &Arc<Client>,
101 server_pubkey: &PublicKey,
102 _relay_urls: &[String],
103) -> Result<Vec<serde_json::Value>> {
104 fetch_list(client, server_pubkey, PROMPTS_LIST_KIND, "prompts").await
105}
106
107pub async fn discover_resource_templates(
109 client: &Arc<Client>,
110 server_pubkey: &PublicKey,
111 _relay_urls: &[String],
112) -> Result<Vec<serde_json::Value>> {
113 fetch_list(
114 client,
115 server_pubkey,
116 RESOURCETEMPLATES_LIST_KIND,
117 "resourceTemplates",
118 )
119 .await
120}
121
122#[cfg(feature = "rmcp")]
124pub async fn discover_tools_typed(
125 client: &Arc<Client>,
126 server_pubkey: &PublicKey,
127 relay_urls: &[String],
128) -> Result<Vec<rmcp::model::Tool>> {
129 let raw = discover_tools(client, server_pubkey, relay_urls).await?;
130 parse_typed_list(raw)
131}
132
133#[cfg(feature = "rmcp")]
135pub async fn discover_resources_typed(
136 client: &Arc<Client>,
137 server_pubkey: &PublicKey,
138 relay_urls: &[String],
139) -> Result<Vec<rmcp::model::Resource>> {
140 let raw = discover_resources(client, server_pubkey, relay_urls).await?;
141 parse_typed_list(raw)
142}
143
144#[cfg(feature = "rmcp")]
146pub async fn discover_prompts_typed(
147 client: &Arc<Client>,
148 server_pubkey: &PublicKey,
149 relay_urls: &[String],
150) -> Result<Vec<rmcp::model::Prompt>> {
151 let raw = discover_prompts(client, server_pubkey, relay_urls).await?;
152 parse_typed_list(raw)
153}
154
155#[cfg(feature = "rmcp")]
157pub async fn discover_resource_templates_typed(
158 client: &Arc<Client>,
159 server_pubkey: &PublicKey,
160 relay_urls: &[String],
161) -> Result<Vec<rmcp::model::ResourceTemplate>> {
162 let raw = discover_resource_templates(client, server_pubkey, relay_urls).await?;
163 parse_typed_list(raw)
164}
165
166async fn fetch_list(
169 client: &Arc<Client>,
170 server_pubkey: &PublicKey,
171 kind: u16,
172 list_key: &str,
173) -> Result<Vec<serde_json::Value>> {
174 let filter = Filter::new()
175 .kind(Kind::Custom(kind))
176 .author(*server_pubkey);
177
178 let events = client
179 .fetch_events(filter, Duration::from_secs(10))
180 .await
181 .map_err(|e| Error::Transport(e.to_string()))?;
182
183 let event = match events.into_iter().next() {
185 Some(e) => e,
186 None => return Ok(Vec::new()),
187 };
188
189 let parsed: serde_json::Value =
190 serde_json::from_str(&event.content).map_err(|e| Error::Other(e.to_string()))?;
191
192 Ok(parsed
193 .get(list_key)
194 .and_then(|v| v.as_array())
195 .cloned()
196 .unwrap_or_default())
197}
198
199#[cfg(feature = "rmcp")]
200fn parse_typed_list<T>(raw: Vec<serde_json::Value>) -> Result<Vec<T>>
201where
202 T: serde::de::DeserializeOwned,
203{
204 let mut parsed = Vec::new();
205 for item in raw {
206 let value = serde_json::from_value(item)
207 .map_err(|e| Error::Other(format!("Failed to parse typed discovery item: {e}")))?;
208 parsed.push(value);
209 }
210 Ok(parsed)
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::core::types::ServerInfo;
217
218 #[test]
219 fn test_server_info_serialization() {
220 let info = ServerInfo {
221 name: Some("Test Server".to_string()),
222 version: Some("1.0.0".to_string()),
223 about: Some("A test MCP server".to_string()),
224 website: Some("https://example.com".to_string()),
225 picture: Some("https://example.com/pic.png".to_string()),
226 };
227
228 let json = serde_json::to_string(&info).unwrap();
229 let parsed: ServerInfo = serde_json::from_str(&json).unwrap();
230
231 assert_eq!(parsed.name, Some("Test Server".to_string()));
232 assert_eq!(parsed.version, Some("1.0.0".to_string()));
233 assert_eq!(parsed.about, Some("A test MCP server".to_string()));
234 assert_eq!(parsed.website, Some("https://example.com".to_string()));
235 assert_eq!(
236 parsed.picture,
237 Some("https://example.com/pic.png".to_string())
238 );
239 }
240
241 #[test]
242 fn test_server_info_default() {
243 let info = ServerInfo::default();
244 assert!(info.name.is_none());
245 assert!(info.version.is_none());
246 assert!(info.about.is_none());
247 assert!(info.website.is_none());
248 assert!(info.picture.is_none());
249 }
250
251 #[test]
252 fn test_server_info_partial_serialization() {
253 let info = ServerInfo {
254 name: Some("Minimal".to_string()),
255 ..Default::default()
256 };
257
258 let json = serde_json::to_string(&info).unwrap();
259 assert!(!json.contains("version"));
261 assert!(!json.contains("about"));
262 assert!(json.contains("Minimal"));
263 }
264
265 #[test]
266 fn test_server_info_deserialization_from_empty() {
267 let info: ServerInfo = serde_json::from_str("{}").unwrap();
268 assert!(info.name.is_none());
269 }
270
271 #[test]
272 fn test_server_announcement_struct() {
273 let keys = nostr_sdk::Keys::generate();
274 let pubkey = keys.public_key();
275
276 let announcement = ServerAnnouncement {
277 pubkey: pubkey.to_hex(),
278 pubkey_parsed: pubkey,
279 server_info: ServerInfo {
280 name: Some("Test".to_string()),
281 ..Default::default()
282 },
283 event_id: EventId::from_hex(
284 "0000000000000000000000000000000000000000000000000000000000000001",
285 )
286 .unwrap(),
287 created_at: Timestamp::now(),
288 };
289
290 assert_eq!(announcement.pubkey, pubkey.to_hex());
291 assert_eq!(announcement.server_info.name, Some("Test".to_string()));
292 }
293}