{
  "markdown": "# nostr-ops-mcp\n\n**A NOSTR identity for your LLM agent.** MCP server that exposes NOSTR protocol primitives — sign, publish, query, NIP-19 encode/decode, NIP-05 lookup, encrypted DMs — as tools your agent can call. Drop it into Claude Desktop, Claude Code, Cursor, or any MCP-speaking client. Hand the agent a NIP-46 bunker key (not a raw nsec). Set a kind allowlist. Let it post on your behalf within rails you control.\n\n> **v0.1 — full read + write + DM surface.** 16 tools wrapped in a defense-in-depth safety stack: kind allowlist (deny-by-default), recipient allowlist, rate limits, optional two-step confirmation, structured audit log. Supports both nsec (dev) and NIP-46 bunker (production).\n\n---\n\n## What you can do with this\n\n- **A bot that publishes kind:1 notes from your npub** — daily summaries, scheduled posts, programmatic reactions to incoming events.\n- **A NOSTR sales agent** — pair with [`marketplace-mcp`](https://npmjs.com/package/marketplace-mcp) to publish NIP-15 stalls + products as the same identity.\n- **Profile management** — `nostr_publish_metadata` for kind:0 (always demands confirmation — overwriting your profile is irreversible without older relay data).\n- **DM-driven workflows** — a storefront agent that watches incoming DMs (with `nostr_list_dms`), decrypts orders, and replies via `nostr_send_dm`. Default-off behind `NOSTR_DM_TOOLS_ENABLED`.\n- **Cross-server identity** — share the same NIP-46 bunker URI across `nostr-ops-mcp` and `marketplace-mcp`. One key, one identity, two specialized tool surfaces.\n\nThe safety stack is the load-bearing reason this is usable in production: an agent with the keys to publish *as you* can ruin your reputation in seconds if unconstrained. The server enforces what kinds it'll sign, what rate, optional second-step confirmation, and writes every call to a structured audit log.\n\n---\n\n## The sixteen tools\n\n### Read-only — local (no network, no signer needed)\n\n| Tool | Purpose |\n|---|---|\n| `nostr_decode` | Parse NIP-19 strings (npub / nsec / note / nevent / naddr / nprofile). `nsec` decoding gated behind `NOSTR_ALLOW_NSEC_DECODE=true`. |\n| `nostr_encode` | Build NIP-19 strings from raw fields. `nsec` encoding deliberately not supported. |\n\n### Read-only — network (no signer needed for query/profile/nip05)\n\n| Tool | Purpose |\n|---|---|\n| `nostr_get_pubkey` | Returns the signer's pubkey + npub. Errors clearly if no signer configured. |\n| `nostr_list_relays` | Configured relay pool + each relay's connection status. |\n| `nostr_query_events` | The workhorse. NIP-01 filters: `kinds`, `authors`, `e_tag` / `p_tag` / `d_tag` / `t_tag` tag filters (mapped to NIP-01 `#e`/`#p`/`#d`/`#t` internally — renamed in v0.2.0 for hosted-API schema compatibility), `since` / `until` / `limit`. |\n| `nostr_get_profile` | Fetch + parse kind:0 metadata for a pubkey or npub. Returns the parsed JSON content (name, about, picture, nip05, lud16, …). |\n| `nostr_verify_nip05` | Resolve `name@domain` → pubkey via `/.well-known/nostr.json`. Optional `expected_pubkey` for verification mode. |\n\n### Write (require signer; gated by KindAllowlist + RateLimiter + optional confirm)\n\n| Tool | Purpose |\n|---|---|\n| `nostr_publish_event` | The primitive write tool. Pass kind / content / tags. |\n| `nostr_publish_text_note` | Convenience for kind:1. Reply/mention/hashtag shortcuts auto-assemble into NIP-10 tags. |\n| `nostr_publish_metadata` | Kind:0 profile. **Always demands two-step confirmation** regardless of `NOSTR_REQUIRE_CONFIRM` — overwriting your profile is hard to reason about. |\n| `nostr_publish_addressable_event` | Kinds 30000–39999 (replaceable). Sets the `d` tag automatically. The bridge `marketplace-mcp` uses for NIP-15. |\n| `nostr_delete_event` | NIP-09 kind:5 soft delete. Best-effort — relays may ignore. |\n| `nostr_confirm_publish` | Execute a token-gated publish. Single-use; safety pipeline re-runs. |\n\n### DMs (highest-risk; default-off via `NOSTR_DM_TOOLS_ENABLED=true`)\n\n| Tool | Purpose |\n|---|---|\n| `nostr_send_dm` | NIP-04 (kind:4) DM with NIP-44 encryption by default; NIP-04 supported for legacy compat. Gated by `NOSTR_DM_ALLOWLIST`. |\n| `nostr_list_dms` | Fetch + decrypt the thread with a counterparty. Auto-detects NIP-44 vs NIP-04 per event. |\n| `nostr_decrypt_dm` | Decrypt a single ciphertext (when you already have the event from elsewhere). |\n\nNIP-17 sealed/gift-wrapped DMs are not yet supported — deferred to a future v0.2 (rumor events + gift-wrapping add nontrivial complexity).\n\n---\n\n## Requirements\n\n- Node 20+\n- A NOSTR signer — **strongly preferred:** a NIP-46 bunker URI from Amber (Android), nsec.app (web), or any other NIP-46 implementation. **Legacy path:** a raw nsec in `.env`. The server logs a stderr warning at startup when nsec-on-disk is detected.\n\n## Install\n\n```bash\n# From npm (once published)\nnpx -y nostr-ops-mcp\n\n# From source\ngit clone <repo>\ncd nostr-ops-mcp\ncorepack enable pnpm\npnpm install\npnpm build\n```\n\n## Configure\n\n```bash\ncp .env.example .env\n# edit .env: set NOSTR_NIP46_URI (recommended) OR NOSTR_PRIVATE_KEY\n#           set NOSTR_RELAYS (comma-separated wss://)\n#           set NOSTR_ALLOWED_KINDS (required when a signer is configured)\n```\n\nThe server auto-loads `.env` from this binary's own directory (next to `dist/`) — deliberately NOT from cwd, to avoid env-var collision when multiple MCP servers run in the same Claude Code session.\n\n### Required\n\n| Var | Purpose |\n|---|---|\n| `NOSTR_RELAYS` | Comma-separated `wss://` relays. Server refuses to start if empty. |\n| `NOSTR_ALLOWED_KINDS` | Comma-separated event-kind numbers the server may sign. Required when a signer is configured. Example: `1,30017,30018` for text-note + NIP-15 marketplace. Default omits kind:0 (profile) and kind:5 (delete) — both easy to misuse. |\n\n### Signer — provide AT MOST one\n\n| Var | Purpose |\n|---|---|\n| `NOSTR_NIP46_URI` | `bunker://<pubkey>?relay=...&secret=...` from Amber / nsec.app / Alby Account / any NIP-46 bunker. **Recommended.** |\n| `NOSTR_PRIVATE_KEY` | Raw `nsec1...`. Dev/legacy only. Server warns at startup. |\n\n### Optional safety knobs\n\n| Var | Default | Purpose |\n|---|---|---|\n| `NOSTR_READ_ONLY` | `false` | Force read-only — disables all write tools. |\n| `NOSTR_DM_TOOLS_ENABLED` | `false` | Opt-in for `send_dm` / `list_dms` / `decrypt_dm`. |\n| `NOSTR_DM_ALLOWLIST` | unset | Hex pubkeys allowed as DM recipients. Empty = `NOSTR_DM_TOOLS_ENABLED` alone gates. |\n| `NOSTR_REQUIRE_CONFIRM` | `false` | Two-step confirm: write tools return a token, `nostr_confirm_publish` executes. |\n| `NOSTR_MAX_EVENTS_PER_MINUTE` | `10` | Rolling 60s rate limit on writes. |\n| `NOSTR_MAX_DMS_PER_MINUTE` | `5` | Same but for DMs. |\n| `NOSTR_ALLOW_NSEC_DECODE` | `false` | Allow `nostr_decode` to return raw private key material. **Don't enable unless you really need it.** |\n| `NOSTR_LOG_PATH` | `./nostr-mcp.log` | Server log path. |\n| `NOSTR_AUDIT_PATH` | `./nostr-mcp-audit.log` | Structured audit log (one JSON line per tool call). |\n\n---\n\n## Wire into an MCP client\n\n### Claude Code (project-scoped)\n\n```bash\nclaude mcp add nostr-ops -s project node \"$(pwd)/dist/index.js\"\n```\n\n### Claude Desktop / Cursor / other clients\n\n```json\n{\n  \"mcpServers\": {\n    \"nostr-ops\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"nostr-ops-mcp\"],\n      \"env\": {}\n    }\n  }\n}\n```\n\nBecause the server loads its own `.env`, leave the `env` block empty in the client config — keep secrets out of any committed file.\n\n---\n\n## Safety model\n\nEvery write tool runs the pipeline in this order:\n\n1. **`NOSTR_READ_ONLY` gate** — refuse outright.\n2. **Signer presence** — refuse if neither nsec nor NIP-46 URI is configured.\n3. **KindAllowlist** — refuse if the event kind isn't in `NOSTR_ALLOWED_KINDS`.\n4. **RateLimiter** — refuse if the rolling 60s `events` bucket is full.\n5. **Confirm gate** — if `NOSTR_REQUIRE_CONFIRM=true` (or the tool always-confirms, like `publish_metadata`), return a 16-byte hex token instead of signing.\n6. **Sign + publish** — via NDK; the signer handshake completes lazily on first use (relevant for NIP-46 where the bunker handshake is async).\n7. **Audit log** — append-only JSON line for every attempt (ok / blocked / error).\n\nDM tools add three more checks on top: `NOSTR_DM_TOOLS_ENABLED`, `DmAllowlist` (per-recipient), and a separate `dms` rate bucket.\n\n**The floor is your signer.** If using NIP-46, the bunker can refuse any sign request — that's the strongest safety boundary. This server's checks are belt-and-suspenders on top.\n\n### Verifying calls actually went through\n\n```bash\ntail -n 5 nostr-mcp-audit.log\n```\n\nSuccessful publish: `{\"ts\":\"...\",\"tool\":\"nostr_publish_text_note\",\"outcome\":\"ok\",\"result\":{\"event_id\":\"...\",\"relays_accepted\":[...]}}`. Blocked / error lines are equally structured. The audit log is **append-only by intent** — rotate it as part of your operational hygiene.\n\n---\n\n## Testing\n\n```bash\npnpm typecheck   # tsc --noEmit\npnpm test        # 13 vitest cases (KindAllowlist, RateLimiter, nip19 roundtrip)\npnpm build       # dist/index.js (~58 KB ESM bundle)\n```\n\nFor end-to-end testing against live relays, configure a throwaway nsec + a couple of public relays (damus.io, nos.lol) and run a small loop: `nostr_publish_text_note` → `nostr_query_events` to confirm the note round-tripped. The `nostr_send_dm` → `nostr_list_dms` loop validates the DM path (you can DM yourself for a closed-loop check).\n\n---\n\n## Companion servers\n\n- [`nwc-mcp`](https://npmjs.com/package/nwc-mcp) — Lightning wallet over NWC. Pair these to build sats-spending NOSTR agents.\n- `marketplace-mcp` — NIP-15 marketplace publish (Shopstr-compatible). Uses the same signer setup as this server.\n\n---\n\n## License\n\nMIT — see [`LICENSE`](./LICENSE).\n\n## Contact / Issues\n\nBuilt by **LLMOps.Pro**.\n\n- **NOSTR:** [`npub1hdg932jvwc3jdvkqywgqv0ue4nn60exrf92asy8mtazt3hjg7d2s2yw0nw`](https://njump.me/npub1hdg932jvwc3jdvkqywgqv0ue4nn60exrf92asy8mtazt3hjg7d2s2yw0nw) — follow, DM, zap.\n- **Lightning Address:** `sovereigncitizens@getalby.com` — for support zaps and \"this was useful\" tips.\n- **Bug reports / feature requests:** open a GitHub issue (link forthcoming).\n- **Security issues:** please disclose privately via NOSTR DM before opening a public issue.\n",
  "bytes": 10212,
  "sha": "7e4a9dfb649e677c13f103e555be5d083f9110b585f3d2ddeb375bc7bd0646cc",
  "repo_slug": "llmops-pro/nostr-ops-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_llmops_pro_nostr_ops_mcp_0d3bed30/readme"
}