Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sandwichfarm/nostr-watch/llms.txt

Use this file to discover all available pages before exploring further.

CVM (ContextVM) exposes NIP-66 relay intelligence as 21 structured MCP tools transported over the Nostr protocol. Instead of parsing raw NIP-66 events yourself, you send a tool call as a Nostr event and receive a clean JSON response. No API keys are required — communication uses Nostr pubkeys. CVM works with Claude Desktop, AI agents, and any MCP-compatible client. The tools mirror the REST API endpoints but use the Nostr protocol for transport. Calls are made with cvmClient.callTool(toolName, params) when using the SDK, or via an MCP client like Claude Desktop.
The CVM server is provided by the rstate service. To connect, you need the server’s CVM relay URLs and its public key. For Claude Desktop setup, see the getting started guide.

Relay query tools

relays/list

Get a paginated list of all relays known to this rstate instance. Input:
limit
number
Results per page. Default 50.
offset
number
Pagination offset. Default 0.
sortBy
string
Sort field: url, updated, or observationCount. Default url.
sortOrder
string
Sort direction: asc or desc. Default asc.
format
string
Response format: full (per-monitor attribution), detailed (aggregated, default), or simple (URLs only).
Output: { relays, total, limit, offset }
const result = await cvmClient.callTool('relays/list', {
  limit: 10,
  sortBy: 'updated',
  sortOrder: 'desc',
  format: 'detailed'
})
console.log(result.relays)
console.log(result.total)

relays/state

Get the aggregated state for a specific relay URL. Input:
relayUrl
string
required
The relay’s WebSocket URL.
format
string
Response format: full, detailed (default), or simple.
Output: { relay } — full relay state object, or null if not found.
const result = await cvmClient.callTool('relays/state', {
  relayUrl: 'wss://relay.damus.io',
  format: 'detailed'
})
console.log(result.relay)

relays/search

Search relays using one or more filter criteria. All filter fields are optional — only relays matching all provided filters are returned. Input:
network
string
Network type: clearnet, tor, i2p, or hybrid.
nips
number[]
Return only relays that support all listed NIP numbers.
software
object
Filter by software: { family, version }.
labels
object[]
Filter by NIP-32 labels: [{ namespace, value }].
maxLatency
object
Maximum RTT in ms: { open, read, write }.
minSupport
number
Minimum monitor support ratio (0–1).
limit
number
Results per page. Default 100.
offset
number
Pagination offset. Default 0.
format
string
Response format. Default detailed.
Output: { relays, total, limit, offset }
const result = await cvmClient.callTool('relays/search', {
  network: 'clearnet',
  nips: [42, 50],
  software: { family: 'strfry' },
  maxLatency: { open: 200 },
  limit: 50,
  format: 'detailed'
})
console.log(`Found ${result.total} matching relays`)

Geospatial tools

relays/nearby

Find relays near a geographic location using Haversine distance. Input:
lat
number
required
Latitude, -90 to 90.
lon
number
required
Longitude, -180 to 180.
radius
number
Search radius in km. Default 100.
maxResults
number
Maximum results. Default 50.
format
string
Response format. Default detailed.
Output: { relays, center: { lat, lon }, radius } — each relay includes a distance field in km.
const result = await cvmClient.callTool('relays/nearby', {
  lat: 37.7749,
  lon: -122.4194,
  radius: 100,
  maxResults: 10,
  format: 'detailed'
})
for (const relay of result.relays) {
  console.log(`${relay.relayUrl}${relay.distance}km away`)
}

relays/bbox

Find relays within a geographic bounding box. Input:
sw
object
required
Southwest corner: { lat, lon }.
ne
object
required
Northeast corner: { lat, lon }.
limit
number
Maximum results. Default 100.
compact
boolean
Return relay URLs only (legacy). Default false.
Output: { relays, bbox: { sw, ne }, total }

Label tools

relays/labels

Get labels for a specific relay. Input:
relayUrl
string
required
The relay’s WebSocket URL.
namespace
string
Filter to a specific label namespace (e.g. country, isp).
Output: { relayUrl, labels }labels is a Record<namespace, values[]>.

relays/labels/list

List all available label namespaces and values across all relays. Input:
namespace
string
Filter to a specific namespace.
Output: { namespaces, labels }namespaces is a sorted string array; labels is Record<namespace, values[]>.

relays/by/label

Get relays that have a specific label value. Input:
namespace
string
required
Label namespace, e.g. country, isp.
value
string
required
Label value, e.g. US, amazon.
limit
number
Results per page. Default 100.
offset
number
Pagination offset. Default 0.
format
string
Response format. Default detailed.
Output: { relays, label: { namespace, value }, total }

Aggregation tools

relays/by/software

Group relays by their software family. Input:
family
string
Filter to a specific software family name, e.g. strfry. Omit to get all groups.
Output: { groups: [{ family, count, relays }] }

relays/by/network

Group relays by network type (clearnet, tor, i2p). Input: None Output: { groups: [{ network, count, relays }] }

relays/by/nip

Group relays by NIP support, with optional minimum support threshold. Input:
nip
number
Filter to a specific NIP number.
minSupport
number
Minimum monitor support ratio (0–1). Default 0.5.
Output: { groups: [{ nip, count, avgSupport, relays }] }

relays/by/country

Group relays by country using ISO 3166-1 alpha-2 codes. Input:
countryCode
string
Filter to a specific country code, e.g. US, DE. Omit to get all countries.
Output: { groups: [{ countryCode, countryName, count, relays }] }

Availability tools

relays/online

List relays that are currently online (responded to a check within the configured window). Input:
onlineWindowSeconds
number
Override the online window. Default is derived from the maximum monitor check frequency.
filters
object
Additional filters: { network, labels }.
limit
number
Results per page. Default 100.
offset
number
Pagination offset. Default 0.
Output: { relays, total, limit, offset }

relays/offline

List relays that are currently offline but have not yet been considered dead. Input:
offlineThresholdSeconds
number
Seconds since last successful open to consider a relay offline.
deadThresholdSeconds
number
Seconds since last seen to consider a relay dead. Default: 7 days (604800).
filters
object
Additional filters: { network, labels }.
limit
number
Results per page.
offset
number
Pagination offset.
Output: { relays, total, limit, offset }

relays/dead

List relays that have not been seen for a long time and are probably permanently offline. Input:
deadThresholdSeconds
number
Override the dead threshold. Default: 7 days (604800).
filters
object
Additional filters: { network, labels }.
limit
number
Results per page.
offset
number
Pagination offset.
Output: { relays, total, limit, offset }

relays/compare

Compare multiple relays side by side. Input:
relayUrls
string[]
required
Array of 2–10 relay WebSocket URLs to compare.
Output: { relays, comparison: { common: { nips, requirements }, differences: { network, software, latency } } }
const result = await cvmClient.callTool('relays/compare', {
  relayUrls: [
    'wss://relay.damus.io',
    'wss://relay.nostr.band',
    'wss://relay.snort.social'
  ]
})
console.log('Common NIPs:', result.comparison.common.nips)
console.log('Different software:', result.comparison.differences.software)

Monitor tools

monitors/get

Get information and analytics for a specific monitor. Input:
pubkey
string
required
The monitor’s Nostr pubkey in hex format.
Output: { monitor, analytics } — analytics includes reliability score and coverage data.

monitors/list

List all known monitors with reliability scores. Input:
limit
number
Results per page. Default 100.
offset
number
Pagination offset. Default 0.
Output: { monitors, total, analytics } — sorted by last seen (most recent first).

System tools

health/ping

Check server health, version, and cache statistics. Input: None Output: { status, version, uptime, relayCount, observationCount, cache, timestamp } status is ok, degraded, or error based on relay connectivity.

policy/get

Get the current aggregation policy configuration. Input: None Output: { policy } — includes quorum, labelQuorum, madScale, and weights.

policy/set

Update the aggregation policy. Requires server-side authorization (the caller’s pubkey must be in allowedPubkeys). Input:
policy
object
required
Policy fields to update. Accepted fields:
  • windowStrategy — aggregation window strategy
  • quorum — minimum monitor agreement fraction (0–1)
  • labelQuorum — minimum label support fraction (0–1)
  • madScale — MAD outlier rejection scale factor
  • weights{ recency, reliability } weighting object
Output: { success, policy, message }

Subscription tools

relays/subscribe_state

Subscribe to relay state change notifications. The server sends a notification each time a subscribed relay’s aggregated state changes. Input:
relayUrls
string[]
Filter notifications to specific relay URLs.
network
string
Filter to a specific network type.
nips
number[]
Filter to relays that support specific NIPs.
software
object
Filter by software family/version.
labels
object[]
Filter by NIP-32 labels.
geo
object
Geographic area filter: { center: { lat, lon }, radius }.
thresholds
object
Change thresholds before triggering a notification: { rttDeltaMs, supportDelta }.
Output: { subscriptionId, message }

relays/unsubscribe

Cancel a state change subscription. Input:
subscriptionId
string
required
The subscription ID to cancel.
Output: { success, message }

Response formats

All relay query tools accept a format parameter that controls response verbosity:
FormatReturnsUse when
fullAll fields including per-monitor attributionYou need to see which monitors reported which values
detailedAggregated values without per-monitor breakdown (default)Most use cases
simpleRelay URLs onlyYou only need a list of addresses

Claude Desktop setup

Add the @nostr-watch/relayvm MCP server to your Claude Desktop configuration:
{
  "mcpServers": {
    "relayvm": {
      "command": "npx",
      "args": ["-y", "@nostr-watch/relayvm"],
      "env": {
        "CVM_RELAYS": "wss://relay.damus.io,wss://relay.nostr.band",
        "INGEST_RELAYS": "wss://history.nostr.watch",
        "CVM_SERVER_NSEC": "nsec1..."
      }
    }
  }
}
Once configured, Claude can answer natural language queries by calling the appropriate tools automatically:
Find all relays that support NIP-42 authentication
List the top 10 most reliable relays by uptime
Show me relays within 100km of San Francisco
Compare relay.damus.io and relay.nostr.band
Find all relays running strfry software
Show me offline relays that were recently seen
The CVM trust model is “verify or self-host.” When using the public nostr.watch CVM, you trust that the rstate instance is honestly aggregating NIP-66 data. To verify independently, run your own rstate instance — it ingests raw NIP-66 events directly from Nostr relays.

Build docs developers (and LLMs) love