client-dev
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