name: client-dev description: Build MCP clients that connect to ContextVM servers over Nostr. Use when creating clients, discovering servers, connecting to remote servers, handling encrypted connections, or implementing the proxy pattern for existing MCP clients.
ContextVM Client Development
Build MCP clients that connect to ContextVM servers over the Nostr network.
Quick Start
Connect to a ContextVM server:
import { Client } from '@modelcontextprotocol/sdk/client'
import {
NostrClientTransport,
PrivateKeySigner,
ApplesauceRelayPool,
EncryptionMode,
} from '@contextvm/sdk'
const signer = new PrivateKeySigner(process.env.CLIENT_PRIVATE_KEY!)
const relayPool = new ApplesauceRelayPool(['wss://relay.contextvm.org', 'wss://cvm.otherstuff.ai'])
const SERVER_PUBKEY = 'server-public-key-hex'
const transport = new NostrClientTransport({
signer,
serverPubkey: SERVER_PUBKEY,
encryptionMode: EncryptionMode.OPTIONAL,
})
const client = new Client({
name: 'my-client',
version: '1.0.0',
})
await client.connect(transport)
// Use the client
const tools = await client.listTools()
const result = await client.callTool({
name: 'echo',
arguments: { message: 'Hello' },
})
Server Discovery
Direct Connection (Known Pubkey)
Connect when you know the server's public key:
const transport = new NostrClientTransport({
signer,
<<<<<<< feat/call
relayHandler: relayPool,
serverPubkey: 'known-server-pubkey',
=======
serverPubkey: "known-server-pubkey",
>>>>>>> main
});
Discovery via Announcements
Find servers broadcasting on the network:
import { CTXVM_MESSAGES_KIND, SERVER_ANNOUNCEMENT_KIND } from '@contextvm/sdk'
// Query relays for server announcements
await relayPool.subscribe([{ kinds: [SERVER_ANNOUNCEMENT_KIND] }], event => {
const serverInfo = JSON.parse(event.content)
console.log(`Found server: ${serverInfo.serverInfo.name}`)
console.log(`Pubkey: ${event.pubkey}`)
})
NostrClientTransport Options
| Option | Type | Description |
|---|---|---|
signer |
NostrSigner |
Required. Signs all Nostr events |
relayHandler |
RelayHandler | string[] |
Optional explicit operational relays |
serverPubkey |
string |
Required. Target server's public key |
discoveryRelayUrls |
string[] |
Optional relay URLs for CEP-17 discovery lookups |
encryptionMode |
EncryptionMode |
OPTIONAL, REQUIRED, or DISABLED |
isStateless |
boolean |
Skip initialization handshake. Default: false |
logLevel |
LogLevel |
Logging verbosity |
Relay Resolution Order
NostrClientTransport resolves operational relays in this order:
- explicit operational relays from
relayHandler - relay hints embedded in
nprofile - CEP-17 relay-list discovery via
discoveryRelayUrls - SDK bootstrap discovery relays when
discoveryRelayUrlsis omitted
This allows leaner client setup when the target server already publishes kind:10002 metadata.
Stateless Mode
Skip the initialization handshake for faster connections:
const transport = new NostrClientTransport({
signer,
serverPubkey: SERVER_PUBKEY,
isStateless: true, // Skip initialize roundtrip
})
Proxy Pattern
Use NostrMCPProxy to connect existing MCP clients to ContextVM servers:
import { NostrMCPProxy } from '@contextvm/sdk'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const proxy = new NostrMCPProxy({
// Local transport for existing client to connect to
mcpHostTransport: new StdioServerTransport(),
// Remote server connection
nostrTransportOptions: {
signer,
serverPubkey: SERVER_PUBKEY,
},
})
await proxy.start()
This allows any standard MCP client to use ContextVM servers without native support.
Encryption
Control encryption behavior:
// Require encrypted connections only
encryptionMode: EncryptionMode.REQUIRED
// Use encryption if server supports it (default)
encryptionMode: EncryptionMode.OPTIONAL
// Never use encryption
encryptionMode: EncryptionMode.DISABLED
Client Templates
See assets/client-template.ts for a complete boilerplate.
Tooling: ctxcn (generate a typed TypeScript client)
If you are building a TypeScript app and want remote ContextVM tools to feel like local functions, use ctxcn.
High-level behavior:
- Connects to a ContextVM server.
- Reads
tools/listschemas. - Generates TypeScript client code into your repo (shadcn-style: you own the generated code).
From ContextVM docs/blog references, the basic flow is:
npx @contextvm/ctxcn init
npx @contextvm/ctxcn add <server-pubkey>
npx @contextvm/ctxcn update
Use this when:
- You want end-to-end type safety.
- You want IDE autocomplete for server tools.
- You want to avoid hand-writing tool interfaces.
Reference Materials
references/nostr-way-without-sdks.md- The Nostr primitives behind CVM (raw events, JSON-RPC, manual implementation)references/discovery.md- Server discovery patternsreferences/proxy-pattern.md- Using NostrMCPProxyreferences/stateless-mode.md- Stateless connection details