contextvm_sdk/proxy/
mod.rs1use crate::core::error::{Error, Result};
7use crate::core::types::JsonRpcMessage;
8use crate::transport::client::{NostrClientTransport, NostrClientTransportConfig};
9
10#[derive(Debug, Clone)]
12#[non_exhaustive]
13pub struct ProxyConfig {
14 pub nostr_config: NostrClientTransportConfig,
16}
17
18impl ProxyConfig {
19 pub fn new(nostr_config: NostrClientTransportConfig) -> Self {
21 Self { nostr_config }
22 }
23}
24
25pub struct NostrMCPProxy {
27 transport: NostrClientTransport,
28 is_running: bool,
29}
30
31impl NostrMCPProxy {
32 pub async fn new<T>(signer: T, config: ProxyConfig) -> Result<Self>
34 where
35 T: nostr_sdk::prelude::IntoNostrSigner,
36 {
37 let transport = NostrClientTransport::new(signer, config.nostr_config).await?;
38
39 Ok(Self {
40 transport,
41 is_running: false,
42 })
43 }
44
45 pub async fn start(&mut self) -> Result<tokio::sync::mpsc::UnboundedReceiver<JsonRpcMessage>> {
47 if self.is_running {
48 return Err(Error::Other("Proxy already running".to_string()));
49 }
50
51 self.transport.start().await?;
52 self.is_running = true;
53
54 self.transport
55 .take_message_receiver()
56 .ok_or_else(|| Error::Other("Message receiver already taken".to_string()))
57 }
58
59 pub async fn send(&self, message: &JsonRpcMessage) -> Result<()> {
61 self.transport.send(message).await
62 }
63
64 pub async fn stop(&mut self) -> Result<()> {
66 if !self.is_running {
67 return Ok(());
68 }
69 self.transport.close().await?;
70 self.is_running = false;
71 Ok(())
72 }
73
74 pub fn is_active(&self) -> bool {
76 self.is_running
77 }
78}
79
80#[cfg(feature = "rmcp")]
81impl NostrMCPProxy {
82 pub async fn serve_client_handler<T, H>(
87 signer: T,
88 config: ProxyConfig,
89 handler: H,
90 ) -> Result<rmcp::service::RunningService<rmcp::RoleClient, H>>
91 where
92 T: nostr_sdk::prelude::IntoNostrSigner,
93 H: rmcp::ClientHandler,
94 {
95 use crate::NostrClientTransport;
96 use rmcp::ServiceExt;
97
98 let transport = NostrClientTransport::new(signer, config.nostr_config).await?;
99 handler
100 .serve(transport)
101 .await
102 .map_err(|e| Error::Other(format!("rmcp client initialization failed: {e}")))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::core::types::*;
110 use crate::transport::client::NostrClientTransportConfig;
111 use std::time::Duration;
112
113 #[test]
114 fn test_proxy_config_construction() {
115 let keys = nostr_sdk::Keys::generate();
116 let server_pubkey = keys.public_key().to_hex();
117
118 let nostr_config = NostrClientTransportConfig {
119 relay_urls: vec!["wss://relay.example.com".to_string()],
120 server_pubkey: server_pubkey.clone(),
121 encryption_mode: EncryptionMode::Required,
122 gift_wrap_mode: GiftWrapMode::Optional,
123 is_stateless: true,
124 timeout: Duration::from_secs(60),
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}