ContextVM Protocol Specification
Status: Draft
Abstract
Section titled “Abstract”The Context Vending Machine (ContextVM) specification defines how Nostr can be used to expose Model Context Protocol (MCP) server capabilities, enabling standardized usage of these resources through a decentralized, cryptographically secure messaging system.
Table of Contents
Section titled “Table of Contents”- Introduction
- Protocol Overview
- Event Kinds
- Server Discovery
- Capability Operations
- Encryption
- Notifications
- Error Handling
- Implementation Requirements
- Complete Protocol Flow
- Subscription Management
Introduction
Section titled “Introduction”The Model Context Protocol provides a protocol specification to create servers exposing capabilities and clients consuming them. Meanwhile, the Nostr network and Context Vending Machines offer a decentralized way to announce and consume computational services. This specification defines how to bridge these protocols, allowing MCP servers to advertise and provide their services through the Nostr network.
This specification aims to:
- Enable discovery of MCP servers and their capabilities through the Nostr network
- Provide a consistent experience for clients accessing capabilities, and servers exposing their capabilities
- Maintain compatibility with both protocols while preserving their security models
By integrating these protocols, ContextVM combines the standardized capability framework of MCP with the decentralized, cryptographically secure messaging of Nostr. This integration enables several key advantages:
- Discoverability: MCP servers can be discovered through the Nostr network without centralized registries
- Verifiability: All messages are cryptographically signed using Nostr’s public keys
- Decentralization: No single point of failure for service discovery or communication
- Protocol Interoperability: Both MCP and ContextVMs utilize JSON-RPC patterns, enabling seamless communication between the protocols
The integration preserves the security model of both protocols while enabling new patterns of interaction.
Public Key Cryptography
Section titled “Public Key Cryptography”ContextVM leverages Nostr’s public key cryptography to ensure message authenticity and integrity:
-
Message Verification: Every message is cryptographically signed by the sender’s private key and can be verified using their public key, ensuring that:
- Server announcements come from legitimate providers
- Client requests are from authorized users
- Responses are from the expected servers
-
Identity Management: Public keys serve as persistent identifiers for all actors in the system:
- Providers can maintain consistent identities across relays
- Clients can be uniquely identified for authorization purposes
The cryptographic properties enable secure authorization flows for paid services and private capabilities without requiring centralized authentication services.
Protocol Overview
Section titled “Protocol Overview”Message Structure
Section titled “Message Structure”The protocol uses these key design principles for message handling:
-
Content Field Structure: The
content
field of Nostr events contains stringified MCP messages. All MCP message structures, are preserved exactly as defined in the MCP specification -
Nostr Metadata in Tags: All Nostr-specific metadata uses event tags:
p
: Public key for addressing providers or clientse
: Event id, references for correlating requests and responsescap
: Capability tag for tools, resources, and prompts to provide pricing metadata
-
Unified Event Kind: ContextVM uses a single event kind for all communication with specific storage characteristics:
25910
: All ContextVM messages (ephemeral events)11316
-11320
: Server announcements and capability listings (replaceable events)1059
: Encrypted Messages (NIP-59 Gift Wrap)
These event kinds follow Nostr’s conventions in NIP-01:
- For kind n such that 20000 <= n < 30000, events are ephemeral, which means they are not expected to be stored by relays for a long period, but rather just transmitted.
- For kind n such that 10000 <= n < 20000, events are addressable by their kind, pubkey and d tag value — which means that, for each combination of kind, and pubkey, only the latest event MUST be stored by relays, older versions MAY be discarded.
Main Actors
Section titled “Main Actors”There are three main actors in this workflow:
- Servers: MCP servers exposing capabilities, operated by a provider using a public key
- Relays: Core part of Nostr protocol that allows communication between clients and servers
- Clients: MCP or Nostr clients that discover and consume capabilities from servers
Event Kinds
Section titled “Event Kinds”This specification defines these event kinds:
Kind | Description |
---|---|
25910 | ContextVM Messages |
1059 | Encrypted Messages (NIP-59 Gift Wrap) |
11316 | Server Announcement |
11317 | Tools List |
11318 | Resources List |
11319 | Resource Templates List |
11320 | Prompts List |
Note on Encryption: When encryption is enabled, kind 25910 events are wrapped using NIP-59 and published as kind 1059 events. Addressable events (kinds 11316-11320) remain unencrypted for discoverability.
Server Discovery
Section titled “Server Discovery”ContextVM provides two methods of server discovery, the main differences between these two methods being the visibility of the servers and the way they are advertised. Public servers can advertise themselves and their capabilities to improve discoverability. Private servers may not advertise themselves and their capabilities, but they can be discovered by clients that know the provider’s public key.
Discovery via Server Announcements (Public Servers)
Section titled “Discovery via Server Announcements (Public Servers)”Public server announcements act as a service catalog, allowing clients to discover servers and their capabilities through replaceable events on the Nostr network. This mechanism provides an initial overview of what a server offers, and their public keys to connect with them.
Since each server is uniquely identified by its public key, the announcement events are replaceable (kinds 11316-11320), ensuring that only the most recent version of the server’s information is active.
Providers announce their servers and capabilities by publishing events with kinds 11316 (server), 11317 (tools/list), 11318 (resources/list), 11319 (resource templates/list), and 11320 (prompts/list).
Note: The content
field of ContextVM events contains stringified MCP messages. The examples below present the content
as a JSON object for readability; it must be stringified before inclusion in a Nostr event.
Server Announcement Event
Section titled “Server Announcement Event”{ "kind": 11316, "pubkey": "<provider-pubkey>", "content": { "protocolVersion": "2025-07-02", "capabilities": { "prompts": { "listChanged": true }, "resources": { "subscribe": true, "listChanged": true }, "tools": { "listChanged": true } }, "serverInfo": { "name": "ExampleServer", "version": "1.0.0" }, "instructions": "Optional instructions for the client" }, "tags": [ ["name", "Example Server"], // Optional: Human-readable server name ["about", "Server description"], // Optional: Server description ["picture", "https://example.com/server.png"], // Optional: Server icon/avatar URL ["website", "https://example.com"], // Optional: Server website ["support_encryption"] // Optional: Presence indicates server supports encrypted messages ]}
Tools List Event
Section titled “Tools List Event”{ "kind": 11317, "pubkey": "<provider-pubkey>", "content": { "tools": [ { "name": "get_weather", "description": "Get current weather information for a location", "inputSchema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name or zip code" } }, "required": ["location"] } } ] }, "tags": []}
Server Connection and Initialization
Section titled “Server Connection and Initialization”Whether a server is discovered via public announcements or its public key is already known, clients MUST use the MCP initialization process to establish a connection. This flow applies to all servers and involves a client initialization request, a server initialization response, and a client initialized notification:
Client Initialization Request
Section titled “Client Initialization Request”{ "kind": 25910, "content": { "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": { "protocolVersion": "2025-07-02", "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "ExampleClient", "version": "1.0.0" } } }, "tags": [["p", "<provider-pubkey>"]]}
- Tags:
p
: Provider public key, to target all the servers from a provider
Server Initialization Response
Section titled “Server Initialization Response”{ "kind": 25910, "pubkey": "<provider-pubkey>", "content": { "jsonrpc": "2.0", "id": 0, "result": { "protocolVersion": "2025-07-02", "capabilities": { "logging": {}, "prompts": { "listChanged": true }, "resources": { "subscribe": true, "listChanged": true }, "tools": { "listChanged": true } }, "serverInfo": { "name": "ExampleServer", "version": "1.0.0" }, "instructions": "Optional instructions for the client" } }, "tags": [ ["e", "<client-init-request-id>"], ["support_encryption"] // Optional: Presence indicates server supports encrypted messages ]}
- Tags:
e
: Reference to the client’s initialization request eventsupport_encryption
: Presence indicates server supports encrypted messages
Client Initialized Notification
Section titled “Client Initialized Notification”After receiving the server initialization response, the client MUST send an initialized notification to indicate it is ready to begin normal operations:
{ "kind": 25910, "pubkey": "<client-pubkey>", "content": { "jsonrpc": "2.0", "method": "notifications/initialized" }, "tags": [ ["p", "<provider-pubkey>"] // Required: Target provider public key ]}
This notification completes the initialization process and signals to the server that the client has processed the server’s capabilities and is ready to begin normal operations.
Capability Operations
Section titled “Capability Operations”After discover a server publicly, or initialization, clients can interact with server capabilities.
List Operations
Section titled “List Operations”All list operations follow the same structure described by MCP, with the specific capability type indicated in the method name.
- Tags:
p
: Provider public key
List Request Template
Section titled “List Request Template”{ "kind": 25910, "pubkey": "<client-pubkey>", "id": "<request-event-id>", "content": { "jsonrpc": "2.0", "id": 1, "method": "<capability>/list", // tools/list, resources/list, or prompts/list "params": { "cursor": "optional-cursor-value" } }, "tags": [ ["p", "<provider-pubkey>"] // Required: Provider's public key ]}
List Response Template
Section titled “List Response Template”{ "kind": 25910, "pubkey": "<provider-pubkey>", "content": { "jsonrpc": "2.0", "id": 1, "result": { "<items>": [ // "tools", "resources", or "prompts" based on capability // Capability-specific item objects ], "nextCursor": "next-page-cursor" } }, "tags": [ ["e", "<request-event-id>"] // Required: Reference to the request event ]}
Capability-Specific Item Examples
Section titled “Capability-Specific Item Examples”Call Tool Request
Section titled “Call Tool Request”{ "kind": 25910, "id": "<request-event-id>", "pubkey": "<client-pubkey>", "content": { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_weather", "arguments": { "location": "New York" } } }, "tags": [["p", "<provider-pubkey>"]]}
Call Tool Response
Section titled “Call Tool Response”{ "kind": 25910, "pubkey": "<provider-pubkey>", "content": { "jsonrpc": "2.0", "id": 2, "result": { "content": [ { "type": "text", "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" } ], "isError": false } }, "tags": [["e", "<request-event-id>"]]}
For the rest of capabilities (resources, prompts, completions, ping, etc) the content
field follows the same pattern as other MCP messages, containing a stringified simplified JSON-RPC object that adheres to the MCP specification.
Capability Pricing
Section titled “Capability Pricing”ContextVM supports pricing for capabilities through the use of cap
tags in capability announcement or list events.
Pricing Tag Format
Section titled “Pricing Tag Format”Pricing information is conveyed using the cap
tag with the following format:
["cap", "<capability-identifier>", "<price>", "<currency-unit>"]
Where:
<capability-identifier>
is the name of the tool, prompt, or resource URI<price>
is a string representing the numerical amount (e.g., “100”)<currency-unit>
is the currency symbol (e.g., “sats”, “usd”)
Example
Section titled “Example”A tool list event with pricing for the get_weather
tool:
From public server announcements:
{ "kind": 11317, "content": { "tools": [ { "name": "get_weather", "description": "Get current weather information" // ... other tool properties } ] }, "tags": [["cap", "get_weather", "100", "sats"]]}
From capability list events:
{ "kind": 25910, "pubkey": "<provider-pubkey>", "content": { "result": { "tools": [ { "name": "get_weather", "description": "Get current weather information" // ... other tool properties } ], "nextCursor": "next-page-cursor" } }, "tags": [ ["e", "<request-event-id>"], // Required: Reference to the request event ["cap", "get_weather", "100", "sats"] // Optional: Pricing metadata ]}
This indicates that using the get_weather
tool costs 100 satoshis. Clients can use this information to display pricing to users.
Payment Handling
Section titled “Payment Handling”When a capability has pricing information, clients and servers should handle payments. The lifecycle of request with payment follows these steps:
- Request: Client sends a capability request to the server
- Invoice Generation: Server sends a
notifications/payment_required
notification with a payment request (e.g., Lightning Network invoice, Cashu PaymentRequest, Payment gateway URL, etc.) - Payment Verification: Client pays and the server verifies the payment
- Capability Access: Once payment is verified, the server processes the capability request, and responds with the result
Payment verification is handled by the server and can be implemented using Lightning Network zaps (NIP-57) or other payment methods.
Encryption
Section titled “Encryption”ContextVM supports optional end-to-end encryption for enhanced privacy and security. This feature leverages a simplified version of NIP-17 (Private Direct Messages) for secure message encryption and NIP-59 (Gift Wrap) pattern with no ‘rumor’ with NIP-59 gift wrapping for metadata protection, ensuring that:
- Message Content Privacy: All ContextVM message content is encrypted using NIP-44 encryption
- Metadata Protection: Gift wrapping hides participant identities, timestamps, and message patterns
- Selective Encryption: Clients and servers can negotiate encryption on a per-session basis
Encryption in ContextVM maintains full compatibility with the standard protocol while adding an additional privacy layer. When encryption is enabled, all kind 25910 events are encrypted using the NIP-17/NIP-59 pattern, while replaceable events (server announcements and capability lists) remain unencrypted for discoverability.
Encryption Support Discovery
Section titled “Encryption Support Discovery”Encryption support is advertised through the support_encryption
tag in server announcement events or direct initialization responses. The presence of this tag indicates that the server supports encryption; its absence signifies that the server does not support encryption:
{ "pubkey": "<provider-pubkey>", "content": { /* server details */ }, "tags": [ ["support_encryption"] // Presence alone indicates encryption support // ... other tags ]}
Clients can discover encryption support by:
- Public Server Discovery: Check for the presence of the
support_encryption
tag in server announcements (kind 11316) - Direct Discovery: Check for the presence of the
support_encryption
tag in initialization responses - Encrypted Handshake: Attempt an encrypted direct discovery, and wait for and encrypted response from the server
When encryption is enabled, ContextVM messages follow a simplified NIP-17 pattern with no ‘rumor’ with NIP-59 gift wrapping.
1. Request Preparation
Section titled “1. Request Preparation”The request is prepared as usual, and should be signed:
{ "kind": 25910, "id": "<request-event-id>", "pubkey": "<client-pubkey>", "content": { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_weather", "arguments": { "location": "New York" } } }, "tags": [["p", "<provider-pubkey>"]], "sig": "<signature>"}
The request is converted into a JSON string and gift-wrapped (kind 1059) with a random key, following NIP-44 encryption.
{ "id": "<gift-wrap-hash>", "pubkey": "<random-pubkey>", "created_at": "<randomized-timestamp>", "kind": 1059, "tags": [["p", "<server-pubkey>"]], "content": "<nip44-encrypted-request>", "sig": "<random-key-signature>"}
Encrypted Response Structure
Section titled “Encrypted Response Structure”Server responses follow the same pattern. The response is converted into a JSON string and gift-wrapped (kind 1059) with a random key, following NIP-44 encryption.
The decrypted inner content contains the standard ContextVM response format. The id field used in responses should match the inner id field used in requests, not the id of the gift-wrap event.
Why a simplified NIP-17/NIP-59 pattern?
Section titled “Why a simplified NIP-17/NIP-59 pattern?”The standard implementation of NIP-17 and NIP-59 is complex and designed for private direct messages that are meant to persist in relays. Therefore, the standard uses an extra layer of encryption to prevent leakage of the original message if an attacker decrypts it. This involves a ‘rumor’ - an unsigned event embedded in a ‘seal’ event. The ‘rumor’ represents the original message, and because it lacks a signature, it cannot be leaked to relays as it is an invalid Nostr event. The ‘seal’ serves as the signature for the ‘rumor’.
In contrast, ContextVM uses ephemeral events that are not intended to be stored in relays, so the ‘rumor’ and ‘seal’ events are unnecessary, but still leveraging the metadata leakage protection of NIP-59 gift wrapping.
Notifications
Section titled “Notifications”All notifications in ContextVM follow the standard MCP notification format and conventions, using the unified kind 25910 event type. This includes notifications for payment requests, progress updates, and all other server-to-client or client-to-server communications.
Notifications are constructed according to the MCP notification template. The direction is determined by the p
tag: client-to-server notifications are signed by the client’s pubkey and use the server’s pubkey as the p
tag; server-to-client notifications are signed by the server’s provider pubkey and use the client’s pubkey as the p
tag.
Payment Required Notification Example
Section titled “Payment Required Notification Example”{ "kind": 25910, "pubkey": "<provider-pubkey>", "content": { "method": "notifications/payment_required", "params": { "amount": 1000, "currency": "sats", "invoice": "lnbc...", "description": "Payment for tool execution" } }, "tags": [ ["p", "<client-pubkey>"], ["e", "<request-event-id>"] ]}
For long-running jobs, servers should send progress notifications frequently to indicate the job is still processing and to prevent client timeout.
Complete Protocol Flow
Section titled “Complete Protocol Flow”sequenceDiagram participant Client participant Relay participant Server
opt Public Server Discovery (Catalog) Client->>Relay: Subscribe to kinds 11316-11320 Relay-->>Client: Server Announcements and Capabilities end
Client->>Relay: Publishes kind 25910 (method: initialize) Relay-->>Server: Forwards initialize request
Server->>Relay: Publishes kind 25910 (initialize response) Relay-->>Client: Forwards initialize response
Client->>Relay: Publishes kind 25910 (notification: initialized) Relay-->>Server: Forwards initialized notification
Note over Client,Server: Capability Interaction (e.g., tools/list)
Client->>Relay: Publishes kind 25910 (method: tools/list) Relay-->>Server: Forwards request Server->>Relay: Publishes kind 25910 (tools/list response) Relay-->>Client: Forwards tools/list response
Note over Client,Server: Capability Interaction (e.g., tools/call)
Client->>Relay: Publishes kind 25910 (method: tools/call)
opt Optional Payment Flow Server->>Relay: Publishes kind 25910 (notification: payment_required) Relay-->>Client: Forwards payment_required Client->>Client: User Pays Invoice end
Server->>Relay: Publishes kind 25910 (tools/call response) Relay-->>Client: Forwards tools/call response