NIP-MACHINE-HANDLERS: Machine Application Handlers
NIP-MACHINE-HANDLERS
Machine Application Handlers
draft optional
Authors: forgesworn
This NIP extends NIP-89 by defining how kind:31990 handler information events can advertise machine-to-machine transports, specifically MCP (Model Context Protocol) servers. It introduces no new event kinds.
Standalone. This NIP works independently on any Nostr application. It composes with NIP-89 (handler discovery), NIP-90 (DVM announcements), and NIP-46 (remote signers), but does not depend on them.
Design principle: Discovery tells you where to connect, not what to do once connected. Transport metadata belongs in the handler event; capability negotiation belongs in the protocol handshake.
Rationale
NIP-89 enables clients to discover applications that handle unknown event kinds. Its platform tags (web, ios, android) assume human-facing applications with URL templates. However, machine-to-machine handlers are increasingly common on Nostr:
- NIP-90 Data Vending Machines publish
kind:31990events to advertise supported job kinds - NIP-46 remote signers publish
kind:31990events with connection metadata (relay,nostrconnect_urltags) - MCP servers handle Nostr events programmatically but have no standard way to advertise their transport
Each project has invented its own approach: DVMs ignore platform tags entirely, NIP-46 uses custom tags, and MCP bridge projects have built parallel discovery layers. This NIP standardises machine handler discovery using NIP-89's existing tag convention.
MCP (Model Context Protocol) is an open standard for machine tool interoperability adopted by Anthropic, OpenAI, Google, Microsoft, Cursor, and others. It is not a single-vendor protocol.
NIP-89 does not define a closed set of platform tags. NIP-46 already extends kind:31990 with signer-specific tags for machine-to-machine use. This NIP follows the same pattern for MCP transports.
Relationship to community NIPs
Several community NIPs address agent communication (kinds 25800-25806, 31430) and trust service discovery (kinds 37570-37576). These define new event kinds for agent-to-agent messaging and capability negotiation. This NIP takes a deliberately different approach: no new kinds, no messaging protocol, just transport metadata on existing handler events. It operates at the discovery layer, not the communication layer, and composes with any messaging or trust NIP.
Specification
A machine application handler is a standard kind:31990 event (as defined in NIP-89) with one or more mcp platform tags describing how to connect to the handler.
Handler information
{
"kind": 31990,
"pubkey": "<application-pubkey>",
"content": "<optional-kind:0-style-metadata>",
"tags": [
["d", <random-id>],
["k", <supported-event-kind>],
["alt", "MCP handler for kind <supported-event-kind>"],
["mcp", "<endpoint>", "<transport>"]
]
}Tags
| Tag | Status | Description |
|---|---|---|
d | REQUIRED | Random identifier. Combines with pubkey for deduplication per NIP-89. |
k | REQUIRED | Supported event kind(s). Multiple k tags allowed. Same semantics as NIP-89. |
mcp | REQUIRED | MCP transport endpoint. At least one MUST be present. Multiple allowed. |
alt | RECOMMENDED | Human-readable description per NIP-31 for clients that do not support this NIP. |
The content field follows the NIP-89 convention: an optional stringified JSON object matching kind:0 metadata (name, about, picture). If empty, clients SHOULD fall back to the pubkey's kind:0 profile.
MCP transport tag
The mcp tag describes how to connect to the MCP server:
["mcp", <endpoint>, <transport>]Two transport values are defined:
stdio -- local process
["mcp", "npx nostr-bray", "stdio"]The endpoint is a shell command. The client spawns the process and communicates over stdin/stdout per the MCP specification. See Security Considerations for constraints on stdio execution.
http -- remote HTTP endpoint
["mcp", "https://mcp.example.com/sse", "http"]The endpoint is an HTTPS URL. The client connects using the MCP Streamable HTTP transport. Endpoints MUST use HTTPS.
Multiple transports
A handler MAY include multiple mcp tags offering different transports for the same server:
{
"kind": 31990,
"pubkey": "<pubkey>",
"content": "{\"name\":\"My MCP Server\",\"about\":\"Handles paid API events\"}",
"tags": [
["d", "my-mcp-server"],
["k", "31402"],
["alt", "MCP handler for kind 31402 (paid API services)"],
["mcp", "npx my-mcp-server", "stdio"],
["mcp", "https://mcp.example.com/sse", "http"]
]
}Clients SHOULD select the best available transport based on their capabilities. A suggested preference order is stdio (lowest latency) > http (widely supported), though clients MAY override this based on user preferences or security policy.
Discovery Flow
sequenceDiagram
participant App as MCP Client / AI Agent
participant Relay as Nostr Relay
participant Handler as MCP Server
App->>Relay: 1. REQ {kinds:[31990], #k:["31402"]}
Relay-->>App: 2. EVENT (kind 31990 with mcp tags)
App->>App: 3. Parse mcp tags, select transport
alt stdio transport
App->>Handler: 4a. Spawn process (npx nostr-bray)
Handler-->>App: 5a. MCP handshake over stdin/stdout
else http transport
App->>Handler: 4b. Connect to HTTPS endpoint
Handler-->>App: 5b. MCP handshake over SSE
end
App->>Handler: 6. tools/list
Handler-->>App: 7. Available tools and schemas
App->>Handler: 8. tools/call
Handler-->>App: 9. ResultBy event kind
A client that encounters an unknown event kind and wants to find an MCP handler:
["REQ", <id>, { "kinds": [31990], "#k": [<desired-event-kind>] }]Filter results for events containing mcp tags. Optionally, use NIP-89's kind:31989 recommendation flow to rank handlers by social graph trust.
Coexistence with NIP-89
A single kind:31990 event MAY contain both human-facing platform tags (web, ios) and machine-facing mcp tags. This allows one handler event to serve both audiences:
{
"kind": 31990,
"pubkey": "<pubkey>",
"content": "{\"name\":\"402.pub\",\"about\":\"L402 service directory\"}",
"tags": [
["d", "402-pub"],
["k", "31402"],
["alt", "L402 service directory -- web viewer and MCP server"],
["web", "https://402.pub/#<bech32>", "naddr"],
["mcp", "npx 402-mcp", "stdio"],
["mcp", "https://mcp.402.pub/sse", "http"]
]
}Coexistence with NIP-46
A remote signer that also offers an MCP interface MAY include both NIP-46 tags and mcp tags on the same kind:31990 event:
{
"kind": 31990,
"pubkey": "<signer-pubkey>",
"content": "{\"name\":\"My Signer\",\"about\":\"Remote signer with MCP interface\"}",
"tags": [
["d", "my-signer"],
["k", "24133"],
["alt", "Remote signer (NIP-46) with MCP transport"],
["relay", "wss://relay.example.com"],
["nostrconnect_url", "bunker://<signer-pubkey>?relay=wss://relay.example.com"],
["mcp", "https://signer.example.com/mcp", "http"]
]
}Security Considerations
stdio transport
The stdio transport instructs clients to execute a shell command. This is a significant attack surface.
Clients MUST:
- Only execute
stdiocommands from handler events published by explicitly trusted pubkeys. Discovering a handler via relay query alone is NOT sufficient trust. - Prompt the user for confirmation before spawning any process, displaying the exact command to be executed.
- Never pass untrusted event content or tag values as arguments to the spawned command without sanitisation.
Clients SHOULD:
- Maintain an allowlist of trusted handler pubkeys or use NIP-89
kind:31989recommendations from the user's social graph as a trust signal. - Sandbox spawned processes where the operating system supports it.
http transport
Endpoints MUST use HTTPS. Clients MUST NOT connect to plain HTTP endpoints.
Clients SHOULD verify the handler pubkey's reputation via NIP-89 recommendations or other trust mechanisms before sending requests to unknown HTTP endpoints.
General
Handler events are replaceable (addressable by pubkey + d tag). A compromised pubkey could publish a malicious handler update. Clients SHOULD pin known-good handler events by event ID when operating in automated or unattended mode.
Why not existing approaches?
Why not NIP-89 as-is?
NIP-89's platform tags use URL templates with <bech32> placeholders, designed for redirecting humans to web pages or native apps. Machine handlers need transport metadata (a command to spawn or an HTTP endpoint to connect to), not a URL template. The mcp tag follows NIP-89's tag convention while providing the information machines need to establish a connection.
Why not NIP-90 DVM discovery?
NIP-90 DVMs already use kind:31990 for discovery, but with no transport information -- clients must know how to speak the DVM job protocol (kind 5xxx/6xxx/7xxx). This NIP complements NIP-90 by letting DVMs that also expose an MCP interface advertise it alongside their existing kind:31990 events.
Why not a new event kind?
There is no need. kind:31990 already means "this pubkey handles these event kinds." The only missing piece is how to connect for machine-to-machine use. A new platform tag value conveys this within the existing NIP-89 framework.
Why not community agent NIPs?
Community NIPs for agent communication (kinds 25800-25806) and trust service machines (kinds 37570-37576) define new event kinds for messaging and capability negotiation. This NIP deliberately avoids new kinds. It operates at the discovery layer -- how to find and connect to a machine handler -- not the communication layer. A handler discovered via this NIP could speak any protocol once connected: MCP, DVM jobs, or custom agent messaging.
Reference implementations
- nostr-bray -- MCP server for Nostr, publishes
kind:31990handler events - 402-mcp -- MCP server for L402/x402 paid API consumption
- satgate -- Lightning-paid AI inference gateway, announces via kind 31402