@reclaimprotocol/agent 0.1.14 → 0.1.17

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.
Files changed (53) hide show
  1. package/lib/authoring/index.d.ts +1 -1
  2. package/lib/authoring/index.js +1 -1
  3. package/lib/authoring/util.js +3 -0
  4. package/lib/config/store.d.ts +7 -0
  5. package/lib/consts.d.ts +8 -0
  6. package/lib/consts.js +20 -0
  7. package/lib/index.js +1 -1
  8. package/lib/mcp/api/operations.gen.d.ts +8 -16
  9. package/lib/mcp/api/operations.gen.js +428 -479
  10. package/lib/mcp/remap_errors.js +7 -14
  11. package/lib/mcp/start.js +10 -3
  12. package/lib/mcp/tools/agent/proof.js +3 -3
  13. package/lib/mcp/tools/authenticate/authenticate.js +178 -93
  14. package/lib/mcp/tools/authenticate/eth-key.js +2 -3
  15. package/lib/mcp/tools/index.d.ts +1 -0
  16. package/lib/mcp/tools/index.js +1 -0
  17. package/lib/mcp/tools/mode/index.d.ts +1 -0
  18. package/lib/mcp/tools/mode/index.js +3 -0
  19. package/lib/mcp/tools/mode/mode.d.ts +16 -0
  20. package/lib/mcp/tools/mode/mode.js +49 -0
  21. package/lib/old/tools/publish.js +75 -4
  22. package/lib/provider/to-version.js +4 -0
  23. package/lib/tools/registry.d.ts +6 -13
  24. package/lib/tools/registry.js +29 -0
  25. package/package.json +5 -8
  26. package/scripts/prepend-shebang.js +1 -1
  27. package/skill/injection-api.md +98 -7
  28. package/src/authoring/index.ts +1 -0
  29. package/src/authoring/util.ts +3 -0
  30. package/src/cdp/launcher.ts +1 -1
  31. package/src/config/store.ts +8 -0
  32. package/src/consts.ts +24 -0
  33. package/src/index.ts +1 -1
  34. package/src/mcp/api/operations.gen.ts +430 -487
  35. package/src/mcp/remap_errors.ts +8 -15
  36. package/src/mcp/start.ts +11 -2
  37. package/src/mcp/tools/agent/proof.ts +3 -3
  38. package/src/mcp/tools/authenticate/authenticate.ts +238 -108
  39. package/src/mcp/tools/authenticate/eth-key.ts +7 -5
  40. package/src/mcp/tools/index.ts +1 -0
  41. package/src/mcp/tools/mode/index.ts +3 -0
  42. package/src/mcp/tools/mode/mode.ts +58 -0
  43. package/src/old/tools/publish.ts +78 -4
  44. package/src/proof/debug-response-dump.ts +1 -1
  45. package/src/proof/proof-core.ts +1 -1
  46. package/src/proof/zk-files.ts +2 -2
  47. package/src/provider/dom-xpath.ts +1 -1
  48. package/src/provider/draft.ts +3 -3
  49. package/src/provider/to-version.ts +4 -0
  50. package/src/tools/registry.ts +19 -0
  51. package/src/cli/args.ts +0 -20
  52. package/src/cli/commands.ts +0 -27
  53. package/src/cli.ts +0 -7
@@ -240,15 +240,89 @@ export function createVersionFromCaptureTool(
240
240
  'Extra requests the customInjection script is allowed to ' +
241
241
  'fire (e.g. pagination) — add-version ONLY, the register ' +
242
242
  'endpoint ignores this. Each item has the same shape as ' +
243
- '`provider`, plus `multiple`/`required` (default true).',
243
+ '`provider`, plus `multiple`/`required` (default true). ' +
244
+ 'IMPORTANT whenever a claim\'s request HAS a body: set `body` ' +
245
+ 'here (see its own description below) — if only a small part ' +
246
+ 'of the real body varies per user/run (e.g. one id field, not ' +
247
+ 'the whole payload), template just that part with ' +
248
+ '`{{paramName}}` and keep the rest as fixed literal text; see ' +
249
+ 'the "bodySniff" section of the customInjection field\'s guide ' +
250
+ 'for the exact mechanics and a worked example.',
244
251
  items: {
245
252
  type: 'object',
246
253
  properties: {
247
254
  url: { type: 'string' },
248
255
  method: { type: 'string' },
249
- body: { type: 'string' },
250
- responseMatches: { type: 'array' },
251
- responseRedactions: { type: 'array' },
256
+ body: {
257
+ type: 'string',
258
+ description:
259
+ 'Becomes `bodySniff: { enabled: true, template: <this ' +
260
+ 'string> }` — set this whenever the request has a ' +
261
+ 'body at all (skip only for a genuinely bodyless ' +
262
+ 'request, typically GET). If the real body is FIXED ' +
263
+ '(identical for every user/run), put that literal body ' +
264
+ 'text here verbatim. If only PART of it varies (e.g. ' +
265
+ '`{"request":1,"type":"user_info","id":"12345"}` where ' +
266
+ 'only `id` differs per user), template ONLY that part: ' +
267
+ '`{"request":1,"type":"user_info",' +
268
+ '"id":"{{REQ_BODY_USER_ID}}"}` — keep everything else ' +
269
+ 'as fixed literal text rather than templating the whole ' +
270
+ 'body. Confirmed directly against attestor-core\'s ' +
271
+ '`hashProviderParams`: the hash embeds this string ' +
272
+ 'LITERALLY, unresolved braces and all, never the real ' +
273
+ 'substituted body, so a claim built with the SAME ' +
274
+ 'literal template (plus a witness/extracted param per ' +
275
+ 'placeholder, supplying the real value for ' +
276
+ 'verification) hashes identically regardless of what ' +
277
+ 'those real values are. This is the OPPOSITE of ' +
278
+ '`templateParams`\' `${var}` syntax below, which ' +
279
+ 'genuinely IS substituted before hashing — do not ' +
280
+ 'confuse `{{param}}` (body, never resolved) with ' +
281
+ '`${var}` (responseMatches/responseRedactions, resolved ' +
282
+ 'at verify time). Before publishing, hand-verify that ' +
283
+ 'substituting real sample values into this template ' +
284
+ 'reproduces the real captured request body ' +
285
+ 'byte-for-byte. Fallback if per-field templating keeps ' +
286
+ 'mismatching (more fields vary than expected, unstable ' +
287
+ 'key order, etc.) and you can\'t pin down every ' +
288
+ 'variable field: template the WHOLE body as one ' +
289
+ 'placeholder instead, `{{REQ_BODY_GRD}}` — the `GRD` ' +
290
+ 'suffix makes it a greedy match (captures everything) ' +
291
+ 'rather than the default non-greedy match, which is ' +
292
+ 'needed when nothing fixed follows the placeholder. ' +
293
+ 'This pins down nothing about body structure, so prefer ' +
294
+ 'minimal per-field placeholders whenever you can ' +
295
+ 'actually identify which fields vary.',
296
+ },
297
+ responseMatches: {
298
+ type: 'array',
299
+ description:
300
+ 'Each item: `{ value: string, type?: "contains" | ' +
301
+ '"regex", invert?: boolean, isOptional?: boolean }`. ' +
302
+ '`value` must literally appear (or match, if `regex`) ' +
303
+ 'somewhere in the response body for the claim to be ' +
304
+ 'accepted; it commonly embeds one or more ' +
305
+ '`{{paramName}}` extraction placeholders (e.g. ' +
306
+ '`"name":"{{name_0}}"`) whose real value is redacted ' +
307
+ 'out and supplied separately as a witness/extracted ' +
308
+ 'param. Use `${var}` (not `{{var}}`) inside this string ' +
309
+ 'only if you also declare that name in this entry\'s ' +
310
+ 'own `templateParams` below — that\'s a DIFFERENT, ' +
311
+ 'verify-time-substituted placeholder, resolved from the ' +
312
+ 'submitted proof before hashing (unlike a body ' +
313
+ '`{{param}}`, which never resolves — see `body` above).',
314
+ },
315
+ responseRedactions: {
316
+ type: 'array',
317
+ description:
318
+ 'Each item: `{ jsonPath?: string, xPath?: string, ' +
319
+ 'regex?: string, hash?: string }`. Identifies WHICH ' +
320
+ 'part of the response the matching `{{paramName}}` in ' +
321
+ '`responseMatches` actually redacts/extracts — e.g. ' +
322
+ '`jsonPath: "$.data.items[0].name"` for a JSON response. ' +
323
+ 'Order/count should line up with `responseMatches`\' ' +
324
+ 'placeholders one-to-one for a typical claim.',
325
+ },
252
326
  multiple: {
253
327
  type: 'boolean',
254
328
  description: 'Allow matching many requests. Default true.',
@@ -26,7 +26,7 @@ let installed = false
26
26
  /** Wrap attestor-core's http `getResponseRedactions` to dump the response it
27
27
  * runs against. Idempotent; no-op unless RECLAIM_AGENT_DUMP_RESPONSE is
28
28
  * set. */
29
- export function installResponseDump(): void {
29
+ export function installResponseDump() {
30
30
  if(installed || !process.env['RECLAIM_AGENT_DUMP_RESPONSE']) {
31
31
  return
32
32
  }
@@ -20,7 +20,7 @@ let cryptoInitialized = false
20
20
  * caller; without this the first proof dies inside the TLS client with
21
21
  * `crypto.randomBytes is not a function`. Shared by every attestor caller —
22
22
  * the agent MCP `run_proof` tool and the app's verification runner. */
23
- export function ensureAttestorCrypto(): void {
23
+ export function ensureAttestorCrypto() {
24
24
  if(cryptoInitialized) {
25
25
  return
26
26
  }
@@ -27,7 +27,7 @@ let inFlight: Promise<void> | undefined
27
27
  * All output goes to stderr (fd 2): the MCP server speaks JSON-RPC over stdout,
28
28
  * so a stray write to fd 1 mid-session would corrupt the protocol.
29
29
  */
30
- export function ensureZkFiles(): Promise<void> {
30
+ export function ensureZkFiles() {
31
31
  inFlight ??= download().catch((err) => {
32
32
  inFlight = undefined
33
33
  throw err
@@ -35,7 +35,7 @@ export function ensureZkFiles(): Promise<void> {
35
35
  return inFlight
36
36
  }
37
37
 
38
- async function download(): Promise<void> {
38
+ async function download() {
39
39
  let mainPath: string
40
40
  try {
41
41
  mainPath = require.resolve('@reclaimprotocol/zk-symmetric-crypto')
@@ -190,7 +190,7 @@ function findElement(root: Node, targets: string[]): Element | undefined {
190
190
  const complete: Element[] = []
191
191
  const embedded: Element[] = []
192
192
 
193
- const walk = (node: Node): void => {
193
+ const walk = (node: Node) => {
194
194
  if(isTag(node)) {
195
195
  const el = node
196
196
  const texts = el.children.filter(isText).map((c) => c.data)
@@ -160,7 +160,7 @@ export function assertPortableMatches(
160
160
  matches: ResponseMatch[],
161
161
  redactions: ResponseRedaction[],
162
162
  targets: DraftTarget[],
163
- ): void {
163
+ ) {
164
164
  // A baked email LOCAL-PART ("jane.doe@…") pins the provider to one user
165
165
  // even when only the domain is captured as a param — the classic result of
166
166
  // proving an email's domain substring. Reject it regardless of the declared
@@ -172,7 +172,7 @@ export function assertPortableMatches(
172
172
  .replace(/\{\{[^}]*\}\}/g, '')
173
173
  .replace(/\(\?<[^>]*>[^)]*\)/g, '')
174
174
  .replace(/\\(.)/g, '$1')
175
- const scanEmail = (text: string, where: string): void => {
175
+ const scanEmail = (text: string, where: string) => {
176
176
  if(EMAIL_LOCAL_BAKED.test(deregex(text))) {
177
177
  throw new Error(
178
178
  `drafted ${where} bakes a per-user email local-part as a literal — `
@@ -203,7 +203,7 @@ export function assertPortableMatches(
203
203
  }
204
204
 
205
205
  const stripParams = (s: string): string => s.replace(/\{\{[^}]*\}\}/g, '')
206
- const scan = (text: string, where: string, own: string | null): void => {
206
+ const scan = (text: string, where: string, own: string | null) => {
207
207
  const bare = stripParams(text)
208
208
  for(const v of guarded) {
209
209
  if(bare.includes(v)) {
@@ -189,6 +189,10 @@ export function providerToCreateVersionRequest(
189
189
  ...providerToRequestSelection(req),
190
190
  multiple: req.multiple ?? true,
191
191
  required: req.required ?? true,
192
+ templateParamsMode: req.templateParamsMode ?? 'separate',
193
+ ...(req.templateParams?.length
194
+ ? { templateParams: req.templateParams }
195
+ : {}),
192
196
  }))
193
197
  const script = jsUserScripts?.trim()
194
198
 
@@ -11,6 +11,8 @@
11
11
  * glue (`registerToolList` onto an `McpServer`) stays in `mcp/server.ts`, which
12
12
  * re-exports this primitive for the MCP-side tool files.
13
13
  */
14
+ import { type Boom, isBoom } from '@hapi/boom'
15
+ import type { ProblemDetails } from '@reclaimprotocol/client'
14
16
  import type { JSONSchema } from 'zod/v4/core'
15
17
  import { toZod } from '../mcp/api/to-zod.ts'
16
18
 
@@ -51,6 +53,20 @@ export function defineTool<TArgs>(
51
53
  }
52
54
  }
53
55
 
56
+ /**
57
+ * Render a tool-handler error as the text every host shows the LLM.
58
+ * Boom errors carrying an RFC 9457 problem include its `detail`; anything
59
+ * else falls back to the plain message.
60
+ */
61
+ export function errorAsToolText(err: unknown): string {
62
+ if(isBoom(err) && err.output?.statusCode) {
63
+ const detail = (err as Boom<ProblemDetails>).data?.detail
64
+ return detail ? `${err.message}: ${detail}` : err.message
65
+ }
66
+
67
+ return err instanceof Error ? err.message : String(err)
68
+ }
69
+
54
70
  /**
55
71
  * Adapt a tool to a host by WIDENING its input schema — add properties, mark
56
72
  * some required, optionally override the description — without touching the
@@ -73,6 +89,9 @@ export function extendTool(
73
89
  ...(base.inputSchema.required ?? []),
74
90
  ...(extra.required ?? []),
75
91
  ]
92
+ // The merged `properties` spread carries the base's untyped property values,
93
+ // which don't structurally match ObjectSchema's strict per-property schema
94
+ // type — assert the merged shape.
76
95
  const inputSchema = {
77
96
  ...base.inputSchema,
78
97
  properties: {
package/src/cli/args.ts DELETED
@@ -1,20 +0,0 @@
1
- import { parseArgs as nodeParseArgs } from 'node:util'
2
-
3
- // Placeholder. Final shape decided once the backend openapi stabilises and
4
- // the CLI surface is fleshed out (see AGENTS.md → "CLI").
5
- export function parseCliArgs(argv: string[]) {
6
- return nodeParseArgs({
7
- args: argv,
8
- options: {
9
- 'base-url': { type: 'string' },
10
- token: { type: 'string' },
11
- param: { type: 'string', multiple: true },
12
- query: { type: 'string', multiple: true },
13
- body: { type: 'string' },
14
- help: { type: 'boolean', short: 'h' },
15
- version: { type: 'boolean', short: 'v' },
16
- },
17
- allowPositionals: true,
18
- strict: false,
19
- })
20
- }
@@ -1,27 +0,0 @@
1
- import { notImplemented } from '@hapi/boom'
2
- import { DESCRIPTION, NAME, VERSION } from '../pkg.ts'
3
- import { parseCliArgs } from './args.ts'
4
-
5
- // Entry point for the `reclaim` bin. Placeholder until backend openapi
6
- // finalises — `--version` and `--help` work; everything else throws.
7
- export async function run(argv: string[]) {
8
- const parsed = parseCliArgs(argv)
9
-
10
- if(parsed.values['version']) {
11
- process.stdout.write(`${NAME} ${VERSION}\n`)
12
- return
13
- }
14
-
15
- if(parsed.values['help']) {
16
- process.stdout.write(
17
- `${NAME} ${VERSION}\n${DESCRIPTION}\n\n` +
18
- 'Usage: reclaim call <OperationId> ' +
19
- '[--param k=v] [--query k=v] [--body <json>]\n',
20
- )
21
- return
22
- }
23
-
24
- throw notImplemented(
25
- 'reclaim CLI: pending @reclaimprotocol/app spec finalisation',
26
- )
27
- }
package/src/cli.ts DELETED
@@ -1,7 +0,0 @@
1
- import { run } from './cli/commands.ts'
2
- import { LOGGER } from './logger.ts'
3
-
4
- run(process.argv.slice(2)).catch((err) => {
5
- LOGGER.error({ err }, 'cli failed')
6
- process.exitCode = 1
7
- })