This article addresses a practical question: when a service lives behind a Nostr pubkey like a DVM or a CVM, what does it mean to "call it"?
In HTTP land, the answer is straightforward: you hit an endpoint, get a response, and parse a contract shaped by OpenAPI docs or SDKs.
On Nostr, the transport differs, but the need remains the same. You will want a request/response loop that you can reason about, debug, and work with.
This piece is written for developers first, but it's also meant to close the "I know Nostr, but I don't know how to call this thing" gap.
The thesis is simple: calling a ContextVM server follows the same core pattern as calling a DVM. You publish a signed request event, and listen for a response event. The difference isn't the transport; it's the message carried inside the message.
Same loop
In this case relays works as permissionless message bus. They don't hand you RPC "for free", but they give you the primitives to build it: identity (pubkeys), authenticity (signatures), delivery (relays), and correlation (event IDs and tags).
Both DVMs and CVM turn those primitives into the same loop: send a request to a service pubkey, then accept only responses from that pubkey that reference your request.
Where integrations diverge is the payload shape and how much of it is standardized.
Calling a DVM: kinds and tags (NIP-90)
The most widely known request/response convention on Nostr is NIP-90, the DVM spec.
The framing is straightforward: you publish a “job request” in a reserved kind range, providers publish results (and sometimes intermediate feedback), and the client correlates the output back to the request. Optional features exist (payments, encryption, discovery), but they’re not uniformly implemented nor documented, so you often end up handling provider-specific details.
At a protocol level, the key conventions are: requests live in kinds 5000–5999, results in 6000–6999, and the common mapping is “result kind = request kind + 1000”. Job feedback is kind 7000. Inputs and parameters are often expressed as tags.
Here is a minimal request shape (illustrative, not tied to any specific job kind):
{
"kind": 5xxx,
"content": "",
"tags": [
["i", "<data>", "text"],
["param", "<key>", "<value>"],
["relays", "wss://..."],
["p", "<provider-pubkey>"]
]
}
And a minimal result shape:
{
"kind": 6xxx,
"pubkey": "<provider-pubkey>",
"content": "<payload>",
"tags": [
["e", "<job-request-id>", "<relay-hint>"],
["p", "<customer-pubkey>"]
]
}
Operationally, your client publishes the request, then subscribes for results, and kind 7000 feedback. It requires an e tag referencing the request event ID, and then parses whatever payload the provider puts into content.
That works. Some useful services exist today with this approach. But...
Why DVM integration can feel messy
This isn’t a criticism of DVMs as an idea. DVMs were an early, important attempt to bring RPC-like behavior into a world built from signed events that fits more naturally in a producer/consumer model.
The messiness shows up at the “contract boundary”. In practice, you’re often stitching together a contract out of a kind number, a handful of conventions, and provider-defined payloads in content. When providers diverge in payload shape, parameter tags, encryption choices, payment signaling, or discovery conventions, integration stops being “call the spec” and becomes “write an adapter”.
That approach can still work well, especially when a community converges on a small set of providers. But it tends to fragment across the broader ecosystem, and defeats the purpose of decentralization and interoperability.
Calling a CVM server
ContextVM takes a different approach. It keeps the same transport primitive, nostr events, and relays, but standardizes the payload using MCP.
MCP (Model Context Protocol) is an open standard for how clients talk to servers using JSON-RPC messages. It standardizes capabilities like tools, resources, and prompts, and it comes with a clear, evolving specification. It can also be integrated out of the box with AI systems, rather than being used programmatically to build user interfaces or applications, but that's a topic for another day.
In CVM, MCP messages are moved through Nostr relays. The server remains an MCP server; the network becomes Nostr, and MCP inherits the benefits of the Nostr protocol.
Two conventions matter most for client developers. CVM uses a single Nostr kind for messages 25910), and the content field carries a stringified MCP JSON-RPC message.
Addressing and correlation stays the same than DVMs, via tags. Requests tag the server pubkey with p. Responses tag the request event ID with e.
The CVM “Nostr way” recipe
This is the mental model you can reuse for any CVM server.
1) Discover what the server can do (tools + schemas)
With DVMs, “what can I call?” is often answered by external docs, repo READMEs, community conventions, or other means.
With CVM, the server can tell you directly.
In MCP, you can introspect a server using the tools/list method. The response includes tool names, descriptions, and JSON Schemas for inputs (and often outputs). That schema is the contract you can validate against.
You can list tools in the Nostr way by following the example below, but if you prefer a UI, https://contextvm.org is also a convenient inspector: you can paste a server pubkey and browse the same tool definitions.
2) Craft a request event
At the Nostr level, you publish an event that targets the server pubkey and carries a JSON-RPC message in content.
To list tools:
{
"kind": 25910,
"tags": [["p", "<server-pubkey>"]],
"content": "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}"
}
To call a tool:
{
"kind": 25910,
"tags": [["p", "<server-pubkey>"]],
"content": "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"<tool-name>\",\"arguments\":{}}}"
}
3) Listen for a correlated response and parse JSON-RPC
The server replies with another kind 25910 event that references your request event ID in an e tag. Minimal shape:
{
"kind": 25910,
"pubkey": "<server-pubkey>",
"tags": [["e", "<request-event-id>"]],
"content": "{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":{...}}"
}
Inside result, MCP gives you a predictable structure for tool outputs. A tool result can include human-readable content[] and machine-friendly structuredContent. That duality matters because your code can safely consume structured output and validate against the schema.
DVM vs CVM
Think of both as “RPC over Nostr,” but with different organizing principles.
With DVMs, the job is a kind. Your integration typically starts with: “what kind do I publish, and what does the provider put in content?” The response is usually a provider-specific payload blob, and parameters are often tag-shaped.
With CVM, the job is a tool name. Your integration starts with: “what tools does this server expose, and what does the JSON schema require?” The response is JSON-RPC, and structured output is first-class.
That difference cascades into developer experience. With CVM you can introspect capabilities with tools/list, validate inputs, and outputs with JSON Schema, and rely on a consistent JSON-RPC envelope. With DVMs you can absolutely build reliable integrations, but the contract is more often “provider + kind + conventions”, which is harder to reuse across providers.
None of this invalidates DVMs. It just explains why CVM feels less like “a custom integration” and more like “a reusable interface.”
A short note on observability and persistence
One subtle difference is how much metadata ends up "hanging off" kinds.
In DVM-style flows, job types are encoded in kind ranges, and requests and results are typically regular events that may persist on relays. That can be a feature for auditability, replay, and analytics, but it also increases observability of what's being requested at scale. Encryption can help improve privacy, but DVMs lack a clear encryption convention, so providers either adopt ad hoc approaches or operate in clear text.
In CVM, message traffic is simplified to a single kind and a structured payload. That doesn't magically make interactions private, but it does keep it consistent and shifts "what the job is" away from kind numbers. This can be considered more private since all requests and responses look the same to an external observer. Encryption is also more straightforward: CVM specifies clear procedures for encryption and signaling, encrypting the JSON-RPC payload using NIP-44 and simplified NIP-17, so you can protect interactions without reinventing the wheel.
What you actually need to implement
At minimum, a CVM client needs four basic Nostr capabilities.
First, create and sign a Nostr event so the service can trust who is speaking. Second, publish it to a set of relays. Third, subscribe for responses from the service pubkey. Fourth, parse the JSON-RPC message in content and handle result versus error.
That's the whole loop, and in reality these are fundamental capabilities that any Nostr client needs to implement: sign, publish, subscribe, parse. Everything else is developer experience.
If "no SDK" sounds intimidating, we are also building tooling to make the experience of working with ContextVM frictionless and enjoyable. The TypeScript SDK gives you ready-made client and server plumbing. ctxcn, our shadcn-inspired CLI tool, can compile tool schemas into strongly typed client code, so remote tools feel like local functions with typed inputs and outputs. The ContextVM website also serves as an inspector, a debugging tool, and a hub for links to all related docs, repos, and more. Another interesting piece is the gateway component, which lets you expose any existing MCP server written in any language through Nostr.
There is more tooling, integrations, and features on the way, but if you only take one thing from this article, let it be this: you can call CVMs as you do with DVMs. Indeed, we could argue that a CVM is a kind of DVM. What changes is the shape of the messages. CVM tries to make that contract explicit, introspectable, and consistent.
Build on CVM
CVM’s promise is not that it hides Nostr. It’s that it makes Nostr-based RPC legible, and workable.
If you can already call DVMs, you can call CVM servers today in the same “Nostr way”: events, signatures, relays. The opportunity now is to make this pattern easier for the next developer by shipping libraries, typed clients, monitoring, and debugging tools that treat Nostr-native RPC as a first-class workflow.
This ecosystem is open-source and community-driven. Every piece of better tooling, docs, or knowledge is a reduction in friction, and every reduction in friction is more decentralization in practice: fewer hidden gatekeepers, fewer platform chokepoints, more sovereign interoperability.
Start here:
https://github.com/contextvm/contextvm
https://github.com/contextvm/wotrlay