feat: public profiles, NIP-05 identity, search & presence tools#28
Merged
Conversation
…P-05
Add REST endpoints for reading any user's profile and batch-resolving
display names, MCP tools for agent profile access, kind:0 side-effect
sync from Nostr metadata events, and a working NIP-05 identity endpoint.
REST API:
- GET /api/users/{pubkey}/profile — read any user's profile (auth required)
- POST /api/users/batch — batch resolve display names (max 200, dedup,
case-normalize; invalid-length inputs returned in `missing`)
MCP tools:
- get_user_profile — read own or any user's profile by pubkey
- get_users_batch — batch resolve display names for message attribution
Kind:0 side effect:
- NIP-01 profile metadata events now sync to the users table
- Absolute-state semantics: absent fields are cleared (set to NULL)
- Fields: display_name (with name fallback), avatar_url (picture/image),
about, nip05_handle
- Empty strings converted to NULL via empty_to_none() to respect the
UNIQUE constraint on nip05_handle
NIP-05 (/.well-known/nostr.json):
- Replaces empty stub with working identity verification
- Exact domain match against relay_url (no LIKE wildcards)
- Access-Control-Allow-Origin: * header (NIP-05 spec requirement)
- No authentication required (public discovery endpoint)
DB changes:
- update_user_profile gains nip05_handle parameter
- New get_user_by_nip05 function (exact match, not LIKE)
- No schema migrations required
Tests:
- 12 new REST API e2e tests (profile CRUD, batch, NIP-05, auth, edges)
- 3 new MCP e2e tests (self profile, other profile, batch)
- Tool count assertion updated from 36 to 38
- All 55 e2e tests pass (32 REST + 10 MCP + 13 relay)
- Update MCP tool count from 36 to 38 across all references - Add get_user_profile and get_users_batch to Profile tools table - Update integration test counts: 32 REST, 10 MCP, 55 total (plus 7 workflow tests noted separately) - Add B-8 exercise: Bob tests profile resolution via MCP tools - Add C-8 exercise: Charlie verifies NIP-05 identity endpoint - Add C-9 exercise: Charlie tests profile lookup edge cases - Update A-5 to set Alice's NIP-05 handle (alice@localhost) - Update Expected Results table with profile and NIP-05 rows - Update 'as of' date to 2026-03-11
…, update TESTING.md - Add 'search' MCP tool wrapping GET /api/search (Typesense full-text) - Add 'get_presence' MCP tool wrapping GET /api/presence (bulk lookup) - Fix set_profile to support nip05_handle field (was silently dropped) - Thread nip05_handle through UpdateProfileBody → update_user_profile - Update TESTING.md section 6 tool table: correct all tool names, add missing tools (40 total), remove nonexistent tools - Fix exercises A-5, B-6 for accuracy; add bootstrap channel timing note - Fix expected results: Charlie in profiles map (not missing list) - Update e2e test assertions: 38 → 40 tools
…ests Crossfire R1 + R2 fixes: - Validate nip05_handle format (user@domain) and restrict domain to relay's own domain on both REST and kind:0 ingestion paths - Return 409 Conflict on duplicate nip05_handle (was generic 500) - Allow empty string to clear nip05_handle (empty → NULL via DB layer) - Kind:0 side effect now validates NIP-05 format and domain before syncing to users table (invalid handles silently cleared) - Add percent_encode to get_presence MCP tool for consistency - Add 3 NIP-05 integration tests: round-trip set+lookup, clear handle, duplicate handle conflict - Update doc comments to include nip05_handle - Make extract_domain pub(crate) for reuse across modules
a7e5578 to
f0faadf
Compare
…lience Crossfire R3 fixes: - Extract shared canonicalize_nip05() in nip05.rs — lowercases local+domain, validates format and relay domain match, used by both REST and kind:0 paths - Batch endpoint: 64-char non-hex inputs now correctly reported in missing list instead of silently dropped - Kind:0 duplicate NIP-05: retry profile sync without contested handle so display_name/about/avatar_url are still written (UNIQUE violation no longer drops all profile fields) - Add kind:0 NIP-05 regression test: valid handle syncs, off-domain cleared - Update batch test to cover non-hex 64-char inputs
f0faadf to
f5b534f
Compare
tlongwell-block
added a commit
that referenced
this pull request
Mar 11, 2026
* origin/main: Plumb desktop profile identity UI (#32) Add unread indicators to desktop channels (#31) feat: public profiles, NIP-05 identity, search & presence tools (#28) Add desktop settings page (#30) Remove OS titlebar and add custom window chrome (#29) # Conflicts: # TESTING.md # crates/sprout-mcp/src/server.rs # crates/sprout-test-client/tests/e2e_mcp.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Public user profiles with display name resolution, NIP-05 identity verification, and two new MCP tools — crossfire-reviewed across 3 rounds (Opus 9/10, Codex 8/10).
What Changed
REST API (2 new endpoints)
GET /api/users/{pubkey}/profile— read any user's profile by pubkey (authenticated)POST /api/users/batch— bulk-resolve display names and NIP-05 handles for up to 200 pubkeys; invalid inputs (wrong-length and non-hex) correctly reported inmissingPUT /api/users/me/profile— now supportsnip05_handlefield with format validation, relay-domain restriction, and lowercase canonicalizationNIP-05 Identity Verification
GET /.well-known/nostr.json?name=alice— standard NIP-05 endpoint with CORS supportcanonicalize_nip05()validator used by both REST and kind:0 paths — lowercases local+domain before storageKind:0 Event Sync
display_name,about,avatar_url, andnip05to the users tableempty_to_nonehelper respects UNIQUE constraint onnip05_handleMCP Tools (2 new + 1 fixed)
search— full-text search across messages via Typesense (GET /api/search)get_presence— bulk presence lookup by pubkey (GET /api/presence)set_profile— fixed to supportnip05_handle(was silently dropped)Error Handling
nip05_handlereturns409 Conflict(was generic 500)nip05_handle(REST and kind:0 paths)Testing (22 new tests)
cargo fmt,cargo clippy,cargo checkall cleanTESTING.md Updates
Files Changed (12 files, +1374/-75)
crates/sprout-db/src/user.rsupdate_user_profilenip05 param,get_user_by_nip05,empty_to_nonecrates/sprout-db/src/lib.rscrates/sprout-relay/src/api/users.rscrates/sprout-relay/src/api/nip05.rscanonicalize_nip05()crates/sprout-relay/src/api/mod.rscrates/sprout-relay/src/router.rscrates/sprout-relay/src/handlers/side_effects.rscrates/sprout-mcp/src/server.rssearch,get_presencetools +set_profilenip05 fixcrates/sprout-test-client/tests/e2e_mcp.rscrates/sprout-test-client/tests/e2e_rest_api.rscrates/sprout-test-client/tests/e2e_relay.rsTESTING.mdCrossfire Review History
Deferred
set_presenceMCP tool (requires new REST endpoint or WS event dispatch)get_channel_feed/get_user_channelsMCP toolscreated_attimestamp ordering guard (pre-existing gap)