Nostr Client Transport
Nostr Client Transport
Section titled “Nostr Client Transport”The NostrClientTransport is a key component of the @contextvm/sdk, enabling MCP clients to communicate with remote MCP servers over the Nostr network. It implements the Transport interface from the @modelcontextprotocol/sdk, making it a plug-and-play solution for any MCP client.
Overview
Section titled “Overview”The NostrClientTransport handles all the complexities of Nostr-based communication, including:
- Connecting to Nostr relays.
- Subscribing to events from a specific server.
- Sending MCP requests as Nostr events.
- Receiving and processing responses and notifications.
- Handling encryption and decryption of messages.
By using this transport, an MCP client can interact with a Nostr-enabled MCP server without needing to implement any Nostr-specific logic itself.
NostrTransportOptions
Section titled “NostrTransportOptions”To create an instance of NostrClientTransport, you must provide a configuration object that implements the NostrTransportOptions interface:
export interface NostrTransportOptions extends BaseNostrTransportOptions { serverPubkey: string; isStateless?: boolean;}serverPubkey: The public key of the target MCP server. The transport will only listen for events from this public key.isStateless(optional): When set totrue, enables stateless mode for the client transport. In stateless mode, the client emulates the server’s initialize response without requiring a full server initialization roundtrip. This enables faster startup and reduced network overhead. Default isfalse.
Usage Example
Section titled “Usage Example”Here’s how you can use the NostrClientTransport with an MCP client from the @modelcontextprotocol/sdk:
import { Client } from "@modelcontextprotocol/sdk/client";import { NostrClientTransport } from "@contextvm/sdk";import { EncryptionMode } from "@contextvm/sdk";import { PrivateKeySigner } from "@contextvm/sdk";import { SimpleRelayPool } from "@contextvm/sdk";
// 1. Configure the signer and relay handlerconst signer = new PrivateKeySigner("your-private-key"); // Replace with your actual private keyconst relayPool = new SimpleRelayPool(["wss://relay.damus.io"]);
// 2. Set the public key of the target serverconst REMOTE_SERVER_PUBKEY = "remote-server-public-key";
// 3. Create the transport instanceconst clientNostrTransport = new NostrClientTransport({ signer, relayHandler: relayPool, serverPubkey: REMOTE_SERVER_PUBKEY, encryptionMode: EncryptionMode.OPTIONAL,});
// 4. Create and connect the MCP clientconst mcpClient = new Client({ name: "My Client", version: "1.0.0",});
await mcpClient.connect(clientNostrTransport);
// 5. Use the client to interact with the serverconst tools = await mcpClient.listTools();console.log("Available tools:", tools);
// 6. Close the connection when done// await mcpClient.close();How It Works
Section titled “How It Works”start(): WhenmcpClient.connect()is called, it internally calls the transport’sstart()method. This method connects to the relays and subscribes to events targeting the client’s public key.- In stateless mode (
isStateless: true), the client emulates the server’s initialize response without sending it over the network, skipping thenotifications/initializedmessage exchange. - In standard mode (
isStateless: falseor undefined), the client performs the full initialization roundtrip with the server.
- In stateless mode (
send(message): When you call an MCP method likemcpClient.listTools(), the client creates a JSON-RPC request and passes it to the transport’ssend()method. The transport then:- Wraps the message in a Nostr event.
- Signs the event.
- Optionally encrypts it.
- Publishes it to the relays, targeting the
serverPubkey.
- Event Processing: The transport listens for incoming events. When an event is received, it is decrypted (if necessary) and converted back into a JSON-RPC message.
- If the message is a response (correlated by the original event ID), it is passed to the MCP client to resolve the pending request.
- If the message is a notification, it is emitted through the
onmessagehandler.
Stateless Mode
Section titled “Stateless Mode”The stateless mode is designed to optimize performance by reducing the initialization overhead:
- Faster Startup: By emulating the initialize response, the client can begin operations immediately without waiting for server response.
- Reduced Network Overhead: Eliminates the need for the initialization roundtrip.
- Use Cases: Ideal for scenarios where the client needs to quickly connect and interact with the server, such as in serverless functions or short-lived processes.
To enable stateless mode, set isStateless: true in the transport configuration:
const clientNostrTransport = new NostrClientTransport({ signer, relayHandler: relayPool, serverPubkey: REMOTE_SERVER_PUBKEY, encryptionMode: EncryptionMode.OPTIONAL, isStateless: true, // Enable stateless mode});Note: The stateless mode might not work with all servers.
Next Steps
Section titled “Next Steps”Next, we will look at the server-side counterpart to this transport:
- Nostr Server Transport: For exposing MCP servers to the Nostr network.