nostr-bray 1.20.0 → 1.21.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
@@ -16,6 +16,41 @@ AI agents interacting with Nostr today are handed a single key pair with no sepa
16
16
 
17
17
  nostr-bray solves this with nsec-tree hierarchical derivation. A single master secret generates unlimited child identities, each with its own key pair, purpose, and relay set. Private keys are zeroed from memory on eviction. Agents can switch personas mid-conversation, prove they control the master without revealing the derivation path, and activate a duress identity if compromised.
18
18
 
19
+ ## How It Works
20
+
21
+ Every tool operates as the "active identity." Derive a persona, switch to it, and everything you do is signed by that persona's key — cryptographically unlinkable to the master unless you publish a proof.
22
+
23
+ ```
24
+ identity-derive-persona("work") → npub1abc...
25
+ identity-switch("work") → now operating as work persona
26
+ social-post("Hello from work!") → signed by npub1abc...
27
+ identity-switch("master") → back to master
28
+ social-post("Back to main") → signed by master npub
29
+ ```
30
+
31
+ This is not just key management — it is context isolation. Each persona has its own relay set, its own contact list, and its own attestation chain. Compromise one and the others remain intact.
32
+
33
+ ## Comparison
34
+
35
+ | Feature | nostr-bray | nak | nostr-mcp | nostr-tools |
36
+ |---------|-----------|-----|-----------|-------------|
37
+ | MCP server | 77 tools | [via `nak mcp`](https://github.com/fiatjaf/nak) | 5–23 tools | — |
38
+ | CLI | 77 commands | ✓ (mature) | — | — |
39
+ | Hierarchical identity (nsec-tree) | ✓ | — | — | — |
40
+ | Persona switching | ✓ | — | — | — |
41
+ | Ring signatures (anonymous proofs) | ✓ | — | — | — |
42
+ | Shamir backup (BIP-39 words) | ✓ | — | — | — |
43
+ | Duress detection | ✓ | — | — | — |
44
+ | NIP-VA attestations (kind 31000) | ✓ | — | — | — |
45
+ | Linkage proofs (blind/full) | ✓ | — | — | — |
46
+ | NIP-46 bunker (server + client) | ✓ | ✓ (client) | partial | — |
47
+ | NIP-17 encrypted DMs | ✓ | ✓ | ✓ | ✓ |
48
+ | NWC Lightning payments | ✓ | — | — | — |
49
+ | Blossom media | ✓ | ✓ | — | — |
50
+ | NIP-29 groups | ✓ | — | — | — |
51
+ | Tor routing (SOCKS5h) | ✓ | — | — | — |
52
+ | 329 tests, 96% coverage | ✓ | — | — | — |
53
+
19
54
  ## Quick Start — CLI
20
55
 
21
56
  ```bash
@@ -68,7 +103,7 @@ Or with a secret file (recommended):
68
103
 
69
104
  ## Tool Groups
70
105
 
71
- ### Identity (12 tools)
106
+ ### Identity (12 tools) — create, derive, switch, prove, backup, and migrate Nostr identities
72
107
 
73
108
  | Tool | Description |
74
109
  |------|-------------|
@@ -85,7 +120,7 @@ Or with a secret file (recommended):
85
120
  | `identity-restore` | Re-sign migratable events under the active identity |
86
121
  | `identity-migrate` | Full migration with preview, confirmation, and linkage proof |
87
122
 
88
- ### Social (14 tools)
123
+ ### Social (14 tools) — post, reply, react, DM, follow, and read feeds
89
124
 
90
125
  | Tool | Description |
91
126
  |------|-------------|
@@ -104,7 +139,7 @@ Or with a secret file (recommended):
104
139
  | `social-notifications` | Fetch mentions, replies, reactions, zap receipts |
105
140
  | `social-feed` | Fetch kind 1 text note feed |
106
141
 
107
- ### Trust (11 tools)
142
+ ### Trust (11 tools) — attestations, ring signatures, linkage proofs, spoken verification
108
143
 
109
144
  | Tool | Description |
110
145
  |------|-------------|
@@ -120,7 +155,7 @@ Or with a secret file (recommended):
120
155
  | `trust-spoken-challenge` | Generate spoken verification token |
121
156
  | `trust-spoken-verify` | Verify spoken token response |
122
157
 
123
- ### Relay (5 tools)
158
+ ### Relay (5 tools) — per-identity relay lists, NIP-65 management, direct queries
124
159
 
125
160
  | Tool | Description |
126
161
  |------|-------------|
@@ -130,7 +165,7 @@ Or with a secret file (recommended):
130
165
  | `relay-query` | Query events from relays by kind, author, tags, or time range |
131
166
  | `relay-info` | Fetch NIP-11 relay information document |
132
167
 
133
- ### Zap (7 tools)
168
+ ### Zap (7 tools) — Lightning payments and invoices via Nostr Wallet Connect
134
169
 
135
170
  | Tool | Description |
136
171
  |------|-------------|
@@ -142,14 +177,14 @@ Or with a secret file (recommended):
142
177
  | `zap-receipts` | Parse zap receipts (amount, sender, message) |
143
178
  | `zap-decode` | Decode bolt11 invoice fields |
144
179
 
145
- ### Safety (2 tools)
180
+ ### Safety (2 tools) — duress personas for coercion resistance
146
181
 
147
182
  | Tool | Description |
148
183
  |------|-------------|
149
184
  | `safety-configure` | Configure an alternative identity persona |
150
185
  | `safety-activate` | Switch to alternative identity |
151
186
 
152
- ### Blossom (3 tools)
187
+ ### Blossom (3 tools) — media uploads and management
153
188
 
154
189
  | Tool | Description |
155
190
  |------|-------------|
@@ -157,7 +192,7 @@ Or with a secret file (recommended):
157
192
  | `blossom-list` | List blobs for a pubkey |
158
193
  | `blossom-delete` | Delete a blob by SHA-256 hash |
159
194
 
160
- ### Groups — NIP-29 (4 tools)
195
+ ### Groups — NIP-29 (4 tools) — group chat, metadata, and membership
161
196
 
162
197
  | Tool | Description |
163
198
  |------|-------------|
@@ -166,14 +201,14 @@ Or with a secret file (recommended):
166
201
  | `group-send` | Send message to a group |
167
202
  | `group-members` | List group members |
168
203
 
169
- ### Community NIPs (2 tools)
204
+ ### Community NIPs (2 tools) — publish and read community-proposed NIPs
170
205
 
171
206
  | Tool | Description |
172
207
  |------|-------------|
173
208
  | `nip-publish` | Publish a community NIP (kind 30817) |
174
209
  | `nip-read` | Fetch community NIPs |
175
210
 
176
- ### Utility (18 tools)
211
+ ### Utility (18 tools) — encode, decode, encrypt, verify, filter, fetch, browse NIPs
177
212
 
178
213
  | Tool | Description |
179
214
  |------|-------------|
@@ -196,20 +231,6 @@ Or with a secret file (recommended):
196
231
  | `nip-list` | List all official NIPs |
197
232
  | `nip-show` | Show a specific NIP's content |
198
233
 
199
- ## Identity Switching
200
-
201
- The killer feature. Every tool operates as the "active identity." Switch with a single call:
202
-
203
- ```
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
209
- ```
210
-
211
- Derived identities are cryptographically unlinkable unless you publish a linkage proof.
212
-
213
234
  ## Configuration
214
235
 
215
236
  | Variable | Description |
@@ -0,0 +1,35 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ type ToolHandler = (args: any) => Promise<{
3
+ content: Array<{
4
+ type: 'text';
5
+ text: string;
6
+ }>;
7
+ }>;
8
+ /**
9
+ * Holds non-promoted tool definitions and exposes them via search-actions
10
+ * and execute-action meta-tools. This keeps the primary tool list lean
11
+ * (context-window economics) while still making every action discoverable.
12
+ */
13
+ export declare class ActionCatalog {
14
+ private readonly entries;
15
+ add(name: string, definition: any, handler: ToolHandler): void;
16
+ get size(): number;
17
+ search(intent: string): Array<{
18
+ name: string;
19
+ description: string;
20
+ parameters?: Record<string, string>;
21
+ }>;
22
+ execute(name: string, params: Record<string, unknown>): Promise<{
23
+ content: Array<{
24
+ type: 'text';
25
+ text: string;
26
+ }>;
27
+ }>;
28
+ registerMetaTools(server: McpServer): void;
29
+ }
30
+ /**
31
+ * Create a proxy around McpServer that intercepts registerTool calls.
32
+ * Promoted tools go to the real server; everything else goes to the catalog.
33
+ */
34
+ export declare function createCatalogProxy(server: McpServer, catalog: ActionCatalog, promoted: Set<string>): McpServer;
35
+ export {};
@@ -0,0 +1,148 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Holds non-promoted tool definitions and exposes them via search-actions
4
+ * and execute-action meta-tools. This keeps the primary tool list lean
5
+ * (context-window economics) while still making every action discoverable.
6
+ */
7
+ export class ActionCatalog {
8
+ entries = new Map();
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ add(name, definition, handler) {
11
+ this.entries.set(name, {
12
+ name,
13
+ description: definition.description ?? '',
14
+ inputSchema: definition.inputSchema,
15
+ annotations: definition.annotations,
16
+ handler,
17
+ });
18
+ }
19
+ get size() {
20
+ return this.entries.size;
21
+ }
22
+ search(intent) {
23
+ const tokens = intent.toLowerCase().split(/[\s\-_]+/).filter(t => t.length > 1);
24
+ const scored = [];
25
+ for (const entry of this.entries.values()) {
26
+ const haystack = `${entry.name} ${entry.description}`.toLowerCase();
27
+ let score = 0;
28
+ for (const token of tokens) {
29
+ if (haystack.includes(token))
30
+ score++;
31
+ }
32
+ if (score > 0)
33
+ scored.push({ entry, score });
34
+ }
35
+ scored.sort((a, b) => b.score - a.score);
36
+ return scored.slice(0, 10).map(({ entry }) => ({
37
+ name: entry.name,
38
+ description: entry.description,
39
+ parameters: extractParams(entry.inputSchema),
40
+ }));
41
+ }
42
+ async execute(name, params) {
43
+ const entry = this.entries.get(name);
44
+ if (!entry) {
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: JSON.stringify({
49
+ error: `Unknown action "${name}". Use search-actions to find available actions.`,
50
+ }),
51
+ }],
52
+ };
53
+ }
54
+ if (entry.inputSchema && Object.keys(entry.inputSchema).length > 0) {
55
+ const schema = z.object(entry.inputSchema);
56
+ const result = schema.safeParse(params);
57
+ if (!result.success) {
58
+ return {
59
+ content: [{
60
+ type: 'text',
61
+ text: JSON.stringify({
62
+ error: 'Invalid parameters',
63
+ action: name,
64
+ issues: result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`),
65
+ expected: extractParams(entry.inputSchema),
66
+ }, null, 2),
67
+ }],
68
+ };
69
+ }
70
+ return entry.handler(result.data);
71
+ }
72
+ return entry.handler(params);
73
+ }
74
+ registerMetaTools(server) {
75
+ const catalog = this;
76
+ server.registerTool('search-actions', {
77
+ description: `Search ${catalog.size} additional actions by describing what you want to do. ` +
78
+ 'Returns matching actions with names, descriptions, and parameter schemas. ' +
79
+ 'Use this when the promoted tools (whoami, social-post, social-reply, social-feed, ' +
80
+ 'dm-send, dm-read, zap-send, zap-balance, identity-switch, relay-query) ' +
81
+ 'do not cover what you need.',
82
+ inputSchema: {
83
+ intent: z.string().describe('Describe what you want to do (e.g. "upload a file", "verify an attestation", ' +
84
+ '"encrypt a message", "manage contacts", "ring signature")'),
85
+ },
86
+ annotations: { readOnlyHint: true },
87
+ }, async ({ intent }) => {
88
+ const matches = catalog.search(intent);
89
+ if (matches.length === 0) {
90
+ return {
91
+ content: [{
92
+ type: 'text',
93
+ text: JSON.stringify({
94
+ matches: [],
95
+ hint: 'No matches found. Try different keywords or broader terms.',
96
+ }, null, 2),
97
+ }],
98
+ };
99
+ }
100
+ return {
101
+ content: [{
102
+ type: 'text',
103
+ text: JSON.stringify({ matches }, null, 2),
104
+ }],
105
+ };
106
+ });
107
+ server.registerTool('execute-action', {
108
+ description: 'Execute an action found via search-actions. Pass the exact action name and its parameters.',
109
+ inputSchema: {
110
+ action: z.string().describe('Action name from search-actions results'),
111
+ params: z.record(z.string(), z.unknown()).default({}).describe('Parameters for the action (see search-actions results for expected params)'),
112
+ },
113
+ annotations: { openWorldHint: true },
114
+ }, async ({ action, params }) => {
115
+ return catalog.execute(action, params);
116
+ });
117
+ }
118
+ }
119
+ /**
120
+ * Create a proxy around McpServer that intercepts registerTool calls.
121
+ * Promoted tools go to the real server; everything else goes to the catalog.
122
+ */
123
+ export function createCatalogProxy(server, catalog, promoted) {
124
+ return new Proxy(server, {
125
+ get(target, prop, receiver) {
126
+ if (prop === 'registerTool') {
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ return (name, definition, handler) => {
129
+ if (promoted.has(name)) {
130
+ return target.registerTool(name, definition, handler);
131
+ }
132
+ catalog.add(name, definition, handler);
133
+ };
134
+ }
135
+ return Reflect.get(target, prop, receiver);
136
+ },
137
+ });
138
+ }
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ function extractParams(schema) {
141
+ if (!schema || Object.keys(schema).length === 0)
142
+ return undefined;
143
+ const params = {};
144
+ for (const [key, zodType] of Object.entries(schema)) {
145
+ params[key] = zodType?.description ?? '(no description)';
146
+ }
147
+ return params;
148
+ }
package/dist/cli.js CHANGED
@@ -130,7 +130,8 @@ Social:
130
130
  group-members <group-id> List group members
131
131
 
132
132
  Trust:
133
- attest <type> <identifier> [subject] Create kind 31000 attestation
133
+ attest <event-id> Verify someone's assertion (assertion-first)
134
+ claim <type> Make a direct claim (endorsement, vouch, review)
134
135
  trust-read [--subject X] [--type X] Read attestations
135
136
  trust-verify <event-json> Validate attestation structure
136
137
  trust-revoke <type> <identifier> Revoke an attestation
@@ -473,13 +474,27 @@ async function run(cmdArgs) {
473
474
  out(await handleNotifications(ctx, pool, { limit: parseInt(flag('limit', '50'), 10) }), fmt.formatNotifications);
474
475
  break;
475
476
  // === Trust ===
476
- case 'attest':
477
+ case 'attest': {
478
+ const assertionId = req(1, 'attest <assertion-event-id> [--subject <hex>] [--type <type>] [--summary <text>]');
477
479
  out(await handleTrustAttest(ctx, pool, {
478
- type: req(1, 'attest <type> <identifier> [subject]'),
479
- identifier: cmdArgs[2],
480
- subject: cmdArgs[3],
480
+ assertionId,
481
+ subject: flag('subject'),
482
+ type: flag('type'),
483
+ summary: flag('summary'),
484
+ assertionRelay: flag('relay'),
481
485
  }));
482
486
  break;
487
+ }
488
+ case 'claim': {
489
+ const type = req(1, 'claim <type> [--subject <hex>] [--identifier <string>] [--summary <text>]');
490
+ out(await handleTrustAttest(ctx, pool, {
491
+ type,
492
+ subject: flag('subject'),
493
+ identifier: flag('identifier'),
494
+ summary: flag('summary'),
495
+ }));
496
+ break;
497
+ }
483
498
  case 'trust-read':
484
499
  out(await handleTrustRead(pool, ctx.activeNpub, {
485
500
  subject: flag('subject'),
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { registerRelayTools } from './relay/tools.js';
11
11
  import { registerZapTools } from './zap/tools.js';
12
12
  import { registerSafetyTools } from './safety/tools.js';
13
13
  import { registerUtilTools } from './util/tools.js';
14
+ import { ActionCatalog, createCatalogProxy } from './catalog.js';
14
15
  const config = loadConfig();
15
16
  const pool = new RelayPool({
16
17
  torProxy: config.torProxy,
@@ -36,19 +37,29 @@ config.bunkerUri = undefined;
36
37
  nip65.loadForIdentity(ctx.activeNpub).then(masterRelays => {
37
38
  pool.reconfigure(ctx.activeNpub, masterRelays);
38
39
  }).catch(e => console.error('NIP-65 relay load failed:', e.message));
39
- const server = new McpServer({ name: 'nostr-bray', version: '0.1.0' });
40
- // Phase 2: Identity tools
41
- registerIdentityTools(server, deps);
42
- // Phase 3: Social tools
43
- registerSocialTools(server, deps);
44
- // Phase 4: Trust tools
45
- registerTrustTools(server, deps);
46
- // Phase 5: Relay, Zap & Safety tools
47
- registerRelayTools(server, deps);
48
- registerZapTools(server, deps);
49
- registerSafetyTools(server, deps);
50
- // Utility tools
51
- registerUtilTools(server, deps);
40
+ const server = new McpServer({ name: 'nostr-bray', version: '0.1.0' }, {
41
+ instructions: 'Always check whoami before posting or signing. Use social-feed or social-notifications to get event IDs and author pubkeys before calling social-reply or social-react. DMs default to NIP-17 gift wrap (most private); only use NIP-04 if the recipient requires it. For less common actions (encoding, encryption, ring signatures, blossom, groups, NIPs, key management, safety), use search-actions to discover them, then execute-action to run them.',
42
+ });
43
+ // Promoted tools are registered directly with the server (always visible to Claude).
44
+ // Everything else goes to the catalog, discoverable via search-actions + execute-action.
45
+ const PROMOTED = new Set([
46
+ 'whoami', 'social-post', 'social-reply', 'social-feed',
47
+ 'dm-send', 'dm-read', 'zap-send', 'zap-balance',
48
+ 'identity-switch', 'relay-query',
49
+ ]);
50
+ const catalog = new ActionCatalog();
51
+ const proxy = createCatalogProxy(server, catalog, PROMOTED);
52
+ // Register all tools — the proxy routes promoted to server, rest to catalog
53
+ registerIdentityTools(proxy, deps);
54
+ registerSocialTools(proxy, deps);
55
+ registerTrustTools(proxy, deps);
56
+ registerRelayTools(proxy, deps);
57
+ registerZapTools(proxy, deps);
58
+ registerSafetyTools(proxy, deps);
59
+ registerUtilTools(proxy, deps);
60
+ // Add search-actions and execute-action meta-tools to the real server
61
+ catalog.registerMetaTools(server);
62
+ console.error(`nostr-bray: ${PROMOTED.size} promoted tools + ${catalog.size} cataloged (${PROMOTED.size + catalog.size + 2} total)`);
52
63
  if (config.transport === 'stdio') {
53
64
  const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
54
65
  await server.connect(new StdioServerTransport());
@@ -9,9 +9,11 @@ export interface AttestResult {
9
9
  }
10
10
  /** Create and publish a kind 31000 attestation */
11
11
  export declare function handleTrustAttest(ctx: IdentityContext, pool: RelayPool, args: {
12
- type: string;
12
+ type?: string;
13
13
  identifier?: string;
14
14
  subject?: string;
15
+ assertionId?: string;
16
+ assertionRelay?: string;
15
17
  summary?: string;
16
18
  content?: string;
17
19
  expiration?: number;
@@ -6,10 +6,17 @@ import { toUnsignedEvent } from 'nsec-tree/event';
6
6
  import { decode } from 'nostr-tools/nip19';
7
7
  /** Create and publish a kind 31000 attestation */
8
8
  export async function handleTrustAttest(ctx, pool, args) {
9
+ if (!args.type && !args.assertionId) {
10
+ throw new Error('at least one of type or assertionId must be provided');
11
+ }
9
12
  const template = createAttestation({
10
13
  type: args.type,
11
14
  identifier: args.identifier,
12
15
  subject: args.subject,
16
+ assertion: args.assertionId ? {
17
+ id: args.assertionId,
18
+ relay: args.assertionRelay,
19
+ } : undefined,
13
20
  summary: args.summary,
14
21
  content: args.content,
15
22
  expiration: args.expiration,
@@ -5,19 +5,45 @@ import { handleTrustRingProve, handleTrustRingVerify } from './ring.js';
5
5
  import { handleTrustSpokenChallenge, handleTrustSpokenVerify } from './spoken.js';
6
6
  export function registerTrustTools(server, deps) {
7
7
  server.registerTool('trust-attest', {
8
- description: 'Create and publish a kind 31000 verifiable attestation (NIP-VA) as the active identity. An attestation is a signed statement about a subject (e.g. "I verify this pubkey belongs to Alice"). Warns if attesting from a derived persona instead of master. Use trust_read to find existing attestations, trust_revoke to revoke them.',
8
+ description: 'Verify someone else\'s claim by attesting to their assertion event. The subject publishes their claim as a Nostr event; you reference it and say "I verify this." The type is inherited from the referenced event. This is the recommended pattern — it puts the individual at the centre.',
9
9
  inputSchema: {
10
- type: z.string().describe('Attestation type (e.g. "identity-verification", "endorsement")'),
11
- identifier: z.string().optional().describe('D-tag identifier (hex pubkey or context string)'),
12
- subject: z.string().optional().describe('Subject hex pubkey (for third-party attestations)'),
10
+ assertionId: hexId.describe('Event ID of the subject\'s assertion to verify'),
11
+ subject: hexId.optional().describe('Subject hex pubkey (auto-detected from assertion if omitted)'),
12
+ type: z.string().optional().describe('Explicit type override (usually inherited from the assertion)'),
13
+ summary: z.string().optional().describe('Human-readable summary of what was verified'),
14
+ content: z.string().optional().describe('Evidence payload (text or JSON)'),
15
+ expiration: z.number().optional().describe('Unix timestamp for attestation expiry'),
16
+ assertionRelay: z.string().optional().describe('Relay hint for the assertion event'),
17
+ },
18
+ annotations: { readOnlyHint: false },
19
+ }, async ({ assertionId, subject, type, summary, content, expiration, assertionRelay }) => {
20
+ const result = await handleTrustAttest(deps.ctx, deps.pool, {
21
+ assertionId, subject, type, summary, content, expiration, assertionRelay,
22
+ });
23
+ const response = {
24
+ id: result.event.id,
25
+ publish: result.publish,
26
+ };
27
+ if (result.warning)
28
+ response.warning = result.warning;
29
+ return {
30
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
31
+ };
32
+ });
33
+ server.registerTool('trust-claim', {
34
+ description: 'Make a direct statement about another identity — an endorsement, review, vouch, or any attestor-originated claim. You define the type and subject. Use trust-attest instead if the subject has published their own assertion event.',
35
+ inputSchema: {
36
+ type: z.string().describe('Claim type (e.g. "endorsement", "vouch", "review")'),
37
+ subject: hexId.optional().describe('Subject hex pubkey (omit for self-declarations)'),
38
+ identifier: z.string().optional().describe('D-tag identifier (defaults to subject pubkey)'),
13
39
  summary: z.string().optional().describe('Human-readable summary'),
14
40
  content: z.string().optional().describe('Event content (text or JSON)'),
15
41
  expiration: z.number().optional().describe('Unix timestamp for attestation expiry'),
16
42
  },
17
- annotations: { readOnlyHint: false, destructiveHint: true },
18
- }, async ({ type, identifier, subject, summary, content, expiration }) => {
43
+ annotations: { readOnlyHint: false },
44
+ }, async ({ type, subject, identifier, summary, content, expiration }) => {
19
45
  const result = await handleTrustAttest(deps.ctx, deps.pool, {
20
- type, identifier, subject, summary, content, expiration,
46
+ type, subject, identifier, summary, content, expiration,
21
47
  });
22
48
  const response = {
23
49
  id: result.event.id,
@@ -30,7 +56,7 @@ export function registerTrustTools(server, deps) {
30
56
  };
31
57
  });
32
58
  server.registerTool('trust-read', {
33
- description: 'Read kind 31000 attestations from relays. Filter by subject, type, or attestor.',
59
+ description: 'Read kind 31000 attestations from relays. Filter by subject, type, or attestor. Works with both assertion-first (trust-attest) and direct claims (trust-claim).',
34
60
  inputSchema: {
35
61
  subject: hexId.optional().describe('Subject hex pubkey to filter by'),
36
62
  type: z.string().optional().describe('Attestation type to filter by'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "MCP server giving AI agents sovereign Nostr identities — key derivation, personas, attestations, duress detection, zaps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -77,7 +77,7 @@
77
77
  "@forgesworn/shamir-words": "^1.0.4",
78
78
  "@modelcontextprotocol/sdk": "^1.27.1",
79
79
  "canary-kit": "^2.6.2",
80
- "nostr-attestations": "^2.1.0",
80
+ "nostr-attestations": "^2.2.0",
81
81
  "nostr-tools": "^2.23.3",
82
82
  "nsec-tree": "^1.4.1",
83
83
  "socks-proxy-agent": "^9.0.0",