contextvm_sdk/gateway/
mod.rs1use crate::core::error::{Error, Result};
7use crate::core::types::JsonRpcMessage;
8use crate::transport::server::{IncomingRequest, NostrServerTransport, NostrServerTransportConfig};
9
10#[derive(Debug, Clone)]
12#[non_exhaustive]
13pub struct GatewayConfig {
14 pub nostr_config: NostrServerTransportConfig,
16}
17
18impl GatewayConfig {
19 pub fn new(nostr_config: NostrServerTransportConfig) -> Self {
21 Self { nostr_config }
22 }
23}
24
25pub struct NostrMCPGateway {
30 transport: NostrServerTransport,
31 is_running: bool,
32}
33
34impl NostrMCPGateway {
35 pub async fn new<T>(signer: T, config: GatewayConfig) -> Result<Self>
37 where
38 T: nostr_sdk::prelude::IntoNostrSigner,
39 {
40 let transport = NostrServerTransport::new(signer, config.nostr_config).await?;
41
42 Ok(Self {
43 transport,
44 is_running: false,
45 })
46 }
47
48 pub async fn start(&mut self) -> Result<tokio::sync::mpsc::UnboundedReceiver<IncomingRequest>> {
53 if self.is_running {
54 return Err(Error::Other("Gateway already running".to_string()));
55 }
56
57 self.transport.start().await?;
58 self.transport.spawn_discoverability_publication();
59 self.is_running = true;
60
61 self.transport
62 .take_message_receiver()
63 .ok_or_else(|| Error::Other("Message receiver already taken".to_string()))
64 }
65
66 pub async fn send_response(&self, event_id: &str, response: JsonRpcMessage) -> Result<()> {
68 self.transport.send_response(event_id, response).await
69 }
70
71 pub async fn announce(&self) -> Result<nostr_sdk::EventId> {
73 self.transport.announce().await
74 }
75
76 pub async fn stop(&mut self) -> Result<()> {
78 if !self.is_running {
79 return Ok(());
80 }
81 self.transport.close().await?;
82 self.is_running = false;
83 Ok(())
84 }
85
86 pub fn is_active(&self) -> bool {
88 self.is_running
89 }
90}
91
92#[cfg(feature = "rmcp")]
93impl NostrMCPGateway {
94 pub async fn serve_handler<T, H>(
99 signer: T,
100 config: GatewayConfig,
101 handler: H,
102 ) -> Result<rmcp::service::RunningService<rmcp::RoleServer, H>>
103 where
104 T: nostr_sdk::prelude::IntoNostrSigner,
105 H: rmcp::ServerHandler,
106 {
107 use crate::NostrServerTransport;
108 use rmcp::ServiceExt;
109
110 let transport = NostrServerTransport::new(signer, config.nostr_config).await?;
111 handler
112 .serve(transport)
113 .await
114 .map_err(|e| Error::Other(format!("rmcp server initialization failed: {e}")))
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::core::types::*;
122 use crate::transport::server::NostrServerTransportConfig;
123 use std::time::Duration;
124
125 #[test]
126 fn test_gateway_config_construction() {
127 let nostr_config = NostrServerTransportConfig {
128 relay_urls: vec!["wss://relay.example.com".to_string()],
129 encryption_mode: EncryptionMode::Required,
130 gift_wrap_mode: GiftWrapMode::Optional,
131 server_info: Some(ServerInfo {
132 name: Some("Test Gateway".to_string()),
133 version: Some("1.0.0".to_string()),
134 ..Default::default()
135 }),
136 is_announced_server: true,
137 allowed_public_keys: vec!["abc123".to_string()],
138 excluded_capabilities: vec![],
139 max_sessions: 1000,
140 cleanup_interval: Duration::from_secs(120),
141 session_timeout: Duration::from_secs(600),
142 request_timeout: Duration::from_secs(60),
143 relay_list_urls: None,
144 bootstrap_relay_urls: None,
145 publish_relay_list: true,
146 profile_metadata: None,
147 oversized_transfer: Default::default(),
148 open_stream: Default::default(),
149 };
150
151 let config = GatewayConfig { nostr_config };
152
153 assert_eq!(
154 config.nostr_config.relay_urls,
155 vec!["wss://relay.example.com"]
156 );
157 assert_eq!(
158 config.nostr_config.encryption_mode,
159 EncryptionMode::Required
160 );
161 assert!(config.nostr_config.is_announced_server);
162 assert_eq!(config.nostr_config.allowed_public_keys.len(), 1);
163 assert!(
164 config
165 .nostr_config
166 .server_info
167 .as_ref()
168 .unwrap()
169 .name
170 .as_ref()
171 .unwrap()
172 == "Test Gateway"
173 );
174 }
175
176 #[test]
177 fn test_gateway_config_with_defaults() {
178 let config = GatewayConfig {
179 nostr_config: NostrServerTransportConfig::default(),
180 };
181 assert_eq!(
182 config.nostr_config.encryption_mode,
183 EncryptionMode::Optional
184 );
185 assert!(!config.nostr_config.is_announced_server);
186 }
187}