contextvm_sdk/proxy/
mod.rs1use crate::core::error::{Error, Result};
7use crate::core::types::JsonRpcMessage;
8use crate::transport::client::{NostrClientTransport, NostrClientTransportConfig};
9
10#[non_exhaustive]
12pub struct ProxyConfig {
13 pub nostr_config: NostrClientTransportConfig,
15}
16
17impl ProxyConfig {
18 pub fn new(nostr_config: NostrClientTransportConfig) -> Self {
20 Self { nostr_config }
21 }
22}
23
24pub struct NostrMCPProxy {
26 transport: NostrClientTransport,
27 is_running: bool,
28}
29
30impl NostrMCPProxy {
31 pub async fn new<T>(signer: T, config: ProxyConfig) -> Result<Self>
33 where
34 T: nostr_sdk::prelude::IntoNostrSigner,
35 {
36 let transport = NostrClientTransport::new(signer, config.nostr_config).await?;
37
38 Ok(Self {
39 transport,
40 is_running: false,
41 })
42 }
43
44 pub async fn start(&mut self) -> Result<tokio::sync::mpsc::UnboundedReceiver<JsonRpcMessage>> {
46 if self.is_running {
47 return Err(Error::Other("Proxy already running".to_string()));
48 }
49
50 self.transport.start().await?;
51 self.is_running = true;
52
53 self.transport
54 .take_message_receiver()
55 .ok_or_else(|| Error::Other("Message receiver already taken".to_string()))
56 }
57
58 pub async fn send(&self, message: &JsonRpcMessage) -> Result<()> {
60 self.transport.send(message).await
61 }
62
63 pub async fn stop(&mut self) -> Result<()> {
65 if !self.is_running {
66 return Ok(());
67 }
68 self.transport.close().await?;
69 self.is_running = false;
70 Ok(())
71 }
72
73 pub fn is_active(&self) -> bool {
75 self.is_running
76 }
77}
78
79#[cfg(feature = "rmcp")]
80impl NostrMCPProxy {
81 pub async fn serve_client_handler<T, H>(
86 signer: T,
87 config: ProxyConfig,
88 handler: H,
89 ) -> Result<rmcp::service::RunningService<rmcp::RoleClient, H>>
90 where
91 T: nostr_sdk::prelude::IntoNostrSigner,
92 H: rmcp::ClientHandler,
93 {
94 use crate::NostrClientTransport;
95 use rmcp::ServiceExt;
96
97 let transport = NostrClientTransport::new(signer, config.nostr_config).await?;
98 handler
99 .serve(transport)
100 .await
101 .map_err(|e| Error::Other(format!("rmcp client initialization failed: {e}")))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::core::types::*;
109 use crate::transport::client::NostrClientTransportConfig;
110 use std::time::Duration;
111
112 #[test]
113 fn test_proxy_config_construction() {
114 let keys = nostr_sdk::Keys::generate();
115 let server_pubkey = keys.public_key().to_hex();
116
117 let nostr_config = NostrClientTransportConfig {
118 relay_urls: vec!["wss://relay.example.com".to_string()],
119 server_pubkey: server_pubkey.clone(),
120 encryption_mode: EncryptionMode::Required,
121 gift_wrap_mode: GiftWrapMode::Optional,
122 is_stateless: true,
123 timeout: Duration::from_secs(60),
124 log_file_path: None,
125 };
126
127 let config = ProxyConfig { nostr_config };
128
129 assert_eq!(
130 config.nostr_config.relay_urls,
131 vec!["wss://relay.example.com"]
132 );
133 assert_eq!(config.nostr_config.server_pubkey, server_pubkey);
134 assert_eq!(
135 config.nostr_config.encryption_mode,
136 EncryptionMode::Required
137 );
138 assert!(config.nostr_config.is_stateless);
139 assert_eq!(config.nostr_config.timeout, Duration::from_secs(60));
140 }
141
142 #[test]
143 fn test_proxy_config_with_defaults() {
144 let config = ProxyConfig {
145 nostr_config: NostrClientTransportConfig::default(),
146 };
147 assert!(!config.nostr_config.is_stateless);
148 assert_eq!(
149 config.nostr_config.encryption_mode,
150 EncryptionMode::Optional
151 );
152 }
153}