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.is_running = true;
59
60 self.transport
61 .take_message_receiver()
62 .ok_or_else(|| Error::Other("Message receiver already taken".to_string()))
63 }
64
65 pub async fn send_response(&self, event_id: &str, response: JsonRpcMessage) -> Result<()> {
67 self.transport.send_response(event_id, response).await
68 }
69
70 pub async fn announce(&self) -> Result<nostr_sdk::EventId> {
72 self.transport.announce().await
73 }
74
75 pub async fn stop(&mut self) -> Result<()> {
77 if !self.is_running {
78 return Ok(());
79 }
80 self.transport.close().await?;
81 self.is_running = false;
82 Ok(())
83 }
84
85 pub fn is_active(&self) -> bool {
87 self.is_running
88 }
89}
90
91#[cfg(feature = "rmcp")]
92impl NostrMCPGateway {
93 pub async fn serve_handler<T, H>(
98 signer: T,
99 config: GatewayConfig,
100 handler: H,
101 ) -> Result<rmcp::service::RunningService<rmcp::RoleServer, H>>
102 where
103 T: nostr_sdk::prelude::IntoNostrSigner,
104 H: rmcp::ServerHandler,
105 {
106 use crate::NostrServerTransport;
107 use rmcp::ServiceExt;
108
109 let transport = NostrServerTransport::new(signer, config.nostr_config).await?;
110 handler
111 .serve(transport)
112 .await
113 .map_err(|e| Error::Other(format!("rmcp server initialization failed: {e}")))
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::core::types::*;
121 use crate::transport::server::NostrServerTransportConfig;
122 use std::time::Duration;
123
124 #[test]
125 fn test_gateway_config_construction() {
126 let nostr_config = NostrServerTransportConfig {
127 relay_urls: vec!["wss://relay.example.com".to_string()],
128 encryption_mode: EncryptionMode::Required,
129 gift_wrap_mode: GiftWrapMode::Optional,
130 server_info: Some(ServerInfo {
131 name: Some("Test Gateway".to_string()),
132 version: Some("1.0.0".to_string()),
133 ..Default::default()
134 }),
135 is_announced_server: true,
136 allowed_public_keys: vec!["abc123".to_string()],
137 excluded_capabilities: vec![],
138 max_sessions: 1000,
139 cleanup_interval: Duration::from_secs(120),
140 session_timeout: Duration::from_secs(600),
141 request_timeout: Duration::from_secs(60),
142 };
143
144 let config = GatewayConfig { nostr_config };
145
146 assert_eq!(
147 config.nostr_config.relay_urls,
148 vec!["wss://relay.example.com"]
149 );
150 assert_eq!(
151 config.nostr_config.encryption_mode,
152 EncryptionMode::Required
153 );
154 assert!(config.nostr_config.is_announced_server);
155 assert_eq!(config.nostr_config.allowed_public_keys.len(), 1);
156 assert!(
157 config
158 .nostr_config
159 .server_info
160 .as_ref()
161 .unwrap()
162 .name
163 .as_ref()
164 .unwrap()
165 == "Test Gateway"
166 );
167 }
168
169 #[test]
170 fn test_gateway_config_with_defaults() {
171 let config = GatewayConfig {
172 nostr_config: NostrServerTransportConfig::default(),
173 };
174 assert_eq!(
175 config.nostr_config.encryption_mode,
176 EncryptionMode::Optional
177 );
178 assert!(!config.nostr_config.is_announced_server);
179 }
180}