# Client Dev

> 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.

- Skill: `contextvm/client-dev` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add contextvm/client-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/contextvm/client-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: contextvm (https://skillmd.com/u/contextvm)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/contextvm/client-dev

---


# ContextVM Client Development

Build MCP clients that connect to ContextVM servers over the Nostr network.

## Quick Start

Connect to a ContextVM server:

```typescript
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']);

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:

```typescript
const transport = new NostrClientTransport({
  signer,
  relayHandler: relayPool,
  serverPubkey: 'known-server-pubkey',
});
```

### Discovery via Announcements

Find servers broadcasting on the network:

```typescript
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`  |
| `oversizedTransfer`  | `object`                   | CEP-22 oversized payload transfer configuration  |
| `logLevel`           | `LogLevel`                 | Logging verbosity                                |

### Oversized Transfer

`NostrClientTransport` supports CEP-22 oversized payload transfer automatically.

- enabled by default
- used when a request is too large for practical relay event limits
- also used when receiving oversized server responses
- usually requires no application-level logic

For stateless client-to-server flows, the transport may send `start`, wait for `accept`, and then continue automatically.

Example:

```typescript
const transport = new NostrClientTransport({
  signer,
  serverPubkey: SERVER_PUBKEY,
  oversizedTransfer: {
    enabled: true,
    thresholdBytes: 48_000,
  },
});
```

Disable it only when you explicitly do not want CEP-22 fragmentation:

```typescript
const transport = new NostrClientTransport({
  signer,
  serverPubkey: SERVER_PUBKEY,
  oversizedTransfer: {
    enabled: false,
  },
});
```

### Relay Resolution Order

`NostrClientTransport` resolves operational relays in this order:

1. explicit operational relays from `relayHandler`
2. relay hints embedded in `nprofile`
3. CEP-17 relay-list discovery via `discoveryRelayUrls`
4. SDK bootstrap discovery relays when `discoveryRelayUrls` is omitted

This allows leaner client setup when the target server already publishes `kind:10002` metadata.

Use `relayHandler` when you already know the relays you want to operate against or when you want tighter control over relay behavior. Omit it when you want the SDK to resolve relays dynamically from server hints and relay-list metadata.

## Stateless Mode

Skip the initialization handshake for faster connections:

```typescript
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:

```typescript
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:

```typescript
// 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`](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/list` schemas.
- Generates TypeScript client code into your repo (shadcn-style: you own the generated code).

From ContextVM docs/blog references, the basic flow is:

```bash
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`](references/nostr-way-without-sdks.md) - The Nostr primitives behind CVM (raw events, JSON-RPC, manual implementation)
- [`references/discovery.md`](references/discovery.md) - Server discovery patterns
- [`references/proxy-pattern.md`](references/proxy-pattern.md) - Using NostrMCPProxy
- [`references/stateless-mode.md`](references/stateless-mode.md) - Stateless connection details

