Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

contextvm-sdk

License: MITRust

Rust SDK for the ContextVM protocolMCP over Nostr.

A complete implementation enabling Model Context Protocol (MCP) servers and clients to communicate over the Nostr network with decentralized discovery, cryptographic identity, and optional end-to-end encryption.

Architecture

┌──────────────────────────────────────────────────────────┐
│ Your Application │
├──────────────┬───────────────┬────────────────────────────┤
│ Gateway │ Proxy │ Discovery │
│ (server → │ (nostr → │ (find servers & │
│ nostr) │ client) │ capabilities) │
├──────────────┴───────────────┴────────────────────────────┤
│ Transport Layer │
│ NostrServerTransport / NostrClientTransport │
├───────────────────────────────────────────────────────────┤
│ Core │ Encryption │ Relay │ Signer │
│ (types, │ (NIP-44, │ (pool │ (key │
│ JSON-RPC, │ NIP-59 │ mgmt) │ mgmt) │
│ validation) │ gift wrap) │ │ │
├────────────────┴─────────────────┴───────────┴────────────┤
│ Nostr Network (relays) │
└───────────────────────────────────────────────────────────┘

Protocol

ContextVM maps MCP's JSON-RPC 2.0 messages onto Nostr events:

KindNameTypeDescription
25910ContextVM MessagesEphemeralMCP request/response/notification
1059Gift Wrap (NIP-59)RegularEncrypted MCP messages
21059Ephemeral Gift WrapEphemeralEncrypted MCP messages (CEP-19)
10002Relay List MetadataReplaceableServer's advertised relays (CEP-17)
11316Server AnnouncementAddressableServer identity & metadata
11317Tools ListAddressablePublished tool capabilities
11318Resources ListAddressablePublished resource capabilities
11319Resource TemplatesAddressablePublished resource template list
11320Prompts ListAddressablePublished prompt capabilities

Messages are routed using Nostr p tags (recipient pubkey) and correlated with e tags (request event ID).

Installation

Add to your Cargo.toml:

[dependencies]
contextvm-sdk = { git = "https://github.com/ContextVM/rs-sdk" }

Or pin a published release from crates.io:

[dependencies]
contextvm-sdk = "0.2.2"

Quick Start

Gateway — Expose a Local MCP Server via Nostr

use contextvm_sdk::gateway::{NostrMCPGateway,GatewayConfig};use contextvm_sdk::transport::server::NostrServerTransportConfig;use contextvm_sdk::core::types::{ServerInfo,EncryptionMode};use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = GatewayConfig::new(NostrServerTransportConfig::default().with_encryption_mode(EncryptionMode::Optional).with_server_info(ServerInfo::default().with_name("My MCP Server").with_about("Tools via Nostr"),).with_announced_server(true),);letmut gateway = NostrMCPGateway::new(keys, config).await?;letmut requests = gateway.start().await?;
gateway.announce().await?;whileletSome(req) = requests.recv().await{println!("Request: {:?}", req.message);// Process and respond:// gateway.send_response(&req.event_id, response).await?;}Ok(())}

Proxy — Connect to a Remote MCP Server via Nostr

use contextvm_sdk::proxy::{NostrMCPProxy,ProxyConfig};use contextvm_sdk::transport::client::NostrClientTransportConfig;use contextvm_sdk::core::types::EncryptionMode;use contextvm_sdk::signer;#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let config = ProxyConfig::new(NostrClientTransportConfig::default().with_server_pubkey("abc123...server_hex_pubkey").with_encryption_mode(EncryptionMode::Optional),);letmut proxy = NostrMCPProxy::new(keys, config).await?;letmut responses = proxy.start().await?;// Send an MCP requestlet request = contextvm_sdk::JsonRpcMessage::Request(contextvm_sdk::JsonRpcRequest{jsonrpc:"2.0".into(),id: serde_json::json!(1),method:"tools/list".into(),params:None,});
proxy.send(&request).await?;// Receive responseifletSome(msg) = responses.recv().await{println!("Response: {:?}", msg);}Ok(())}

Discovery — Find MCP Servers on Nostr

use contextvm_sdk::{discovery, signer,RelayPool};#[tokio::main]asyncfnmain() -> contextvm_sdk::Result<()>{let keys = signer::generate();let pool = RelayPool::new(keys).await?;let relays = vec!["wss://relay.damus.io".into()];
pool.connect(&relays).await?;let servers = discovery::discover_servers(pool.client(),&relays).await?;for server in&servers {println!("Server: {} ({:?})", server.pubkey, server.server_info.name);let tools = discovery::discover_tools(pool.client(),&server.pubkey_parsed,&relays).await?;println!(" Tools: {}", tools.len());}Ok(())}

Documentation

The in-repo Rust SDK guides live in docs/README.md:

Module Overview

ModuleDescription
coreProtocol constants, JSON-RPC types, error types, validation
transportClient/server Nostr transports with event loop and correlation
gatewayHigh-level gateway bridging local MCP servers to Nostr
proxyHigh-level proxy connecting to remote MCP servers via Nostr
discoveryServer/capability discovery via addressable Nostr events
encryptionNIP-44 encryption and NIP-59 gift wrapping
relayNostr relay pool management (connect, publish, subscribe)
signerKey generation and management utilities

Configuration

Encryption Modes

ModeBehavior
OptionalEncrypt responses if the incoming request was encrypted
RequiredAll messages must be encrypted (rejects plaintext)
DisabledNo encryption; all messages sent as plaintext kind 25910

Encryption uses NIP-44 for payload encryption and NIP-59 (Gift Wrap) for metadata-private delivery. Server announcements (kinds 11316–11320) are always public.

Messages too large for a single relay event are fragmented into ordered frames and reassembled by the receiver (CEP-22 oversized transfer, enabled by default; it adds no event kind — frames ride inside notifications/progress messages). See docs/oversized-transfer.md for the timeout model and tuning.

Open-ended streaming (CEP-41) lets a server tool emit an ordered sequence of chunks back to the client while a request is in flight. The client consumes them as an async Stream via call_tool_stream. Unlike CEP-22, the stream supplements the final JSON-RPC response rather than replacing it. Disabled by default; opt in with with_open_stream(OpenStreamConfig::enabled()). See docs/open-stream.md for the writer and client APIs and the keepalive timer model.

Server Transport Config

FieldDefaultDescription
relay_urls["wss://relay.damus.io"]Nostr relays to connect to
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
server_infoNoneServer metadata for announcements
is_announced_serverfalseAuto-publish announcements on start (CEP-6)
allowed_public_keys[] (allow all)Client pubkey allowlist (hex)
excluded_capabilities[]Methods exempt from allowlist
session_timeout300sInactive session expiry
relay_list_urlsNoneRelay URLs for kind 10002 (CEP-17); defaults to relay_urls
bootstrap_relay_urlsNoneAdditional relays for publishing announcements (CEP-6/17)
publish_relay_listtrueWhether to publish kind 10002 relay list metadata
profile_metadataNoneProfile metadata for kind 0 publication (CEP-23)
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

Client Transport Config

FieldDefaultDescription
relay_urls[]Nostr relays to connect to (empty = use relay resolution)
server_pubkey(required)Target server's public key (hex, npub, or nprofile)
encryption_modeOptionalEncryption policy
gift_wrap_modeOptionalGift-wrap policy (CEP-19): persistent (1059) vs ephemeral (21059)
is_statelessfalseEmulate initialize locally
timeout30sResponse timeout
discovery_relay_urlsNone (bootstrap relays)Relays for CEP-17 kind 10002 discovery
fallback_operational_relay_urlsNoneRelays probed in parallel with CEP-17 discovery
oversized_transferenabledCEP-22 oversized payload transfer config (guide)
open_streamdisabledCEP-41 open-stream config; opt-in (guide)

When relay_urls is empty, start() runs automatic relay resolution: configured relays > nprofile hints > CEP-17 kind 10002 discovery > fallback probing > bootstrap defaults.

References

License

MIT

About

Rust SDK for the ContextVM protocol

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages