nostr-bray 1.27.2 → 1.27.3

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/dist/catalog.d.ts CHANGED
@@ -32,4 +32,25 @@ export declare class ActionCatalog {
32
32
  * Promoted tools go to the real server; everything else goes to the catalog.
33
33
  */
34
34
  export declare function createCatalogProxy(server: McpServer, catalog: ActionCatalog, promoted: Set<string>): McpServer;
35
+ /**
36
+ * Coerce execute-action's `params`/`parameters` argument into a plain object.
37
+ *
38
+ * Accepts either:
39
+ * - an object (the canonical shape), passed through unchanged
40
+ * - a JSON string literal that parses to an object (some MCP clients serialise
41
+ * record-typed fields this way because the compiled JSON Schema has no
42
+ * fixed properties list)
43
+ * - undefined (returns an empty object)
44
+ *
45
+ * Rejects malformed JSON strings and JSON that does not parse to a plain object
46
+ * (e.g. arrays, numbers, null), returning a structured error the caller can
47
+ * surface to the client without crashing.
48
+ */
49
+ export declare function coerceActionParams(raw: unknown): {
50
+ ok: true;
51
+ value: Record<string, unknown>;
52
+ } | {
53
+ ok: false;
54
+ error: string;
55
+ };
35
56
  export {};
package/dist/catalog.js CHANGED
@@ -104,21 +104,48 @@ export class ActionCatalog {
104
104
  }],
105
105
  };
106
106
  });
107
+ // Accept either an object (the natural shape) or a JSON string literal.
108
+ // Some MCP clients (notably the Claude Code harness as of April 2026)
109
+ // serialise schema fields typed as z.record(z.unknown()) as a JSON string
110
+ // rather than a nested object, because the compiled JSON Schema has no
111
+ // fixed `properties` list. The handler below coerces strings by parsing
112
+ // them, returning a clean error on malformed JSON.
113
+ const paramsSchema = z.union([
114
+ z.record(z.string(), z.unknown()),
115
+ z.string(),
116
+ ]);
107
117
  server.registerTool('execute-action', {
108
118
  description: 'Execute an action found via search-actions. Pass the exact action name and its parameters.',
109
119
  inputSchema: {
110
120
  action: z.string().describe('Action name from search-actions results'),
111
- params: z.record(z.string(), z.unknown()).optional().describe('Parameters for the action (see search-actions results for expected params). Alias: parameters.'),
121
+ params: paramsSchema.optional().describe('Parameters for the action (see search-actions results for expected params). ' +
122
+ 'Alias: parameters. Accepts a JSON object or a JSON string literal.'),
112
123
  // Alias for clients that naturally reach for "parameters" rather than "params".
113
124
  // Without this, MCP SDK strips unknown keys from the validated args and the
114
125
  // inner action handler receives an empty object, causing spurious
115
126
  // "expected string, received undefined" validation failures.
116
- parameters: z.record(z.string(), z.unknown()).optional().describe('Alias for params. Either field is accepted.'),
127
+ parameters: paramsSchema.optional().describe('Alias for params. Either field is accepted. ' +
128
+ 'Accepts a JSON object or a JSON string literal.'),
117
129
  },
118
130
  annotations: { openWorldHint: true },
119
131
  }, async ({ action, params, parameters }) => {
120
- const resolved = (params ?? parameters ?? {});
121
- return catalog.execute(action, resolved);
132
+ const raw = params ?? parameters;
133
+ const coerced = coerceActionParams(raw);
134
+ if (!coerced.ok) {
135
+ return {
136
+ content: [{
137
+ type: 'text',
138
+ text: JSON.stringify({
139
+ error: coerced.error,
140
+ action,
141
+ hint: 'Pass params (or parameters) as a JSON object like ' +
142
+ '{"pubkeyHex":"..."}, or as a JSON string literal of the ' +
143
+ 'same shape. Malformed JSON strings are rejected.',
144
+ }, null, 2),
145
+ }],
146
+ };
147
+ }
148
+ return catalog.execute(action, coerced.value);
122
149
  });
123
150
  }
124
151
  }
@@ -152,3 +179,53 @@ function extractParams(schema) {
152
179
  }
153
180
  return params;
154
181
  }
182
+ /**
183
+ * Coerce execute-action's `params`/`parameters` argument into a plain object.
184
+ *
185
+ * Accepts either:
186
+ * - an object (the canonical shape), passed through unchanged
187
+ * - a JSON string literal that parses to an object (some MCP clients serialise
188
+ * record-typed fields this way because the compiled JSON Schema has no
189
+ * fixed properties list)
190
+ * - undefined (returns an empty object)
191
+ *
192
+ * Rejects malformed JSON strings and JSON that does not parse to a plain object
193
+ * (e.g. arrays, numbers, null), returning a structured error the caller can
194
+ * surface to the client without crashing.
195
+ */
196
+ export function coerceActionParams(raw) {
197
+ if (raw === undefined || raw === null) {
198
+ return { ok: true, value: {} };
199
+ }
200
+ if (typeof raw === 'string') {
201
+ const trimmed = raw.trim();
202
+ if (trimmed === '') {
203
+ return { ok: true, value: {} };
204
+ }
205
+ let parsed;
206
+ try {
207
+ parsed = JSON.parse(trimmed);
208
+ }
209
+ catch (err) {
210
+ const message = err instanceof Error ? err.message : String(err);
211
+ return {
212
+ ok: false,
213
+ error: `Invalid parameters: could not parse JSON string (${message})`,
214
+ };
215
+ }
216
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
217
+ return {
218
+ ok: false,
219
+ error: 'Invalid parameters: JSON string must decode to an object, not an array or primitive',
220
+ };
221
+ }
222
+ return { ok: true, value: parsed };
223
+ }
224
+ if (typeof raw === 'object' && !Array.isArray(raw)) {
225
+ return { ok: true, value: raw };
226
+ }
227
+ return {
228
+ ok: false,
229
+ error: `Invalid parameters: expected an object or JSON string, received ${typeof raw}`,
230
+ };
231
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.27.2",
3
+ "version": "1.27.3",
4
4
  "description": "Trust-aware Nostr for AI and humans. Three dimensions of trust -- verification, proximity, and access -- woven into every interaction.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",