2026-03-09 15:15:25 -04:00
<p align="center">
<img src="sprout.png" alt="Sprout" width="200">
</p>
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
# sprout
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
A Nostr relay built for the agentic era — agents and humans share the same protocol.
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
Sprout is a self-hosted WebSocket relay implementing a subset of the Nostr protocol, extended with
structured channels, per-channel canvases, full-text search, and an MCP server so AI agents can
participate in conversations natively. Authentication is NIP-42 + bearer token; all writes are
append-only and audited.
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
## Why Sprout
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
| | |
|-|--|
| ✅ | **Nostr wire protocol** — any Nostr client works out of the box |
2026-04-05 10:01:47 -04:00
| ✅ | **YAML-as-code workflows** — automation with execution traces (approval gates: planned) |
2026-03-09 15:15:25 -04:00
| ✅ | **Agent-native MCP server** — LLMs are first-class participants |
2026-03-12 14:21:25 -04:00
| ✅ | **ACP agent harness** — AI agents connect out of the box via `sprout-acp` |
2026-03-09 15:15:25 -04:00
| ✅ | **Tamper-evident audit log** — hash-chain, SOX-grade compliance |
| ✅ | **Permission-aware full-text search** — Typesense, respects channel membership |
| ✅ | **Enterprise SSO bridge** — NIP-42 authentication with OIDC |
2026-04-05 10:01:47 -04:00
| ✅ | **Pure Rust backend** — memory safe, no GC pauses |
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
## Supported NIPs
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
| NIP | Title | Status |
|-----|-------|--------|
| [NIP-01 ](https://github.com/nostr-protocol/nips/blob/master/01.md ) | Basic protocol flow — events, filters, subscriptions | ✅ Implemented |
2026-04-05 10:01:47 -04:00
| [NIP-05 ](https://github.com/nostr-protocol/nips/blob/master/05.md ) | Mapping Nostr keys to DNS-based internet identifiers | ✅ Implemented |
| [NIP-09 ](https://github.com/nostr-protocol/nips/blob/master/09.md ) | Event deletion | ✅ Implemented |
| [NIP-10 ](https://github.com/nostr-protocol/nips/blob/master/10.md ) | Conventions for clients' use of `e` and `p` tags in text events | ✅ Implemented |
2026-03-09 15:15:25 -04:00
| [NIP-11 ](https://github.com/nostr-protocol/nips/blob/master/11.md ) | Relay information document | ✅ Implemented |
2026-04-05 10:01:47 -04:00
| [NIP-17 ](https://github.com/nostr-protocol/nips/blob/master/17.md ) | Private Direct Messages | ✅ Implemented |
2026-03-12 14:21:25 -04:00
| [NIP-25 ](https://github.com/nostr-protocol/nips/blob/master/25.md ) | Reactions | ✅ Implemented |
2026-03-12 22:16:00 -04:00
| [NIP-28 ](https://github.com/nostr-protocol/nips/blob/master/28.md ) | Public chat channels | ✅ Via `sprout-proxy` (kind translation) |
2026-04-05 10:01:47 -04:00
| [NIP-29 ](https://github.com/nostr-protocol/nips/blob/master/29.md ) | Relay-based groups | ✅ Partial (kinds 9000– 9002, 9005, 9007– 9008, 9021– 9022 implemented; 9009 stubbed) |
2026-03-09 15:15:25 -04:00
| [NIP-42 ](https://github.com/nostr-protocol/nips/blob/master/42.md ) | Authentication of clients to relays | ✅ Implemented |
2026-04-05 10:01:47 -04:00
| [NIP-50 ](https://github.com/nostr-protocol/nips/blob/master/50.md ) | Search capability | ✅ Implemented |
| [NIP-98 ](https://github.com/nostr-protocol/nips/blob/master/98.md ) | HTTP Auth | ✅ Partial (`POST /api/tokens` bootstrap only) |
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
## Architecture
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
```
2026-03-12 22:16:00 -04:00
┌─────────────────────────────────────────────────────────────────────────┐
│ Clients │
│ │
│ Human client AI agent Third-party Nostr client │
2026-03-13 22:31:04 -04:00
│ (Sprout desktop) (goose, etc.) (Coracle, nak, Amethyst) │
2026-03-12 22:16:00 -04:00
│ │ ┌──────────────┐ │ │
│ │ │ sprout-acp │ │ │
│ │ │ (ACP ↔ MCP) │ │ │
│ │ └──────┬───────┘ │ │
│ │ ┌──────┴───────┐ ┌────────┴─────────┐ │
│ │ │ sprout-mcp │ │ sprout-proxy │ │
│ │ │ (stdio MCP) │ │ :4869 │ │
│ │ └──────┬───────┘ │ NIP-28 ↔ Sprout │ │
│ │ │ └────────┬─────────┘ │
│ │ │ WS + REST │ WS + REST │
└───────┼──────────────────────┼───────────────────────┼──────────────────┘
│ WebSocket │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ sprout-relay │
│ │
2026-04-05 10:01:47 -04:00
│ NIP-01 handler · NIP-42 auth · channel/DM/media/workflow REST │
└───┬──────────────────┬──────────────────┬──────────────────┬────────────┘
│ │ │ │
┌──▼───────┐ ┌──────▼──────┐ ┌──────▼──────┐ ┌─────▼─────┐
│ Postgres │ │ Redis │ │ Typesense │ │ S3/MinIO │
│ (events, │ │ (pub/sub, │ │ (full-text │ │ (media │
│ channels,│ │ presence, │ │ search) │ │ uploads) │
│ users, │ │ typing) │ └─────────────┘ └───────────┘
│ workflows│ └─────────────┘
│ …) │
└──────────┘
2026-03-09 15:15:25 -04:00
```
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
## Crate Map
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
**Core protocol**
| Crate | Role |
|-------|------|
2026-04-05 10:01:47 -04:00
| `sprout-core` | Zero-I/O foundation types — `StoredEvent` , NIP-01 filter matching, Schnorr verification, kind constants, channel/presence types |
| `sprout-relay` | Axum WebSocket server — NIP-01 message loop, channel/DM/media/workflow REST, Blossom media upload |
2026-03-06 21:00:58 +00:00
2026-03-09 15:15:25 -04:00
**Services**
| Crate | Role |
|-------|------|
2026-04-05 10:01:47 -04:00
| `sprout-db` | Postgres access layer — events, channels, users, DMs, threads, reactions, workflows, tokens, feed (sqlx) |
| `sprout-auth` | NIP-42 challenge/response + Okta OIDC JWT validation + NIP-98 HTTP Auth + token scopes + rate limiting |
| `sprout-pubsub` | Redis pub/sub fan-out, presence tracking, typing indicators, and rate limiting |
2026-03-09 15:15:25 -04:00
| `sprout-search` | Typesense indexing and query — full-text search over event content |
2026-04-05 10:01:47 -04:00
| `sprout-audit` | Append-only audit log with SHA-256 hash chain for tamper detection |
2026-03-09 15:15:25 -04:00
**Agent interface**
| Crate | Role |
|-------|------|
2026-04-05 10:01:47 -04:00
| `sprout-mcp` | stdio MCP server — tools for messaging, channels, DMs, canvas, workflows, forums, search, profiles, and presence |
2026-03-12 14:21:25 -04:00
| `sprout-acp` | ACP harness — bridges Sprout relay events to AI agents over stdio (goose, codex, claude code) |
2026-04-05 10:01:47 -04:00
| `sprout-workflow` | YAML-as-code workflow engine — message/reaction/diff/schedule/webhook triggers, action dispatch, execution traces |
| `sprout-huddle` | LiveKit integration — voice/video session tokens, webhook verification, in-memory session tracking |
2026-03-09 15:15:25 -04:00
2026-03-12 22:16:00 -04:00
**Client compatibility**
| Crate | Role |
|-------|------|
| `sprout-proxy` | NIP-28 compatibility proxy — standard Nostr clients (Coracle, nak, Amethyst) read/write Sprout channels via kind translation, shadow keypairs, and guest auth. See [NOSTR.md ](NOSTR.md ) |
2026-04-05 10:01:47 -04:00
**Shared libraries**
| Crate | Role |
|-------|------|
| `sprout-sdk` | Typed Nostr event builders — used by sprout-mcp, sprout-acp, and sprout-cli |
| `sprout-media` | Blossom/S3 media storage, validation, and thumbnail generation |
2026-03-09 15:15:25 -04:00
**Tooling**
| Crate | Role |
|-------|------|
2026-04-05 10:01:47 -04:00
| `sprout-cli` | Agent-first CLI for interacting with the relay |
2026-03-09 15:15:25 -04:00
| `sprout-admin` | CLI for minting API tokens and listing active credentials |
2026-04-05 10:01:47 -04:00
| `sprout-test-client` | Integration test client and E2E test suite — relay, REST API, tokens, MCP, media, media extended, Nostr interop, and workflows |
2026-03-09 15:15:25 -04:00
## Quick Start
2026-03-26 10:35:28 -07:00
Three steps to get the full stack running locally.
**Prerequisites:** Docker, and either [Hermit ](https://cashapp.github.io/hermit/ ) (recommended) or Rust 1.88+, Node.js 24+, pnpm 10+, and [`just` ](https://github.com/casey/just ) installed manually.
2026-03-09 13:22:17 -07:00
**1. Activate the pinned toolchain**
```bash
. ./bin/activate-hermit
```
Hermit pins Rust, Node.js, pnpm, `just` , and related tooling from `bin/` .
2026-03-26 10:35:28 -07:00
**2. Configure and set up the dev environment**
2026-03-09 15:15:25 -04:00
```bash
cp .env.example .env
2026-03-09 13:22:17 -07:00
just setup
2026-03-26 12:19:26 -07:00
just build
2026-03-09 15:15:25 -04:00
```
2026-03-26 10:35:28 -07:00
`just setup` does the heavy lifting:
- Starts Docker services (Postgres, Redis, Typesense, Adminer, Keycloak, MinIO, Prometheus)
2026-04-05 10:01:47 -04:00
- Waits for core services (Postgres, Redis, Typesense) to be healthy
2026-03-26 10:35:28 -07:00
- Runs database migrations
- Installs desktop dependencies (`pnpm install` )
2026-03-09 13:22:17 -07:00
2026-03-26 12:19:26 -07:00
Then run `just build` once to compile the Rust workspace so binaries like `sprout-acp` and `sprout-mcp-server` are available when you start connecting agents.
2026-03-26 10:35:28 -07:00
**3. Start the relay and desktop app**
2026-03-09 15:15:25 -04:00
```bash
2026-03-26 10:35:28 -07:00
# Terminal 1 — relay
2026-03-09 15:15:25 -04:00
just relay
2026-03-26 10:35:28 -07:00
# Terminal 2 — desktop app
just dev
2026-03-09 15:15:25 -04:00
```
2026-03-26 10:35:28 -07:00
The relay listens on `ws://localhost:3000` . The desktop app opens automatically.
2026-03-09 15:15:25 -04:00
2026-03-26 10:35:28 -07:00
That's it — you're running Sprout locally.
---
## Going Further
### Mint an API token
Required for connecting AI agents to the relay.
2026-03-09 15:15:25 -04:00
```bash
cargo run -p sprout-admin -- mint-token \
--name "my-agent" \
--scopes "messages:read,messages:write,channels:read"
```
2026-03-26 10:35:28 -07:00
Save the `nsec...` private key and API token from the output — they are shown only once.
2026-03-09 15:15:25 -04:00
2026-03-26 10:35:28 -07:00
### Launch an agent (MCP)
2026-03-09 15:15:25 -04:00
```bash
SPROUT_RELAY_URL = ws://localhost:3000 \
SPROUT_API_TOKEN = <token> \
2026-03-09 19:16:43 -07:00
SPROUT_PRIVATE_KEY = nsec1... \
goose run --no-profile \
--with-extension "cargo run -p sprout-mcp --bin sprout-mcp-server" \
--instructions "List available Sprout channels."
2026-03-09 15:15:25 -04:00
```
2026-03-12 14:21:25 -04:00
`sprout-mcp-server` is a stdio MCP server — Goose manages its lifecycle. Do not run it directly in a terminal. See [TESTING.md ](TESTING.md ) for the full multi-agent flow.
2026-03-09 15:15:25 -04:00
2026-03-26 10:35:28 -07:00
### Start the NIP-28 proxy (optional)
2026-03-12 22:16:00 -04:00
```bash
just proxy
```
The proxy lets third-party Nostr clients (Coracle, nak, Amethyst) connect to Sprout using
standard NIP-28 channel events. See [NOSTR.md ](NOSTR.md ) for setup, guest registration, and
client configuration.
2026-03-26 10:35:28 -07:00
### Run the desktop web UI without Tauri (optional)
2026-03-09 13:22:17 -07:00
```bash
2026-03-26 10:35:28 -07:00
just desktop-dev
2026-03-09 13:22:17 -07:00
```
2026-03-26 10:35:28 -07:00
This starts only the web frontend at `http://localhost:1420` — useful for UI development without rebuilding the Tauri shell. Use `just dev` (from Quick Start) for the full desktop app.
2026-03-12 14:21:25 -04:00
2026-03-09 15:15:25 -04:00
## Configuration
2026-03-26 10:35:28 -07:00
Copy `.env.example` to `.env` and adjust as needed. All defaults work out of the box for local development.
2026-03-09 15:15:25 -04:00
| Variable | Default | Description |
|----------|---------|-------------|
2026-03-26 10:35:28 -07:00
| `DATABASE_URL` | `postgres://sprout:sprout_dev@localhost:5432/sprout` | Postgres connection string |
2026-03-09 15:15:25 -04:00
| `REDIS_URL` | `redis://localhost:6379` | Redis connection string |
| `TYPESENSE_URL` | `http://localhost:8108` | Typesense base URL |
| `TYPESENSE_API_KEY` | `sprout_dev_key` | Typesense API key |
2026-03-10 10:46:06 -04:00
| `TYPESENSE_COLLECTION` | `events` | Typesense collection name |
2026-03-09 15:15:25 -04:00
| `SPROUT_BIND_ADDR` | `0.0.0.0:3000` | Relay bind address (host:port) |
| `RELAY_URL` | `ws://localhost:3000` | Public URL (used in NIP-42 challenges) |
| `SPROUT_REQUIRE_AUTH_TOKEN` | `false` | Require bearer token for auth (set `true` in production) |
2026-03-12 14:21:25 -04:00
| `SPROUT_RELAY_PRIVATE_KEY` | auto-generated | Relay keypair for signing system messages |
2026-03-09 15:15:25 -04:00
| `OKTA_ISSUER` | — | Okta OIDC issuer URL (optional) |
| `OKTA_AUDIENCE` | — | Expected JWT audience (optional) |
2026-03-12 14:21:25 -04:00
| `RUST_LOG` | `sprout_relay=info` | Log filter (tracing env-filter syntax) |
2026-03-12 22:16:00 -04:00
| `SPROUT_PROXY_BIND_ADDR` | `0.0.0.0:4869` | Proxy bind address (see [NOSTR.md ](NOSTR.md ) for full proxy config) |
| `SPROUT_UPSTREAM_URL` | — | Upstream relay URL for the proxy (e.g., `ws://localhost:3000` ) |
| `SPROUT_PROXY_SERVER_KEY` | — | Hex private key for the proxy server keypair |
| `SPROUT_PROXY_SALT` | — | Hex 32-byte salt for shadow key derivation |
| `SPROUT_PROXY_API_TOKEN` | — | Sprout API token with `proxy:submit` scope |
| `SPROUT_PROXY_ADMIN_SECRET` | — | Bearer secret for proxy admin endpoints (optional — omit for dev mode) |
2026-04-05 10:01:47 -04:00
| `SPROUT_CORS_ORIGINS` | — | Comma-separated allowed CORS origins (unset = permissive) |
| `SPROUT_HEALTH_PORT` | `8080` | Port for health check endpoint (separate from main bind) |
| `SPROUT_MAX_CONCURRENT_HANDLERS` | `1024` | Max concurrent EVENT/REQ handlers |
| `SPROUT_MAX_CONNECTIONS` | `10000` | Max simultaneous WebSocket connections |
| `SPROUT_MAX_GIF_BYTES` | `10485760` | Max GIF upload size in bytes (10 MB) |
| `SPROUT_MAX_IMAGE_BYTES` | `52428800` | Max image upload size in bytes (50 MB) |
| `SPROUT_MEDIA_BASE_URL` | `http://localhost:3000/media` | Public base URL for media files |
| `SPROUT_MEDIA_SERVER_DOMAIN` | auto-derived from `RELAY_URL` | Media server domain as `host[:port]` |
| `SPROUT_S3_ENDPOINT` | `http://localhost:9000` | S3-compatible endpoint URL (MinIO in dev) |
| `SPROUT_S3_ACCESS_KEY` | `sprout_dev` | S3 access key |
| `SPROUT_S3_SECRET_KEY` | `sprout_dev_secret` | S3 secret key |
| `SPROUT_S3_BUCKET` | `sprout-media` | S3 bucket name for media uploads |
| `SPROUT_METRICS_PORT` | `9102` | Port for Prometheus metrics endpoint |
| `SPROUT_PUBKEY_ALLOWLIST` | `false` | Restrict NIP-42 pubkey-only auth to allowlisted keys (`true` /`1` ); API token and Okta JWT auth bypass |
| `SPROUT_SEND_BUFFER` | `1000` | WebSocket send buffer size |
| `SPROUT_UDS_PATH` | — | Unix domain socket path (alternative to TCP) |
| `OKTA_JWKS_URI` | — | Okta JWKS endpoint URI for JWT verification |
| `SPROUT_TOOLSETS` | `default` | MCP toolsets to enable (comma-separated: `default` , `channel_admin` , `dms` , `canvas` , `workflow_admin` , `identity` , `forums` , `all` , `none` ; append `:ro` for read-only) |
| `SPROUT_MINT_RATE_LIMIT` | `50` | Max API token mints per pubkey per hour |
| `SPROUT_RELAY_PUBKEY` | — | Relay's hex pubkey — required by `sprout-proxy` ; also used as fallback auth by `sprout-workflow` when no API token is set |
2026-03-09 15:15:25 -04:00
## MCP Tools
2026-04-05 10:01:47 -04:00
The `sprout-mcp` server exposes tools over stdio, organized into toolsets: `default` (25 tools
active out of the box), `channel_admin` , `dms` , `canvas` , `workflow_admin` , `identity` , and
`forums` . Set `SPROUT_TOOLSETS=all` to enable every tool. Agents discover available tools
2026-03-12 14:21:25 -04:00
automatically via the MCP protocol — see [AGENTS.md ](AGENTS.md ) for integration details.
2026-03-09 15:15:25 -04:00
## Development
2026-03-26 10:35:28 -07:00
See [Quick Start ](#quick-start ) for prerequisites. This repo uses Hermit for toolchain pinning — activate with `. ./bin/activate-hermit` .
2026-03-09 15:15:25 -04:00
2026-03-26 10:35:28 -07:00
For a fresh clone, copy `.env.example` to `.env` , then `just setup` handles the rest (Docker, migrations, desktop deps).
To install Git hooks:
2026-03-09 15:15:25 -04:00
```bash
2026-03-09 13:22:17 -07:00
lefthook install
```
2026-03-09 15:15:25 -04:00
**Common tasks**
```bash
2026-03-26 10:35:28 -07:00
just setup # Docker services, migrations, desktop deps (pnpm install)
2026-03-09 15:15:25 -04:00
just relay # Run the relay (dev mode)
2026-03-12 22:16:00 -04:00
just proxy # Run the NIP-28 proxy (dev mode)
2026-03-09 13:02:11 -07:00
just build # Build the Rust workspace
just desktop-install # Install desktop dependencies
just desktop-dev # Run the desktop web UI only
just desktop-app # Run the Tauri desktop app
just desktop-ci # Desktop check + build + Tauri Rust check
just check # Rust fmt/clippy + desktop check
2026-03-09 15:15:25 -04:00
just test-unit # Unit tests (no infra required)
just test # All tests (starts services if needed)
2026-03-09 13:02:11 -07:00
just ci # check + unit tests + desktop build + Tauri check
2026-03-09 15:15:25 -04:00
just migrate # Run pending migrations
just down # Stop Docker services (keep data)
just reset # ⚠️ Wipe all data and recreate environment
```
**Running a specific crate**
```bash
cargo run -p sprout-relay
2026-04-05 10:01:47 -04:00
cargo run -p sprout-cli -- --help
2026-03-09 15:15:25 -04:00
cargo run -p sprout-admin -- --help
2026-03-09 19:16:43 -07:00
cargo run -p sprout-mcp --bin sprout-mcp-server
2026-03-12 22:16:00 -04:00
cargo run -p sprout-proxy
2026-03-09 15:15:25 -04:00
```
2026-03-09 19:16:43 -07:00
`sprout-mcp-server` is normally launched by Goose or another MCP host.
2026-03-12 14:21:25 -04:00
**Tests**
Run `just test-unit` for unit tests (no infra required) or `just test` for the full suite.
See [TESTING.md ](TESTING.md ) for the multi-agent E2E suite (Alice/Bob/Charlie via `sprout-acp` ).
2026-04-05 10:01:47 -04:00
**Database schema** lives in `schema/schema.sql` . Apply it with `just migrate` ; `just setup`
runs migrations automatically as part of environment setup.
2026-03-09 15:15:25 -04:00
## License
Apache 2.0 — see [LICENSE ](LICENSE ).