draft mcp:2025-03-26 rev1.1
This document defines how Nostr and Data Vending Machines can be used to expose Model Context Protocol (MCP) server capabilities, enabling standardized usage of these resources.
- Introduction
- Motivation
- Protocol Overview
- Protocol Consistency
- Event Kinds
- Server Discovery
- Capability Operations
- Completions
- Ping
- Encryption
- Notifications
- Error Handling
- Implementation Requirements
- Complete Protocol Flow
- Subscription Management
The Model Context Protocol provides a protocol specification to create servers exposing capabilities and clients consuming them. Meanwhile, the Nostr network and Data 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, DVMCP 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 DVMs 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.
DVMCP 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
- Server identifiers are associated with provider public keys
-
Authorization Flow: The cryptographic properties enable secure authorization flows for paid services and private capabilities without requiring centralized authentication services.
DVMCP bridges MCP and Nostr protocols through a consistent message structure and well-defined workflow.
The protocol uses these key design principles for message handling:
-
Content Field Structure: The
contentfield of Nostr events contains stringified simplified MCP messages that:- Omit the
jsonrpcversion field - Omit the
idfield - For requests: Contain only
methodandparamsfields - For responses: Contain the response data directly at the root level without nesting inside a
resultfield
This approach maintains protocol integrity while enabling translation between the two systems.
- Omit the
-
Nostr Metadata in Tags: All Nostr-specific metadata uses event tags:
d: Unique server identifier, defined by the providers: Server identifier for targeting specific servers, should be thedtag of the server being targetedp: Public key for addressing providers or clientse: Event id, references for correlating requests and responsesmethod: MCP method for easy filtering and routing, it's duplicated from the MCP message of the contentcap: Capability name tag for tools, resources, and prompts to enhance discoverability, filtering, and provide nostr related metadata
-
Event Kind Separation: Different event kinds are used for different message categories with specific storage characteristics:
31316-31319: Server announcements and capability listings (addressable events)25910: Client requests (ephemeral events)26910: Server responses (ephemeral events)21316: Notifications and feedback (ephemeral events)
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 30000 <= n < 40000, events are addressable by their kind, pubkey and d tag value -- which means that, for each combination of kind, pubkey and the d tag value, only the latest event MUST be stored by relays, older versions MAY be discarded.
There are four main actors in this workflow:
- Providers: Entities running MCP server(s), operating behind a Nostr public key
- Servers: MCP servers exposing capabilities, operated by a provider
- DVMs: Bridge protocol that translates between Nostr and MCP protocols
- Clients: MCP or Nostr clients that discover and consume capabilities from servers
The protocol consists of three main phases:
- Discovery: Finding available MCP servers in Nostr and retrieving available capabilities
- Capability Execution/Read: Requesting tool execution, reading resources, or prompts, and receiving results
- Capability Feedback: Status updates, notifications, and payment handling
This specification defines these event kinds:
| Kind | Description |
|---|---|
| 31316 | Server Announcement |
| 31317 | Tools List |
| 31318 | Resources List |
| 31319 | Prompts List |
| 25910 | Requests |
| 26910 | Responses |
| 21316 | Feedback/Notifications |
| 1059 | Encrypted Messages (NIP-59 Gift Wrap) |
Note on Encryption: When encryption is enabled, ephemeral events (kinds 25910, 26910, 21316) are wrapped using NIP-17/NIP-59 encryption and published as kind 1059 events. Addressable events (kinds 31316-31319) remain unencrypted for discoverability.
DVMCP 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 when providing a "public" or accessible service. Private servers may not advertise themselves and their capabilities, but they can be discovered by clients that know the provider's public key or server identifier.
Providers announce their servers and capabilities by publishing events with kinds 31316 (server), 31317 (tools/list), 31318 (resources/list), and 31319 (prompts/list).
After a client discovers a server through these announcements, it can immediately begin making requests to the server without requiring an explicit initialization step.
Notice: The content field of all events must always be a string. For better readability, event examples are presented as JSON objects. However, these must first be converted to strings before they can be included in the content field.
{
"kind": 31316,
"pubkey": "<provider-pubkey>",
"content": {
"protocolVersion": "2025-03-26",
"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": [
["d", "<server-identifier>"], // Required: Unique identifier for the server
["k", "25910"], // Required: Accepted event kinds (for requests)
["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", "true"] // Optional: Whether server supports encrypted messages
]
}{
"kind": 31317,
"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": [
["d", "<unique-identifier>"], // Required: Unique identifier for the tools list
["s", "<server-identifier>"], // Required: Reference to the server it belongs to
["cap", "get_weather"] // Required: One cap tag per tool name
]
}Static resources list event
{
"kind": 31318,
"pubkey": "<provider-pubkey>",
"content": {
"resources": [
{
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"description": "Primary application entry point",
"mimeType": "text/x-rust"
}
]
},
"tags": [
["d", "<unique-identifier>"], // Required: Unique identifier for the resources list
["s", "<server-identifier>"], // Required: Reference to the server it belongs to
["cap", "main.rs"] // Optional: One cap tag per resource name
]
}Resource template list event
{
"kind": 31318,
"pubkey": "<provider-pubkey>",
"content": {
"resourceTemplates": [
{
"uriTemplate": "file:///{path}",
"name": "Project Files",
"description": "Access files in the project directory",
"mimeType": "application/octet-stream"
}
]
},
"tags": [
["d", "<unique-identifier>"], // Required: Unique identifier for the resource templates list
["s", "<server-identifier>"], // Required: Reference to the server it belongs to
["cap", "Project Files"] // Optional: One cap tag per resource template name
]
}{
"kind": 31319,
"pubkey": "<provider-pubkey>",
"content": {
"prompts": [
{
"name": "code_review",
"description": "Asks the LLM to analyze code quality and suggest improvements",
"arguments": [
{
"name": "code",
"description": "The code to review",
"required": true
}
]
}
]
},
"tags": [
["d", "<unique-identifier>"], // Required: Unique identifier for the prompts list
["s", "<server-identifier>"], // Required: Reference to the server it belongs to
["cap", "code_review"] // Optional: One cap tag per prompt name
]
}DVMCP supports pricing for capabilities through the use of cap tags in announcement events.
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")
A tool list event with pricing for the get_weather tool:
{
"kind": 31317,
"tags": [
["d", "server-123/tools/list"],
["s", "server-123"],
["cap", "get_weather", "100", "sats"]
],
"content": {
"tools": [
{
"name": "get_weather",
"description": "Get current weather information"
// ... other tool properties
}
]
}
}This indicates that using the get_weather tool costs 100 satoshis. Clients can use this information to display pricing to users and handle payments before making requests.
When a capability has pricing information, clients should handle payments before making requests. The payment process follows these steps:
- Payment Request: Client sends a payment request to the server with the capability identifier
- Invoice Generation: Server generates an invoice (e.g., Lightning Network invoice)
- Payment Verification: Client pays the invoice and provides proof of payment
- Capability Access: Once payment is verified, the server processes the capability request
Payment verification can be implemented using Lightning Network zaps (NIP-57) or other payment methods. The specific payment flow is implementation-dependent, but servers should include payment verification before processing paid capability requests.
For servers that are not publicly announced, clients MUST use the MCP initialization process. The flow involves a client initialization request, a server initialization response, and a client initialized notification:
{
"kind": 25910,
"content": {
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
},
"tags": [
["p", "<provider-pubkey>"],
["s", "<server-identifier>"], // Optional field, if ommited all the servers from the providers SHOULD respond
["method", "initialize"]
]
}- Tags:
p: Provider public key, to target all the servers from a providers: Server identifier, optional for target a servermethod: Method name
{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"protocolVersion": "2025-03-26",
"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>"],
["d", "<server-identifier>"],
["support_encryption", "true"] // Whether or not the server supports encryption.
]
}When a server responds to an initialization request, it includes a d tag in the response that serves as the server identifier. Clients should use this identifier in the s tag of subsequent requests to target this specific server. This approach allows clients to discover and interact with servers without prior knowledge of their identifiers.
- Tags:
d: Server identifier, uniquely identifies this server for future requestse: Reference to the client's initialization request event
After receiving the server initialization response, the client MUST send an initialized notification to indicate it is ready to begin normal operations:
{
"kind": 21316,
"pubkey": "<client-pubkey>",
"content": {
"method": "notifications/initialized"
},
"tags": [
["p", "<provider-pubkey>"], // Required: Target provider public key
["s", "<server-identifier>"], // Required: Server identifier
["method", "notifications/initialized"] // Required: Same as method in content
]
}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.
After initialization, clients can interact with server capabilities, even if the server is public, and its exposing capabilities publicly, you can still requesting list tools, resources, prompts, in order to use pagination if necessary
DVMCP provides a consistent pattern for listing capabilities (tools, resources, and prompts). All list operations follow the same structure, with the specific capability type indicated in the method name.
{
"kind": 25910,
"pubkey": "<client-pubkey>",
"id": "<request-event-id>",
"content": {
"method": "<capability>/list", // tools/list, resources/list, or prompts/list
"params": {
"cursor": "optional-cursor-value"
}
},
"tags": [
["method", "<capability>/list"], // Required: Same as method in content for filtering
["p", "<provider-pubkey>"], // Required: Provider's public key
["s", "<server-identifier>"] // Required: Server identifier
]
}{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"<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
]
}Tool Item:
{
"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"]
}
}{
"kind": 25910,
"pubkey": "<client-pubkey>",
"content": {
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
},
"tags": [
["method", "tools/call"],
["p", "<provider-pubkey>"],
["s", "<server-identifier>"]
]
}{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
},
"tags": [["e", "<request-event-id>"]]
}Resource Item Example:
{
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"description": "Primary application entry point",
"mimeType": "text/x-rust"
}{
"kind": 25910,
"pubkey": "<client-pubkey>",
"content": {
"method": "resources/read",
"params": {
"uri": "file:///project/src/main.rs"
}
},
"tags": [
["method", "resources/read"],
["p", "<provider-pubkey>"],
["s", "<server-identifier>"]
]
}{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"contents": [
{
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}"
}
]
},
"tags": [
["e", "<request-event-id>"] // Required: Reference to the request event
]
}Prompt Item Example:
{
"name": "code_review",
"description": "Asks the LLM to analyze code quality and suggest improvements",
"arguments": [
{
"name": "code",
"description": "The code to review",
"required": true
}
]
}{
"kind": 25910,
"pubkey": "<client-pubkey>",
"content": {
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello():\n print('world')"
}
}
},
"tags": [
["method", "prompts/get"],
["p", "<provider-pubkey>"],
["s", "<server-identifier>"]
]
}{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this Python code:\ndef hello():\n print('world')"
}
}
]
},
"tags": [
["e", "<request-event-id>"] // Required: Reference to the request event
]
}This section describes the DVMCP-native implementation for the completions capability, defining discovery, message formats, and workflow for argument autocompletion related to prompts and resources. The completions protocol brings IDE-like contextual suggestions into DVMCP while remaining fully interoperable with JSON-RPC/MCP.
Completions in DVMCP enable servers to deliver argument and URI autocompletion for prompts and resources. This improves end-user experience by providing interactive, responsive suggestions as users type parameters or select options. Clients can leverage this support to offer dropdown suggestion UIs, command auto-fill, or automated argument validation.
To advertise completion support, servers must include the "completions" capability in both announcement and initialization response events.
- Server Announcement Event Example (public servers, kind 31316):
{ "protocolVersion": "2025-03-26", "capabilities": { "tools": { "listChanged": true }, "resources": { "subscribe": true, "listChanged": true }, "prompts": { "listChanged": true }, "completions": {} // Announce completions support }, "serverInfo": { "name": "ExampleServer", "version": "1.0.0" } }
Be sure to always signal the completions capability if the server supports it, so clients know to offer dynamic argument suggestions.
Clients use the completion/complete method to obtain autocompletion suggestions. All request/response pairs use the standard DVMCP pattern with simplified JSON-RPC content fields.
-
Completion Request Event (kind 25910):
{ "kind": 25910, "pubkey": "<client-pubkey>", "content": "{\"method\":\"completion/complete\",\"params\":{\"ref\":{\"type\":\"ref/prompt\",\"name\":\"code_review\"},\"argument\":{\"name\":\"language\",\"value\":\"py\"}}}", "tags": [ ["method", "completion/complete"], ["p", "<provider-pubkey>"], ["s", "<server-identifier>"] ] }- The
reffield identifies the target (either prompt or resource). - The
argumentobject containsname(argument key being completed) and the partialvalue(the current, possibly incomplete, user input). - All tag requirements and event fields mirror core DVMCP conventions.
- The
-
Completion Response Event (kind 26910):
{ "kind": 26910, "pubkey": "<provider-pubkey>", "content": "{\"completion\":{\"values\":[\"python\",\"pytorch\",\"pyside\"],\"total\":10,\"hasMore\":true}}", "tags": [["e", "<request-event-id>"]] }- The response includes a
completionobject with an array of suggestion values, the optional total number of matches, and ahasMoreflag for paging or indicating more results exist.
- The response includes a
The protocol distinguishes two reference types:
| Type | Description | Example |
|---|---|---|
ref/prompt |
Reference a prompt by name | {"type": "ref/prompt", "name": "code_review"} |
ref/resource |
Reference a resource URI | {"type": "ref/resource", "uri": "file:///readme"} |
- Servers return up to 100 suggestions per response, sorted by relevance.
- If there are additional results, set
hasMore: trueand optionally providetotal.
The lifecycle for completions in DVMCP includes:
- Capability Discovery
- Client detects completion support via server capabilities in announcement (31316) or initialization (26910).
- Completion Request
- Client sends a
completion/completeevent (25910) with the target ref, argument name, and (in-progress) argument value.
- Client sends a
- Server Suggestion Logic
- Server matches/filters autocompletion candidates for the prompt/resource and argument, sorts by relevance or fuzzy similarity, and rates limits requests as needed.
- Response Return
- Server responds with suggested values, total matches (if calculated), and
hasMorefor result paging.
- Server responds with suggested values, total matches (if calculated), and
- Iterative Interaction
- User continues typing, client sends incremental
completion/completeevents and receives refined suggestions each time.
- User continues typing, client sends incremental
- Client Integration
- UI shows dropdown, popup, or inline suggestions per business logic. Clients are expected to debounce rapid requests.
Scenario: A client is completing the language argument for a prompt called "code_review".
-
Client initiates completion:
{ "kind": 25910, "pubkey": "abc...", "content": "{\"method\":\"completion/complete\",\"params\":{\"ref\":{\"type\":\"ref/prompt\",\"name\":\"code_review\"},\"argument\":{\"name\":\"language\",\"value\":\"py\"}}}", "tags": [ ["method", "completion/complete"], ["p", "provider123"], ["s", "server456"] ] } -
Server responds:
{ "kind": 26910, "pubkey": "provider123", "content": "{\"completion\":{\"values\":[\"python\",\"pytorch\",\"pyside\"],\"total\":12,\"hasMore\":true}}", "tags": [["e", "<client-request-event-id>"]] }The client displays
"python","pytorch","pyside"as suggested completions for thelanguageargument.
All completion/complete requests and responses use DVMCP error conventions:
- Protocol errors (e.g., unsupported method, invalid params) use the
errorobject at the root of the response. - Tooling errors can use
content: [...]withisError: true. - Error codes:
-32601: Method not found (completions not supported by server)-32602: Invalid parameter or missing argument-32603: Internal server error
Example error response:
{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
"error": {
"code": -32601,
"message": "Completion capability not supported"
}
},
"tags": [["e", "<request-event-id>"]]
}sequenceDiagram
participant Client as DVMCP Client
participant Server as DVMCP Server
Client->>Server: completion/complete (argument in progress)
Server-->>Client: Return suggestions (values)
Note over Client,Server: User continues typing/refines argument
Client->>Server: completion/complete (refined value)
Server-->>Client: Updated suggestions
The Model Context Protocol includes an optional ping mechanism that allows either party to verify that their counterpart is still responsive and the connection is alive. DVMCP implements this feature following the same request/response pattern as other protocol operations while adapting it to the Nostr event-based architecture.
The ping functionality enables connection health monitoring between DVMCP clients and servers. Either the client or server can initiate a ping to verify the connection status and responsiveness of their counterpart.
Ping requests follow the standard DVMCP message structure using the simplified JSON-RPC format:
{
"kind": 25910,
"pubkey": "<sender-pubkey>",
"content": {
"method": "ping"
},
"tags": [
["method", "ping"], // Required: Method name for filtering
["p", "<recipient-pubkey>"], // Required: Target public key (provider or client)
["s", "<server-identifier>"] // Optional: Server identifier when targeting specific server
]
}The receiver MUST respond promptly with an empty response following DVMCP's simplified response format:
{
"kind": 26910,
"pubkey": "<recipient-pubkey>",
"content": {},
"tags": [
["e", "<ping-request-event-id>"] // Required: Reference to the ping request event
]
}-
Prompt Response: The receiver MUST respond promptly with an empty response as shown above.
-
Timeout Handling: If no response is received within a reasonable timeout period, the sender MAY:
- Consider the connection stale
- Terminate the connection
- Attempt reconnection procedures
- Log the failure for diagnostic purposes
-
Bidirectional Support: Both clients and servers MAY initiate ping requests:
- Client-initiated pings: Use the provider's public key in the
ptag - Server-initiated pings: Use the client's public key in the
ptag
- Client-initiated pings: Use the provider's public key in the
sequenceDiagram
participant Sender as Sender (Client/Server)
participant Receiver as Receiver (Server/Client)
Sender->>Receiver: ping request (kind 25910)
Receiver->>Sender: empty response (kind 26910)
Note over Sender,Receiver: Connection verified as healthy
- Implementations MAY periodically issue pings to detect connection health
- Timeouts SHOULD be appropriate for the network environment (recommended: 10-15 seconds)
- Excessive pinging SHOULD be avoided to reduce network overhead and relay load
- Timeouts: SHOULD be treated as connection failures
- Multiple Failed Pings: MAY trigger connection reset or server unavailability status
- Protocol Errors: If a ping request is malformed, standard DVMCP error responses should be used
- Logging: Implementations SHOULD log ping failures for diagnostics
If a ping cannot be processed due to protocol errors:
{
"kind": 26910,
"pubkey": "<recipient-pubkey>",
"content": {
"error": {
"code": -32603,
"message": "Internal error processing ping"
}
},
"tags": [["e", "<ping-request-event-id>"]]
}DVMCP supports optional end-to-end encryption for enhanced privacy and security using the Nostr protocol's encryption standards. This feature leverages NIP-17 (Private Direct Messages) for secure message encryption and NIP-59 (Gift Wrap) for metadata protection, ensuring that:
- Message Content Privacy: All DVMCP message content is encrypted using NIP-44 encryption
- Metadata Protection: Gift wrapping hides participant identities, timestamps, and message patterns
- Forward Secrecy: Messages can be configured for automatic expiration
- Selective Encryption: Clients and servers can negotiate encryption on a per-session basis
Encryption in DVMCP maintains full compatibility with the standard protocol while adding an additional privacy layer. When encryption is enabled, all ephemeral events (requests, responses, and notifications) are encrypted using the NIP-17/NIP-59 pattern, while addressable events (server announcements and capability lists) remain unencrypted for discoverability.
Encryption support is advertised through the support_encryption tag in server announcement events:
{
"kind": 31316,
"pubkey": "<provider-pubkey>",
"tags": [
["d", "<server-identifier>"],
["support_encryption", "true"]
// ... other tags
]
// ... rest of announcement
}Clients can discover encryption support by:
- Public Server Discovery: Check for the
support_encryptiontag in server announcements (kind 31316) - Direct Discovery: Include encryption capability in initialization requests and check server responses
- Non-announced/Direct Discovery: For servers that are not publicly announced or during direct discovery, clients wanting to use encryption should attempt to use encryption first and fall back to unencrypted communication if desired or if encryption fails
- Dynamic Negotiation: Attempt encrypted communication and fall back to unencrypted if not supported
When encryption is enabled, DVMCP messages follow the NIP-17 pattern with NIP-59 gift wrapping:
The original DVMCP message content is prepared as usual:
{
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "New York" }
}
}The DVMCP content is encrypted and sealed as an unsigned kind 14 event:
{
"id": "<usual hash>",
"pubkey": "<sender-pubkey>",
"created_at": "<timestamp>",
"kind": 14,
"tags": [
["p", "<receiver-pubkey>"],
["s", "<server-identifier>"] // Optional: Server identifier when targeting specific server
],
"content": "<original-dvmcp-content-as-plaintext>"
}This unsigned event is then sealed (kind 13) with NIP-44 encryption:
{
"id": "<seal-hash>",
"pubkey": "<sender-pubkey>",
"created_at": "<randomized-timestamp>",
"kind": 13,
"tags": [],
"content": "<nip44-encrypted-kind-14>",
"sig": "<sender-signature>"
}The seal is gift-wrapped (kind 1059) with a random key:
{
"id": "<gift-wrap-hash>",
"pubkey": "<random-pubkey>",
"created_at": "<randomized-timestamp>",
"kind": 1059,
"tags": [["p", "<receiver-pubkey>"]],
"content": "<nip44-encrypted-seal>",
"sig": "<random-key-signature>"
}When encryption is active, DVMCP events are transformed as follows:
{
"kind": 25910,
"pubkey": "<client-pubkey>",
"content": "{\"method\":\"tools/call\",\"params\":{\"name\":\"get_weather\",\"arguments\":{\"location\":\"New York\"}}}",
"tags": [
["method", "tools/call"],
["p", "<provider-pubkey>"],
["s", "<server-identifier>"]
]
}{
"kind": 1059,
"pubkey": "<random-ephemeral-pubkey>",
"created_at": "<randomized-timestamp>",
"content": "<nip44-encrypted-seal-containing-dvmcp-message>",
"tags": [["p", "<provider-pubkey>"]],
"sig": "<ephemeral-key-signature>"
}The inner content (after decryption) maintains the original DVMCP structure with all tags and metadata intact.
Server responses follow the same pattern:
{
"kind": 1059,
"pubkey": "<random-ephemeral-pubkey>",
"created_at": "<randomized-timestamp>",
"content": "<nip44-encrypted-seal-containing-dvmcp-response>",
"tags": [["p", "<client-pubkey>"]],
"sig": "<ephemeral-key-signature>"
}The decrypted inner content contains the standard DVMCP response format.
DVMCP encryption follows Nostr's standard key management practices:
- Client Identity: Client's main Nostr private/public key pair
- Server Identity: Provider's Nostr private/public key pair
- Conversation Keys: Derived using NIP-44 key derivation between client and server keys
- Gift Wrap Keys: Random one-time-use key pairs for each gift wrap
- Key Rotation: New ephemeral keys generated for each message
- Key Disposal: Ephemeral private keys discarded immediately after use
- Public Key Exchange: Client and server public keys exchanged during discovery/initialization
- Relay Preferences: Clients should respect server's preferred relays (kind 10050) for encrypted message delivery
- Capability Advertisement: Servers MUST advertise encryption support in announcements
- Client Detection: Clients SHOULD attempt encrypted communication when supported
- Graceful Fallback: Implementations MUST handle encryption failures gracefully
- Mixed Mode Support: Systems MAY support both encrypted and unencrypted sessions simultaneously
- Relay Selection: Use recipient's preferred relays (NIP-10050) for encrypted messages
- Delivery Confirmation: Implement appropriate timeout and retry mechanisms
- Relay Privacy: Choose relays that respect encrypted message privacy
- Encryption Overhead: Account for additional processing time for encryption/decryption
- Message Size: Encrypted messages are larger due to wrapping layers
- Caching: Avoid caching decrypted content; re-decrypt as needed
- Key Hygiene: Properly dispose of ephemeral keys after use
- Timestamp Randomization: Randomize timestamps within 2-day windows
- Metadata Minimization: Avoid leaking patterns through timing or relay selection
- Forward Secrecy: Support message expiration through appropriate tagging
- Decryption Failures: Handle gracefully with appropriate error messages
- Key Mismatches: Provide clear feedback for key-related issues
- Relay Failures: Implement retry logic for delivery failures
sequenceDiagram
participant Client as DVMCP Client
participant Relay as Nostr Relay
participant Server as DVMCP Server
Note over Client,Server: Encryption Negotiation
Client->>Relay: Check server announcement (kind 31316)
Relay-->>Client: Server supports encryption (support_encryption: true)
Note over Client,Server: Encrypted Request Flow
Client->>Client: Prepare DVMCP request
Client->>Client: Create unsigned kind 14 with DVMCP content
Client->>Client: Seal as kind 13 (encrypted)
Client->>Client: Gift wrap as kind 1059 (double encrypted)
Client->>Relay: Publish encrypted gift wrap
Relay-->>Server: Deliver encrypted message
Server->>Server: Unwrap gift wrap (kind 1059)
Server->>Server: Decrypt seal (kind 13)
Server->>Server: Extract DVMCP request (kind 14)
Server->>Server: Process DVMCP request
Note over Client,Server: Encrypted Response Flow
Server->>Server: Prepare DVMCP response
Server->>Server: Create unsigned kind 14 with response
Server->>Server: Seal as kind 13 (encrypted)
Server->>Server: Gift wrap as kind 1059 (double encrypted)
Server->>Relay: Publish encrypted response
Relay-->>Client: Deliver encrypted response
Client->>Client: Decrypt and process response
Notifications in DVMCP are divided into two categories: MCP-compliant notifications that follow the Model Context Protocol specification, and Nostr-specific notifications that leverage Nostr's event-based architecture for features like payment handling.
For notifications, we use an ephemeral event type (21316), meaning they are not expected to be stored by relays.
For MCP-compliant notifications, the content field follows the same pattern as other MCP messages, containing a stringified simplified JSON-RPC object that adheres to the MCP specification.
The direction of the notifications is determined by the p tag used. Client to server notifications are signed by the client pubkey and use the server pubkey as p tag, server to client notifications are signed by the server's provider pubkey and use the client pubkey as p tag.
{
"kind": 21316,
"pubkey": "<provider-pubkey>",
"content": {
"method": "notifications/<type>",
"params": {
/* Optional parameters */
}
},
"tags": [
["p", "<client-pubkey>"], // Required: Target public key (recipient)
["method", "notifications/<type>"], // Required: Same as method in content
["s", "<server-identifier>"], // Optional: Server identifier (for Client to Server notifications)
["e", "<request-event-id>"] // Optional: Reference to the request (for progress/cancel)
]
}| Notification Type | Method | Direction | Parameters | Description |
|---|---|---|---|---|
| Initialized | notifications/initialized |
Client → Server | None | Sent after initialization to indicate client is ready (required for Direct Discovery) |
| Tools List Changed | notifications/tools/list_changed |
Server → Client | None | Sent when the list of available tools changes |
| Resources List Changed | notifications/resources/list_changed |
Server → Client | None | Sent when the list of available resources changes |
| Resource Updated | notifications/resources/updated |
Server → Client | uri: Resource URI |
Sent when a specific resource is updated |
| Prompts List Changed | notifications/prompts/list_changed |
Server → Client | None | Sent when the list of available prompts changes |
| Progress | notifications/progress |
Server → Client | id: Request IDmessage: Status message |
Indicates progress on long-running operations |
| Cancel | notifications/cancel |
Client → Server | id: Request ID |
Cancels an in-progress operation |
Note: Progress notifications include an additional e tag referencing the request event ID:
["e", "<request-id>"]
// Only for progress notificationsFor long-running jobs, servers should send progress notifications frequently to indicate the job is still processing and to prevent client timeout.
For Nostr-specific features like payment handling, we use the event tags while keeping the content empty:
{
"kind": 21316,
"pubkey": "<provider-pubkey>",
"content": "",
"tags": [
["status", "payment-required"], // Required: Indicates payment is needed
["amount", "1000", "lnbc..."], // Required: Amount in sats and Lightning invoice
["e", "<job-request-id>"] // Required: Reference to the original request
]
}DVMCP handles two types of errors: protocol errors and execution errors.
| Error Type | Description | Format |
|---|---|---|
| Protocol Error | JSON-RPC protocol-level errors (invalid method, params, etc.) | Error object in content with error code and message |
| Execution Error | Errors during tool execution (API failures, business logic errors) | Object with isError: true and error details in content |
{
"kind": 26910,
"pubkey": "<provider-pubkey>",
"content": {
// Either an error object (protocol error):
"error": {
"code": -32602, // Standard JSON-RPC error code
"message": "Error description"
},
// Or a direct response with isError flag (execution error):
"content": [
{
"type": "text",
"text": "Error details"
}
],
"isError": true
},
"tags": [
["e", "<request-event-id>"] // Required: Reference to the request event
]
}Common Error Codes:
-32700: Parse error-32600: Invalid request-32601: Method not found-32602: Invalid params-32603: Internal error-32002: Resource not found
- Use consistent server identifiers in the
dtags - Structure event content as valid stringified simplified JSON-RPC objects (without jsonrpc version, id fields, and nested result objects) according to MCP specification
- Respond to initialization requests with proper capability information
- Process the initialized notification for Direct Discovery connections
- Include appropriate error information for failed requests
- Process notifications according to the MCP specification
- Use standard Nostr tags for Nostr-specific features (like payments)
- Respond promptly to ping requests with empty responses
- Advertise encryption support through the
support_encryptiontag when available - Handle both encrypted and unencrypted messages when encryption is supported
- Properly decrypt NIP-17/NIP-59 wrapped messages when encryption is enabled
- Use appropriate key management practices for encryption keys
- Include the proper server reference in the
stag for all requests - Parse simplified JSON-RPC responses from the event content (without jsonrpc version, id fields, and nested result objects)
- Handle error conditions appropriately
- Track event IDs for request-response correlation
- Subscribe to notifications from the server is interacting with
- Send the initialized notification when using Direct Discovery (private servers)
- Handle ping requests and responses appropriately for connection health monitoring
- Respect server encryption capabilities and negotiate appropriately
- Implement proper NIP-17/NIP-59 encryption when communicating with encryption-enabled servers
- Handle decryption failures gracefully with appropriate fallback mechanisms
sequenceDiagram
participant Client as Nostr Client/DVMCP-Discovery
participant Relay as Nostr Relay
participant DVM as DVMCP-Bridge
participant Server as MCP Server
rect rgb(240, 240, 240)
Note over Client,Server: Discovery Path A: Public Server
Note over Relay: Server events already published to relay
Client->>Relay: Subscribe to kind:31316 events (Server Announcements)
Relay-->>Client: Server announcements
Client->>Relay: Subscribe to kind:31317 events (Tools List)
Relay-->>Client: Available tools
Client->>Relay: Subscribe to kind:31318 events (Resources List)
Relay-->>Client: Available resources
Client->>Relay: Subscribe to kind:31319 events (Prompts List)
Relay-->>Client: Available prompts
end
rect rgb(240, 240, 240)
Note over Client,Server: Discovery Path B: Direct Request
Client->>DVM: kind:25910, method:initialize
DVM->>Server: Initialize connection
Server-->>DVM: Capability response
DVM-->>Client: kind:26910, Server capabilities
Client->>DVM: kind:21316, method:notifications/initialized
Note over Client,DVM: Direct capability requests
Client->>DVM: kind:25910, method:tools/list
DVM->>Server: Request tools list
Server-->>DVM: Tools list
DVM-->>Client: kind:26910, Tools list
Note over Client,DVM: (Similar flows for resources/list and prompts/list)
end
Note over Client,Server: Tool Execution (Same for both paths)
Client->>DVM: kind:25910, method:tools/call
rect rgb(230, 240, 255)
Note over Client,DVM: Optional Payment Flow
DVM-->>Client: kind:21316, status:payment-required
Client->>DVM: Payment
end
DVM->>Server: Execute Tool
Server-->>DVM: Results
DVM-->>Client: kind:26910, Tool results
Note over Client,Server: Resource Access
Client->>DVM: kind:25910, method:resources/read
DVM->>Server: Read Resource
Server-->>DVM: Resource contents
DVM-->>Client: kind:26910, Resource content
Note over Client,Server: Prompt Access
Client->>DVM: kind:25910, method:prompts/get
DVM->>Server: Get Prompt
Server-->>DVM: Prompt content
DVM-->>Client: kind:26910, Prompt content
Note over Client,Server: MCP Notifications
Server->>DVM: Resource updated
DVM-->>Client: kind:21316, notifications/resources/updated
Note over Client,Server: Connection Health Monitoring
Client->>DVM: kind:25910, method:ping
DVM-->>Client: kind:26910, empty response
Unlike direct MCP connections, Nostr's pub/sub model requires special handling for long-lived subscriptions:
-
Connection Persistence: Clients and servers SHOULD maintain relay connections to receive notifications for subscribed resources.
-
Progress Notifications: For long-running operations, servers SHOULD send progress notifications as defined in the protocol to:
- Indicate processing status
- Prevent client timeouts
- Maintain subscription activity
-
Subscription Termination: Subscriptions can be terminated in several ways:
- When the client receives a successful result
- When an error occurs during processing
- When the client explicitly sends a cancellation request
- When the connection times out due to network issues