nostr-bray 1.18.2 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![licence](https://img.shields.io/npm/l/nostr-bray)](./LICENSE)
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-ESM-blue)](./tsconfig.json)
10
10
 
11
- An MCP server that gives AI agents a full Nostr identity — not just a key pair, but a hierarchical identity tree with personas, attestations, ring signatures, encrypted DMs, and duress detection. 74 tools across 10 groups.
11
+ An MCP server that gives AI agents a full Nostr identity — not just a key pair, but a hierarchical identity tree with personas, attestations, ring signatures, encrypted DMs, and duress detection. 77 tools across 10 groups.
12
12
 
13
13
  ## The Problem
14
14
 
@@ -68,140 +68,144 @@ Or with a secret file (recommended):
68
68
 
69
69
  ## Tool Groups
70
70
 
71
- ### Identity (11 tools)
71
+ ### Identity (12 tools)
72
72
 
73
73
  | Tool | Description |
74
74
  |------|-------------|
75
- | `identity_create` | Generate a fresh identity with BIP-39 mnemonic |
76
- | `identity_derive` | Derive a child identity by purpose and index |
77
- | `identity_derive_persona` | Derive a named persona (work, personal, anonymous) |
78
- | `identity_switch` | Switch active identity — all tools operate as the new identity |
79
- | `identity_list` | List all known identities (public info only) |
80
- | `identity_prove` | Create blind/full linkage proof |
81
- | `identity_backup_shamir` | Split master secret into Shamir shard files |
82
- | `identity_restore_shamir` | Reconstruct secret from shard files |
83
- | `identity_backup` | Fetch profile, contacts, relay list as portable bundle |
84
- | `identity_restore` | Re-sign migratable events under the active identity |
85
- | `identity_migrate` | Full migration with preview, confirmation, and linkage proof |
75
+ | `whoami` | Returns the active identity's npub |
76
+ | `identity-create` | Generate a fresh identity with BIP-39 mnemonic |
77
+ | `identity-derive` | Derive a child identity by purpose and index |
78
+ | `identity-derive-persona` | Derive a named persona (work, personal, anonymous) |
79
+ | `identity-switch` | Switch active identity — all tools operate as the new identity |
80
+ | `identity-list` | List all known identities (public info only) |
81
+ | `identity-prove` | Create blind/full linkage proof |
82
+ | `identity-backup-shamir` | Split master secret into Shamir shard files |
83
+ | `identity-restore-shamir` | Reconstruct secret from shard files |
84
+ | `identity-backup` | Fetch profile, contacts, relay list as portable bundle |
85
+ | `identity-restore` | Re-sign migratable events under the active identity |
86
+ | `identity-migrate` | Full migration with preview, confirmation, and linkage proof |
86
87
 
87
88
  ### Social (14 tools)
88
89
 
89
90
  | Tool | Description |
90
91
  |------|-------------|
91
- | `social_post` | Post a text note (kind 1) |
92
- | `social_reply` | Reply with correct e-tag and p-tag threading |
93
- | `social_react` | React to an event (kind 7) |
94
- | `social_delete` | Request deletion of your event (kind 5) |
95
- | `social_repost` | Repost/boost an event (kind 6) |
96
- | `social_profile_get` | Fetch and parse a kind 0 profile |
97
- | `social_profile_set` | Set profile with overwrite safety guard |
98
- | `dm_send` | Send encrypted DM (NIP-17 default, NIP-04 opt-in) |
99
- | `dm_read` | Read and decrypt received DMs |
100
- | `contacts_get` | Fetch contact list (kind 3 follows) |
101
- | `contacts_follow` | Follow a pubkey (publishes updated kind 3) |
102
- | `contacts_unfollow` | Unfollow a pubkey |
103
- | `social_notifications` | Fetch mentions, replies, reactions, zap receipts |
104
- | `social_feed` | Fetch kind 1 text note feed |
92
+ | `social-post` | Post a text note (kind 1) |
93
+ | `social-reply` | Reply with correct e-tag and p-tag threading |
94
+ | `social-react` | React to an event (kind 7) |
95
+ | `social-delete` | Request deletion of your event (kind 5) |
96
+ | `social-repost` | Repost/boost an event (kind 6) |
97
+ | `social-profile-get` | Fetch and parse a kind 0 profile |
98
+ | `social-profile-set` | Set profile with overwrite safety guard |
99
+ | `dm-send` | Send encrypted DM (NIP-17 default, NIP-04 opt-in) |
100
+ | `dm-read` | Read and decrypt received DMs |
101
+ | `contacts-get` | Fetch contact list (kind 3 follows) |
102
+ | `contacts-follow` | Follow a pubkey (publishes updated kind 3) |
103
+ | `contacts-unfollow` | Unfollow a pubkey |
104
+ | `social-notifications` | Fetch mentions, replies, reactions, zap receipts |
105
+ | `social-feed` | Fetch kind 1 text note feed |
105
106
 
106
107
  ### Trust (11 tools)
107
108
 
108
109
  | Tool | Description |
109
110
  |------|-------------|
110
- | `trust_attest` | Create kind 31000 verifiable attestation |
111
- | `trust_read` | Read attestations by subject/type/attestor |
112
- | `trust_verify` | Validate attestation structure |
113
- | `trust_revoke` | Revoke an attestation (identity check) |
114
- | `trust_request` | Send attestation request via NIP-17 |
115
- | `trust_request_list` | Scan DMs for attestation requests |
116
- | `trust_proof_publish` | Publish linkage proof (kind 30078) with confirmation |
117
- | `trust_ring_prove` | Anonymous group membership proof (ring signature) |
118
- | `trust_ring_verify` | Verify ring signature proof |
119
- | `trust_spoken_challenge` | Generate spoken verification token |
120
- | `trust_spoken_verify` | Verify spoken token response |
121
-
122
- ### Relay (4 tools)
111
+ | `trust-attest` | Create kind 31000 verifiable attestation |
112
+ | `trust-read` | Read attestations by subject/type/attestor |
113
+ | `trust-verify` | Validate attestation structure |
114
+ | `trust-revoke` | Revoke an attestation (identity check) |
115
+ | `trust-request` | Send attestation request via NIP-17 |
116
+ | `trust-request-list` | Scan DMs for attestation requests |
117
+ | `trust-proof-publish` | Publish linkage proof (kind 30078) with confirmation |
118
+ | `trust-ring-prove` | Anonymous group membership proof (ring signature) |
119
+ | `trust-ring-verify` | Verify ring signature proof |
120
+ | `trust-spoken-challenge` | Generate spoken verification token |
121
+ | `trust-spoken-verify` | Verify spoken token response |
122
+
123
+ ### Relay (5 tools)
123
124
 
124
125
  | Tool | Description |
125
126
  |------|-------------|
126
- | `relay_list` | List relays with shared-relay warnings |
127
- | `relay_set` | Publish kind 10002 relay list |
128
- | `relay_add` | Add relay to active identity (in-memory) |
129
- | `relay_info` | Fetch NIP-11 relay information document |
127
+ | `relay-list` | List relays with shared-relay warnings |
128
+ | `relay-set` | Publish kind 10002 relay list |
129
+ | `relay-add` | Add relay to active identity (in-memory) |
130
+ | `relay-query` | Query events from relays by kind, author, tags, or time range |
131
+ | `relay-info` | Fetch NIP-11 relay information document |
130
132
 
131
133
  ### Zap (7 tools)
132
134
 
133
135
  | Tool | Description |
134
136
  |------|-------------|
135
- | `zap_send` | Pay a Lightning invoice via NWC |
136
- | `zap_balance` | Request wallet balance via NWC |
137
- | `zap_make_invoice` | Generate a Lightning invoice via NWC |
138
- | `zap_lookup_invoice` | Check invoice payment status via NWC |
139
- | `zap_list_transactions` | List recent Lightning transactions |
140
- | `zap_receipts` | Parse zap receipts (amount, sender, message) |
141
- | `zap_decode` | Decode bolt11 invoice fields |
137
+ | `zap-send` | Pay a Lightning invoice via NWC |
138
+ | `zap-balance` | Request wallet balance via NWC |
139
+ | `zap-make-invoice` | Generate a Lightning invoice via NWC |
140
+ | `zap-lookup-invoice` | Check invoice payment status via NWC |
141
+ | `zap-list-transactions` | List recent Lightning transactions |
142
+ | `zap-receipts` | Parse zap receipts (amount, sender, message) |
143
+ | `zap-decode` | Decode bolt11 invoice fields |
142
144
 
143
145
  ### Safety (2 tools)
144
146
 
145
147
  | Tool | Description |
146
148
  |------|-------------|
147
- | `safety_configure` | Configure an alternative identity persona |
148
- | `safety_activate` | Switch to alternative identity |
149
+ | `safety-configure` | Configure an alternative identity persona |
150
+ | `safety-activate` | Switch to alternative identity |
149
151
 
150
152
  ### Blossom (3 tools)
151
153
 
152
154
  | Tool | Description |
153
155
  |------|-------------|
154
- | `blossom_upload` | Upload file to a blossom media server |
155
- | `blossom_list` | List blobs for a pubkey |
156
- | `blossom_delete` | Delete a blob by SHA-256 hash |
156
+ | `blossom-upload` | Upload file to a blossom media server |
157
+ | `blossom-list` | List blobs for a pubkey |
158
+ | `blossom-delete` | Delete a blob by SHA-256 hash |
157
159
 
158
160
  ### Groups — NIP-29 (4 tools)
159
161
 
160
162
  | Tool | Description |
161
163
  |------|-------------|
162
- | `group_info` | Fetch group metadata |
163
- | `group_chat` | Read group chat messages |
164
- | `group_send` | Send message to a group |
165
- | `group_members` | List group members |
164
+ | `group-info` | Fetch group metadata |
165
+ | `group-chat` | Read group chat messages |
166
+ | `group-send` | Send message to a group |
167
+ | `group-members` | List group members |
166
168
 
167
169
  ### Community NIPs (2 tools)
168
170
 
169
171
  | Tool | Description |
170
172
  |------|-------------|
171
- | `nip_publish` | Publish a community NIP (kind 30817) |
172
- | `nip_read` | Fetch community NIPs |
173
+ | `nip-publish` | Publish a community NIP (kind 30817) |
174
+ | `nip-read` | Fetch community NIPs |
173
175
 
174
- ### Utility (16 tools)
176
+ ### Utility (18 tools)
175
177
 
176
178
  | Tool | Description |
177
179
  |------|-------------|
178
180
  | `decode` | Decode npub/nsec/note/nevent/nprofile/naddr |
179
- | `encode_npub` | Encode hex pubkey as npub |
180
- | `encode_note` | Encode hex event ID as note |
181
- | `encode_nprofile` | Encode pubkey + relays as nprofile |
182
- | `encode_nevent` | Encode event ID + relays as nevent |
183
- | `encode_naddr` | Encode addressable event as naddr |
184
- | `encode_nsec` | Encode hex private key as nsec |
185
- | `key_public` | Derive pubkey from secret key |
186
- | `verify_event` | Verify event hash and signature |
181
+ | `encode-npub` | Encode hex pubkey as npub |
182
+ | `encode-note` | Encode hex event ID as note |
183
+ | `encode-nprofile` | Encode pubkey + relays as nprofile |
184
+ | `encode-nevent` | Encode event ID + relays as nevent |
185
+ | `encode-naddr` | Encode addressable event as naddr |
186
+ | `encode-nsec` | Encode hex private key as nsec |
187
+ | `key-public` | Derive pubkey from secret key |
188
+ | `key-encrypt` | Encrypt a secret key with a password (NIP-49 ncryptsec) |
189
+ | `key-decrypt` | Decrypt an ncryptsec (NIP-49) with a password |
190
+ | `verify-event` | Verify event hash and signature |
187
191
  | `filter` | Test if an event matches a filter |
188
- | `nip44_encrypt` | NIP-44 encrypt for a recipient |
189
- | `nip44_decrypt` | NIP-44 decrypt from a sender |
192
+ | `nip44-encrypt` | NIP-44 encrypt for a recipient |
193
+ | `nip44-decrypt` | NIP-44 decrypt from a sender |
190
194
  | `count` | Count events matching a filter |
191
195
  | `fetch` | Fetch events by nip19 code |
192
- | `nip_list` | List all official NIPs |
193
- | `nip_show` | Show a specific NIP's content |
196
+ | `nip-list` | List all official NIPs |
197
+ | `nip-show` | Show a specific NIP's content |
194
198
 
195
199
  ## Identity Switching
196
200
 
197
201
  The killer feature. Every tool operates as the "active identity." Switch with a single call:
198
202
 
199
203
  ```
200
- identity_derive_persona("work") → npub1abc...
201
- identity_switch("work") → now operating as work persona
202
- social_post("Hello from work!") → signed by npub1abc...
203
- identity_switch("master") → back to master
204
- social_post("Back to main") → signed by master npub
204
+ identity-derive-persona("work") → npub1abc...
205
+ identity-switch("work") → now operating as work persona
206
+ social-post("Hello from work!") → signed by npub1abc...
207
+ identity-switch("master") → back to master
208
+ social-post("Back to main") → signed by master npub
205
209
  ```
206
210
 
207
211
  Derived identities are cryptographically unlinkable unless you publish a linkage proof.
@@ -6,7 +6,15 @@ import { handleBackupShamir, handleRestoreShamir } from './shamir.js';
6
6
  import { hexId } from '../validation.js';
7
7
  import { handleIdentityBackup, handleIdentityRestore, handleIdentityMigrate } from './migration.js';
8
8
  export function registerIdentityTools(server, deps) {
9
- server.registerTool('identity_create', {
9
+ server.registerTool('whoami', {
10
+ description: 'Returns the active identity\'s npub. The simplest way to check which identity is currently in use.',
11
+ annotations: { readOnlyHint: true },
12
+ }, async () => {
13
+ return {
14
+ content: [{ type: 'text', text: deps.ctx.activeNpub }],
15
+ };
16
+ });
17
+ server.registerTool('identity-create', {
10
18
  description: 'Generate a fresh Nostr identity with a BIP-39 mnemonic seed. Returns { npub, mnemonic }. Store the mnemonic securely — it will not be shown again. Use this when the user needs a brand new identity unrelated to the current one.',
11
19
  annotations: { readOnlyHint: false },
12
20
  }, async () => {
@@ -15,7 +23,7 @@ export function registerIdentityTools(server, deps) {
15
23
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
16
24
  };
17
25
  });
18
- server.registerTool('identity_derive', {
26
+ server.registerTool('identity-derive', {
19
27
  description: 'Derive a child Nostr identity from the master key by purpose and index. Deterministic — same inputs always produce the same npub. Returns { npub, purpose, index }. Use identity_switch after deriving to operate as the new identity.',
20
28
  inputSchema: {
21
29
  purpose: z.string().describe('Purpose string for derivation (e.g. "messaging", "signing")'),
@@ -28,7 +36,7 @@ export function registerIdentityTools(server, deps) {
28
36
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
29
37
  };
30
38
  });
31
- server.registerTool('identity_derive_persona', {
39
+ server.registerTool('identity-derive-persona', {
32
40
  description: 'Derive a named persona (e.g. "work", "personal", "anonymous"). Like identity_derive but uses a human-readable name. Returns { npub, personaName, index }. Follow with identity_switch to activate.',
33
41
  inputSchema: {
34
42
  name: z.string().describe('Persona name (e.g. "work", "personal", "anonymous")'),
@@ -41,7 +49,7 @@ export function registerIdentityTools(server, deps) {
41
49
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
42
50
  };
43
51
  });
44
- server.registerTool('identity_switch', {
52
+ server.registerTool('identity-switch', {
45
53
  description: 'Switch the active Nostr identity. ALL subsequent tool calls will sign events and query relays as the new identity. Pass "master" to return to root. Returns { npub } of the now-active identity. This is the key tool for multi-persona workflows.',
46
54
  inputSchema: {
47
55
  target: z.string().describe('Purpose, persona name, or "master" to switch to'),
@@ -54,17 +62,17 @@ export function registerIdentityTools(server, deps) {
54
62
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
55
63
  };
56
64
  });
57
- server.registerTool('identity_list', {
65
+ server.registerTool('identity-list', {
58
66
  description: 'List all known identities (master + all derived). Returns array of { npub, purpose, index, personaName }. Never includes private keys. Use this to see what identities are available before switching.',
59
67
  inputSchema: {
60
- output: z.enum(['json', 'human']).default('json').describe('Response format'),
68
+ output: z.enum(['json', 'human']).default('human').describe('Response format'),
61
69
  },
62
70
  annotations: { readOnlyHint: true },
63
71
  }, async ({ output }) => {
64
72
  const result = handleIdentityList(deps.ctx);
65
73
  return toolResponse(result, output, fmt.formatIdentityList);
66
74
  });
67
- server.registerTool('identity_prove', {
75
+ server.registerTool('identity-prove', {
68
76
  description: 'Create a cryptographic linkage proof between the master key and the active identity. "blind" (default) proves they share a master without revealing how the child was derived. "full" also reveals the purpose and index. Returns a LinkageProof object that can be verified by anyone with nsec-tree. Use trust_proof_publish to make it permanent on relays.',
69
77
  inputSchema: {
70
78
  mode: z.enum(['blind', 'full']).default('blind').describe('Proof mode: "blind" hides derivation path, "full" reveals purpose and index'),
@@ -76,7 +84,7 @@ export function registerIdentityTools(server, deps) {
76
84
  content: [{ type: 'text', text: JSON.stringify(proof, null, 2) }],
77
85
  };
78
86
  });
79
- server.registerTool('identity_backup_shamir', {
87
+ server.registerTool('identity-backup-shamir', {
80
88
  description: 'Split the active identity\'s private key into Shamir shard files using threshold secret sharing. Any "threshold" shards can reconstruct the key. Files written with 0600 permissions via atomic rename. Returns file paths only — shard content never appears in the response.',
81
89
  inputSchema: {
82
90
  threshold: z.number().int().min(2).describe('Minimum shards needed to reconstruct'),
@@ -98,7 +106,7 @@ export function registerIdentityTools(server, deps) {
98
106
  }, null, 2) }],
99
107
  };
100
108
  });
101
- server.registerTool('identity_restore_shamir', {
109
+ server.registerTool('identity-restore-shamir', {
102
110
  description: 'Reconstruct a secret from Shamir shard files. Returns the restored npub for verification.',
103
111
  inputSchema: {
104
112
  files: z.array(z.string()).describe('Paths to shard files'),
@@ -121,7 +129,7 @@ export function registerIdentityTools(server, deps) {
121
129
  }, null, 2) }],
122
130
  };
123
131
  });
124
- server.registerTool('identity_backup', {
132
+ server.registerTool('identity-backup', {
125
133
  description: 'Fetch profile, contacts, relay list, and attestations for a pubkey. Returns a portable JSON bundle (no private keys).',
126
134
  inputSchema: {
127
135
  pubkeyHex: hexId.describe('Hex pubkey to back up'),
@@ -139,7 +147,7 @@ export function registerIdentityTools(server, deps) {
139
147
  }, null, 2) }],
140
148
  };
141
149
  });
142
- server.registerTool('identity_restore', {
150
+ server.registerTool('identity-restore', {
143
151
  description: 'Re-sign migratable events (profile, contacts, relay list) under the active identity. Skips attestations (trust chain protection).',
144
152
  inputSchema: {
145
153
  pubkeyHex: hexId.describe('Hex pubkey of the original identity to restore from'),
@@ -154,7 +162,7 @@ export function registerIdentityTools(server, deps) {
154
162
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
155
163
  };
156
164
  });
157
- server.registerTool('identity_migrate', {
165
+ server.registerTool('identity-migrate', {
158
166
  description: 'Migrate from an old identity to the active one. Shows preview first — set confirm: true to execute. Publishes linkage proof and re-signs migratable events.',
159
167
  inputSchema: {
160
168
  oldPubkeyHex: z.string().describe('Hex pubkey of the old identity'),
package/dist/index.js CHANGED
@@ -32,9 +32,10 @@ export const deps = { ctx: ctx, pool, nip65, nwcUri: config.nwcUri };
32
32
  config.secretKey = '';
33
33
  config.nwcUri = undefined;
34
34
  config.bunkerUri = undefined;
35
- // Load master identity relay list
36
- const masterRelays = await nip65.loadForIdentity(ctx.activeNpub);
37
- pool.reconfigure(ctx.activeNpub, masterRelays);
35
+ // Load master identity relay list in the background — don't block tool registration
36
+ nip65.loadForIdentity(ctx.activeNpub).then(masterRelays => {
37
+ pool.reconfigure(ctx.activeNpub, masterRelays);
38
+ }).catch(e => console.error('NIP-65 relay load failed:', e.message));
38
39
  const server = new McpServer({ name: 'nostr-bray', version: '0.1.0' });
39
40
  // Phase 2: Identity tools
40
41
  registerIdentityTools(server, deps);
@@ -4,18 +4,18 @@ import { toolResponse } from '../tool-response.js';
4
4
  import * as fmt from '../format.js';
5
5
  import { handleRelayList, handleRelaySet, handleRelayAdd, handleRelayInfo, handleRelayQuery } from './handlers.js';
6
6
  export function registerRelayTools(server, deps) {
7
- server.registerTool('relay_list', {
7
+ server.registerTool('relay-list', {
8
8
  description: 'List the relay set (read/write) for the active identity. Optionally check for shared relays with another identity.',
9
9
  inputSchema: {
10
10
  compareWithNpub: z.string().optional().describe('Compare shared relays with this npub'),
11
- output: z.enum(['json', 'human']).default('json').describe('Response format'),
11
+ output: z.enum(['json', 'human']).default('human').describe('Response format'),
12
12
  },
13
13
  annotations: { readOnlyHint: true },
14
14
  }, async ({ compareWithNpub, output }) => {
15
15
  const result = handleRelayList(deps.ctx, deps.pool, compareWithNpub);
16
16
  return toolResponse(result, output, fmt.formatRelays);
17
17
  });
18
- server.registerTool('relay_set', {
18
+ server.registerTool('relay-set', {
19
19
  description: 'Publish a kind 10002 relay list for the active identity. Warns if a relay list already exists.',
20
20
  inputSchema: {
21
21
  relays: z.array(z.object({
@@ -24,7 +24,7 @@ export function registerRelayTools(server, deps) {
24
24
  })).describe('Relay entries'),
25
25
  confirm: z.boolean().default(false).describe('Set true to overwrite existing relay list'),
26
26
  },
27
- annotations: { readOnlyHint: false },
27
+ annotations: { readOnlyHint: false, destructiveHint: true },
28
28
  }, async ({ relays, confirm }) => {
29
29
  const result = await handleRelaySet(deps.ctx, deps.pool, { relays, confirm });
30
30
  return {
@@ -33,7 +33,7 @@ export function registerRelayTools(server, deps) {
33
33
  : { published: true, id: result.event.id }, null, 2) }],
34
34
  };
35
35
  });
36
- server.registerTool('relay_add', {
36
+ server.registerTool('relay-add', {
37
37
  description: 'Add a single relay to the active identity\'s relay set (in-memory only — does not publish kind 10002).',
38
38
  inputSchema: {
39
39
  url: relayUrl.describe('Relay WebSocket URL'),
@@ -46,7 +46,7 @@ export function registerRelayTools(server, deps) {
46
46
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
47
47
  };
48
48
  });
49
- server.registerTool('relay_query', {
49
+ server.registerTool('relay-query', {
50
50
  description: 'Query events from Nostr relays by kind, author, tags, or time range. Useful for discovering events, scanning for specific kinds, or investigating unknown event schemas. Uses explicit relays if provided, otherwise the active identity\'s read relays.',
51
51
  inputSchema: {
52
52
  kinds: z.array(z.number().int()).optional().describe('Event kinds to filter by (e.g. [30301, 31000])'),
@@ -74,7 +74,7 @@ export function registerRelayTools(server, deps) {
74
74
  content: [{ type: 'text', text: JSON.stringify({ count: events.length, events: summary }, null, 2) }],
75
75
  };
76
76
  });
77
- server.registerTool('relay_info', {
77
+ server.registerTool('relay-info', {
78
78
  description: 'Fetch the NIP-11 relay information document for a relay URL.',
79
79
  inputSchema: {
80
80
  url: relayUrl.describe('Relay WebSocket URL (wss://...)'),
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { handleDuressConfigure, handleDuressActivate } from './handlers.js';
3
3
  export function registerSafetyTools(server, deps) {
4
- server.registerTool('safety_configure', {
4
+ server.registerTool('safety-configure', {
5
5
  description: 'Configure an alternative identity persona and pre-warm relay connections.',
6
6
  inputSchema: {
7
7
  personaName: z.string().default('anonymous').describe('Persona name for the alternative identity'),
@@ -14,12 +14,12 @@ export function registerSafetyTools(server, deps) {
14
14
  };
15
15
  });
16
16
  // Description intentionally does NOT mention duress — appears as normal identity switch
17
- server.registerTool('safety_activate', {
17
+ server.registerTool('safety-activate', {
18
18
  description: 'Switch to an alternative identity configuration.',
19
19
  inputSchema: {
20
20
  personaName: z.string().default('anonymous').describe('Persona name to switch to'),
21
21
  },
22
- annotations: { readOnlyHint: false },
22
+ annotations: { readOnlyHint: false, destructiveHint: true },
23
23
  }, async ({ personaName }) => {
24
24
  const result = handleDuressActivate(deps.ctx, { personaName });
25
25
  return {