{"id":"5ad03dfca4396e5a3bb7b9d0d61f9b4050d1a3bb818902c37a58e8a657955d46","pubkey":"da19f1cd34beca44be74da4b306d9d1dd86b6343cef94ce22c49c6f59816e5bd","created_at":1775138324,"kind":30817,"tags":[["d","machine-application-handlers"],["title","NIP-MACHINE-HANDLERS: Machine Application Handlers"],["k","31990"]],"content":"NIP-MACHINE-HANDLERS\n======\n\nMachine Application Handlers\n-----------------------------\n\n`draft` `optional`\n\nAuthors: [forgesworn](https://github.com/forgesworn)\n\nThis 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.\n\n> **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.\n\n> **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.\n\n## Rationale\n\nNIP-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:\n\n- NIP-90 Data Vending Machines publish `kind:31990` events to advertise supported job kinds\n- NIP-46 remote signers publish `kind:31990` events with connection metadata (`relay`, `nostrconnect_url` tags)\n- MCP servers handle Nostr events programmatically but have no standard way to advertise their transport\n\nEach 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.\n\nMCP (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.\n\nNIP-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.\n\n### Relationship to community NIPs\n\nSeveral 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.\n\n## Specification\n\nA 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.\n\n### Handler information\n\n```jsonc\n{\n  \"kind\": 31990,\n  \"pubkey\": \"<application-pubkey>\",\n  \"content\": \"<optional-kind:0-style-metadata>\",\n  \"tags\": [\n    [\"d\", <random-id>],\n    [\"k\", <supported-event-kind>],\n    [\"alt\", \"MCP handler for kind <supported-event-kind>\"],\n    [\"mcp\", \"<endpoint>\", \"<transport>\"]\n  ]\n}\n```\n\n### Tags\n\n| Tag | Status | Description |\n|-----|--------|-------------|\n| `d` | REQUIRED | Random identifier. Combines with pubkey for deduplication per NIP-89. |\n| `k` | REQUIRED | Supported event kind(s). Multiple `k` tags allowed. Same semantics as NIP-89. |\n| `mcp` | REQUIRED | MCP transport endpoint. At least one MUST be present. Multiple allowed. |\n| `alt` | RECOMMENDED | Human-readable description per NIP-31 for clients that do not support this NIP. |\n\nThe `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.\n\n### MCP transport tag\n\nThe `mcp` tag describes how to connect to the MCP server:\n\n```\n[\"mcp\", <endpoint>, <transport>]\n```\n\nTwo transport values are defined:\n\n#### `stdio` -- local process\n\n```jsonc\n[\"mcp\", \"npx nostr-bray\", \"stdio\"]\n```\n\nThe 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.\n\n#### `http` -- remote HTTP endpoint\n\n```jsonc\n[\"mcp\", \"https://mcp.example.com/sse\", \"http\"]\n```\n\nThe endpoint is an HTTPS URL. The client connects using the MCP Streamable HTTP transport. Endpoints MUST use HTTPS.\n\n### Multiple transports\n\nA handler MAY include multiple `mcp` tags offering different transports for the same server:\n\n```jsonc\n{\n  \"kind\": 31990,\n  \"pubkey\": \"<pubkey>\",\n  \"content\": \"{\\\"name\\\":\\\"My MCP Server\\\",\\\"about\\\":\\\"Handles paid API events\\\"}\",\n  \"tags\": [\n    [\"d\", \"my-mcp-server\"],\n    [\"k\", \"31402\"],\n    [\"alt\", \"MCP handler for kind 31402 (paid API services)\"],\n    [\"mcp\", \"npx my-mcp-server\", \"stdio\"],\n    [\"mcp\", \"https://mcp.example.com/sse\", \"http\"]\n  ]\n}\n```\n\nClients 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.\n\n## Discovery Flow\n\n```mermaid\nsequenceDiagram\n    participant App as MCP Client / AI Agent\n    participant Relay as Nostr Relay\n    participant Handler as MCP Server\n\n    App->>Relay: 1. REQ {kinds:[31990], #k:[\"31402\"]}\n    Relay-->>App: 2. EVENT (kind 31990 with mcp tags)\n    App->>App: 3. Parse mcp tags, select transport\n    alt stdio transport\n        App->>Handler: 4a. Spawn process (npx nostr-bray)\n        Handler-->>App: 5a. MCP handshake over stdin/stdout\n    else http transport\n        App->>Handler: 4b. Connect to HTTPS endpoint\n        Handler-->>App: 5b. MCP handshake over SSE\n    end\n    App->>Handler: 6. tools/list\n    Handler-->>App: 7. Available tools and schemas\n    App->>Handler: 8. tools/call\n    Handler-->>App: 9. Result\n```\n\n### By event kind\n\nA client that encounters an unknown event kind and wants to find an MCP handler:\n\n```\n[\"REQ\", <id>, { \"kinds\": [31990], \"#k\": [<desired-event-kind>] }]\n```\n\nFilter results for events containing `mcp` tags. Optionally, use NIP-89's `kind:31989` recommendation flow to rank handlers by social graph trust.\n\n### Coexistence with NIP-89\n\nA 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:\n\n```jsonc\n{\n  \"kind\": 31990,\n  \"pubkey\": \"<pubkey>\",\n  \"content\": \"{\\\"name\\\":\\\"402.pub\\\",\\\"about\\\":\\\"L402 service directory\\\"}\",\n  \"tags\": [\n    [\"d\", \"402-pub\"],\n    [\"k\", \"31402\"],\n    [\"alt\", \"L402 service directory -- web viewer and MCP server\"],\n    [\"web\", \"https://402.pub/#<bech32>\", \"naddr\"],\n    [\"mcp\", \"npx 402-mcp\", \"stdio\"],\n    [\"mcp\", \"https://mcp.402.pub/sse\", \"http\"]\n  ]\n}\n```\n\n### Coexistence with NIP-46\n\nA remote signer that also offers an MCP interface MAY include both NIP-46 tags and `mcp` tags on the same `kind:31990` event:\n\n```jsonc\n{\n  \"kind\": 31990,\n  \"pubkey\": \"<signer-pubkey>\",\n  \"content\": \"{\\\"name\\\":\\\"My Signer\\\",\\\"about\\\":\\\"Remote signer with MCP interface\\\"}\",\n  \"tags\": [\n    [\"d\", \"my-signer\"],\n    [\"k\", \"24133\"],\n    [\"alt\", \"Remote signer (NIP-46) with MCP transport\"],\n    [\"relay\", \"wss://relay.example.com\"],\n    [\"nostrconnect_url\", \"bunker://<signer-pubkey>?relay=wss://relay.example.com\"],\n    [\"mcp\", \"https://signer.example.com/mcp\", \"http\"]\n  ]\n}\n```\n\n## Security Considerations\n\n### stdio transport\n\nThe `stdio` transport instructs clients to execute a shell command. This is a significant attack surface.\n\nClients MUST:\n- Only execute `stdio` commands from handler events published by explicitly trusted pubkeys. Discovering a handler via relay query alone is NOT sufficient trust.\n- Prompt the user for confirmation before spawning any process, displaying the exact command to be executed.\n- Never pass untrusted event content or tag values as arguments to the spawned command without sanitisation.\n\nClients SHOULD:\n- Maintain an allowlist of trusted handler pubkeys or use NIP-89 `kind:31989` recommendations from the user's social graph as a trust signal.\n- Sandbox spawned processes where the operating system supports it.\n\n### http transport\n\nEndpoints MUST use HTTPS. Clients MUST NOT connect to plain HTTP endpoints.\n\nClients SHOULD verify the handler pubkey's reputation via NIP-89 recommendations or other trust mechanisms before sending requests to unknown HTTP endpoints.\n\n### General\n\nHandler 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.\n\n## Why not existing approaches?\n\n### Why not NIP-89 as-is?\n\nNIP-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.\n\n### Why not NIP-90 DVM discovery?\n\nNIP-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.\n\n### Why not a new event kind?\n\nThere 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.\n\n### Why not community agent NIPs?\n\nCommunity 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.\n\n## Reference implementations\n\n- [nostr-bray](https://github.com/forgesworn/bray) -- MCP server for Nostr, publishes `kind:31990` handler events\n- [402-mcp](https://github.com/forgesworn/402-mcp) -- MCP server for L402/x402 paid API consumption\n- [satgate](https://github.com/forgesworn/satgate) -- Lightning-paid AI inference gateway, announces via kind 31402","sig":"e0a83d97c8c3df4dcd34ec5b0ba9a954b89a90a526969e85e38f1b60910f5f2ac27ed52a4d9aeec867859ff6965b855c82399119e0a9d18285bc469f8a7461ce"}