@reclaimprotocol/agent 0.1.15 → 0.1.18

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 (64) 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 +15 -0
  6. package/lib/consts.js +35 -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/agent/publish.js +33 -0
  14. package/lib/mcp/tools/authenticate/authenticate.js +178 -93
  15. package/lib/mcp/tools/authenticate/eth-key.js +2 -3
  16. package/lib/mcp/tools/index.d.ts +1 -0
  17. package/lib/mcp/tools/index.js +1 -0
  18. package/lib/mcp/tools/mode/index.d.ts +1 -0
  19. package/lib/mcp/tools/mode/index.js +3 -0
  20. package/lib/mcp/tools/mode/mode.d.ts +17 -0
  21. package/lib/mcp/tools/mode/mode.js +69 -0
  22. package/lib/old/to-register.d.ts +16 -8
  23. package/lib/old/to-register.js +29 -21
  24. package/lib/old/tools/publish.js +86 -8
  25. package/lib/provider/injection-doc.js +4 -3
  26. package/lib/provider/schema.d.ts +26 -1
  27. package/lib/provider/to-version.d.ts +19 -2
  28. package/lib/provider/to-version.js +22 -4
  29. package/lib/tools/registry.d.ts +6 -13
  30. package/lib/tools/registry.js +29 -0
  31. package/package.json +5 -8
  32. package/scripts/prepend-shebang.js +1 -1
  33. package/skill/builder-mode.md +2 -0
  34. package/skill/old-mode.md +3 -1
  35. package/src/authoring/index.ts +1 -0
  36. package/src/authoring/util.ts +3 -0
  37. package/src/cdp/launcher.ts +1 -1
  38. package/src/config/store.ts +8 -0
  39. package/src/consts.ts +44 -0
  40. package/src/index.ts +1 -1
  41. package/src/mcp/api/operations.gen.ts +430 -487
  42. package/src/mcp/remap_errors.ts +8 -15
  43. package/src/mcp/start.ts +11 -2
  44. package/src/mcp/tools/agent/proof.ts +3 -3
  45. package/src/mcp/tools/agent/publish.ts +42 -0
  46. package/src/mcp/tools/authenticate/authenticate.ts +238 -108
  47. package/src/mcp/tools/authenticate/eth-key.ts +7 -5
  48. package/src/mcp/tools/index.ts +1 -0
  49. package/src/mcp/tools/mode/index.ts +3 -0
  50. package/src/mcp/tools/mode/mode.ts +82 -0
  51. package/src/old/to-register.ts +50 -27
  52. package/src/old/tools/publish.ts +108 -8
  53. package/src/proof/debug-response-dump.ts +1 -1
  54. package/src/proof/proof-core.ts +1 -1
  55. package/src/proof/zk-files.ts +2 -2
  56. package/src/provider/dom-xpath.ts +1 -1
  57. package/src/provider/draft.ts +3 -3
  58. package/src/provider/injection-doc.ts +4 -3
  59. package/src/provider/schema.ts +27 -1
  60. package/src/provider/to-version.ts +45 -6
  61. package/src/tools/registry.ts +19 -0
  62. package/src/cli/args.ts +0 -20
  63. package/src/cli/commands.ts +0 -27
  64. package/src/cli.ts +0 -7
@@ -0,0 +1,82 @@
1
+ import type { ConfigStore, DevtoolsMode } from '../../../config/store.ts'
2
+ import { defaultConfigPath } from '../../../config/store.ts'
3
+ import { oldModeSource } from '../../../consts.ts'
4
+ import { defineTool, type RegisteredTool } from '../../server.ts'
5
+
6
+ /**
7
+ * The `set_devtools_mode` / `get_devtools_mode` tools: persist which backend
8
+ * the MCP server targets (`old` devtools vs the `builder`), and
9
+ * report the mode currently in effect. The choice is written to
10
+ * `~/.reclaim/config.json` and applied the NEXT time the server starts —
11
+ * the registered tool set, server instructions, and backend client are all
12
+ * fixed at connect time, so the switch requires a restart/reconnect. Follows
13
+ * the `attach_chrome` persist-through-config pattern.
14
+ *
15
+ * Registered in BOTH modes (so the user can switch either direction) and
16
+ * outside `buildAgentTools` (so it survives `RECLAIM_AGENT_DISABLED=1`). An
17
+ * explicit `USE_OLD_DEVTOOLS` env var still overrides the persisted value —
18
+ * see `resolveOldMode` in consts.ts.
19
+ */
20
+ export function buildModeTools(
21
+ config: ConfigStore,
22
+ currentOldMode: boolean,
23
+ ): RegisteredTool[] {
24
+ const current: DevtoolsMode = currentOldMode ? 'old' : 'builder'
25
+ return [
26
+ defineTool<{ mode: DevtoolsMode }>(
27
+ {
28
+ name: 'set_devtools_mode',
29
+ description:
30
+ 'Choose which backend the MCP server targets: "old" (devtools) '
31
+ + 'or "builder" (the builder backend). '
32
+ + 'Persists the choice to ~/.reclaim/config.json; it takes effect '
33
+ + 'the next time the MCP server starts, so restart/reconnect after '
34
+ + 'switching. An explicit USE_OLD_DEVTOOLS env var overrides this.',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ mode: { type: 'string', enum: ['old', 'builder'] },
39
+ },
40
+ required: ['mode'],
41
+ },
42
+ },
43
+ async(parsed) => {
44
+ config.write({ mode: parsed.mode })
45
+ const restartRequired = parsed.mode !== current
46
+ return {
47
+ saved: true,
48
+ mode: parsed.mode,
49
+ effectiveNow: current,
50
+ restartRequired,
51
+ configPath: defaultConfigPath(),
52
+ message: restartRequired
53
+ ? 'Saved. Restart / reconnect the MCP server for '
54
+ + `"${parsed.mode}" mode to take effect.`
55
+ : `Already running in "${parsed.mode}" mode; nothing to restart.`,
56
+ }
57
+ },
58
+ ),
59
+ defineTool<Record<string, never>>(
60
+ {
61
+ name: 'get_devtools_mode',
62
+ description:
63
+ 'Report which backend this MCP server is currently targeting: '
64
+ + '"old" (devtools) or "builder" (the builder '
65
+ + 'backend). Also reports WHY that mode is in effect (source: '
66
+ + 'a USE_OLD_DEVTOOLS env override, the persisted config, or '
67
+ + 'the default) and which mode is persisted for the next '
68
+ + 'start. Use set_devtools_mode to change it.',
69
+ inputSchema: { type: 'object', properties: {} },
70
+ },
71
+ async() => {
72
+ const persisted = config.read()?.mode
73
+ return {
74
+ mode: current,
75
+ source: oldModeSource(persisted),
76
+ persistedMode: persisted ?? null,
77
+ configPath: defaultConfigPath(),
78
+ }
79
+ },
80
+ ),
81
+ ]
82
+ }
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert'
1
2
  import type { ReclaimProvider } from '../provider/schema.ts'
2
3
  import type { AllowedJsRequest, DraftRequest } from '../provider/to-version.ts'
3
4
  import type {
@@ -6,7 +7,7 @@ import type {
6
7
  } from './client.ts'
7
8
 
8
9
  /** Default web settings, mirroring the builder openapi defaults
9
- * (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The old
10
+ * (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The
10
11
  * backend accepts both: `geoLocation` lives on the providerConfig, `useProxy`
11
12
  * is a Provider-level flag (register only). */
12
13
  const DEFAULT_GEO_LOCATION = '{{DYNAMIC_GEO}}'
@@ -14,15 +15,15 @@ const DEFAULT_USE_PROXY = true
14
15
 
15
16
  /**
16
17
  * Translate a drafted `ReclaimProvider` (from `propose_provider`) into the
17
- * OLD devtools backend shapes. Deliberately a SEPARATE module from
18
+ * devtools backend shapes. Deliberately a SEPARATE module from
18
19
  * `provider/to-version.ts` (the builder translator) — the two backends have
19
20
  * structurally different bodies:
20
21
  *
21
22
  * - builder NESTS each redaction into its match's `extract`;
22
- * - old devtools keeps `responseMatches` and `responseRedactions` as
23
+ * - devtools keeps `responseMatches` and `responseRedactions` as
23
24
  * PARALLEL arrays on each `requestData` entry.
24
25
  *
25
- * Old-devtools limit we enforce here (a clear error beats a confusing
26
+ * Devtools limit we enforce here (a clear error beats a confusing
26
27
  * server-side 400): only `GET` / `POST` methods. OPRF redactions are passed
27
28
  * through unchanged — the backend's `hash` field accepts them.
28
29
  */
@@ -35,7 +36,7 @@ export interface OldProviderOpts {
35
36
  /** Defaults to `true` when omitted. Register only (Provider-level flag). */
36
37
  useProxy?: boolean
37
38
  providerType?: 'PRIVATE' | 'PUBLIC'
38
- /** Page-injection script — old-devtools' name for the builder's
39
+ /** Page-injection script — the devtools name for the builder's
39
40
  * `webSettings.jsUserScripts`. Top-level on register, nested under
40
41
  * `providerConfig` on config (add-version). The backend rejects a few
41
42
  * deprecated injection APIs (e.g. `window.payloadData`) with a 400. */
@@ -59,6 +60,14 @@ export interface OldProviderOpts {
59
60
  /** Config (add-version) ONLY — the register endpoint never reads this
60
61
  * field from the request body. Free-form JSON. */
61
62
  extensionConfig?: unknown
63
+ /** Provider-level `disableRequestReplay` (HAWKEYE document replay skip).
64
+ * Config (add-version) ONLY: the register endpoint hardcodes it to false
65
+ * — the publish tool follows a register up with an add-version call to
66
+ * apply it for new providers (see `old/tools/publish.ts`). NOTE the
67
+ * builder backend has NO equivalent; its inverse is
68
+ * `webSettings.clientOptions.inapp.interceptorOptions
69
+ * .isDocumentRequestReplayEnabled`. */
70
+ disableRequestReplay?: boolean
62
71
  }
63
72
 
64
73
  interface OldResponseMatch {
@@ -80,11 +89,11 @@ interface OldResponseRedaction {
80
89
 
81
90
  interface OldRequestData {
82
91
  url: string
83
- urlType: 'CONSTANT' | 'TEMPLATE'
92
+ urlType: 'REGEX' | 'CONSTANT' | 'TEMPLATE'
84
93
  method: 'GET' | 'POST'
85
94
  responseMatches: OldResponseMatch[]
86
95
  responseRedactions: OldResponseRedaction[]
87
- credentials: 'include'
96
+ credentials: 'omit' | 'same-origin' | 'include'
88
97
  bodySniff?: { enabled: boolean, template: string }
89
98
  }
90
99
 
@@ -103,36 +112,42 @@ export interface OldInjectedRequestData extends OldRequestData {
103
112
  * entries — a `ReclaimProvider` satisfies it structurally either way. */
104
113
  function requestDataFromProvider(provider: DraftRequest): OldRequestData {
105
114
  const method = String(provider.method).toUpperCase()
106
- if(method !== 'GET' && method !== 'POST') {
107
- throw new Error(
108
- 'Old devtools supports only GET/POST requests; got ' +
115
+ assert(
116
+ method === 'GET' || method === 'POST',
117
+ new Error(
118
+ 'Devtools supports only GET/POST requests; got ' +
109
119
  `"${provider.method}". Re-capture against a GET/POST endpoint.`,
110
120
  )
111
- }
121
+ )
112
122
 
113
- // The old backend has no dotted interpolation: consumer-supplied params
123
+ // The devtools backend has no dotted interpolation: consumer-supplied params
114
124
  // must use the `{{context_<name>}}` form here, not the builder's
115
125
  // `{{context.<name>}}`. Catch the mix-up at publish with a clear error
116
126
  // instead of a template that silently never resolves.
117
127
  const dotted = `${provider.url}\n${provider.body ?? ''}`
118
128
  .match(/\{\{context\.[A-Za-z_][A-Za-z0-9_]*\}\}/)
119
- if(dotted) {
120
- throw new Error(
121
- `Old devtools does not support ${dotted[0]} — rename ` +
129
+ assert(
130
+ !dotted,
131
+ new Error(
132
+ `Devtools does not support ${dotted?.[0]} — rename ` +
122
133
  'consumer-supplied params to the {{context_<name>}} form ' +
123
134
  'for this backend.',
124
135
  )
125
- }
136
+ )
126
137
 
127
138
  const responseMatches: OldResponseMatch[] = (provider.responseMatches ?? [])
128
139
  .map((m, i) => ({
129
140
  value: m.value,
130
141
  type: m.type ?? 'contains',
131
- isOptional: false,
142
+ // The backend honors isOptional on BOTH register and add-version
143
+ // (responseMatches pass through verbatim) — forward the author's
144
+ // flag, default false. (`invert` has no old-backend equivalent and
145
+ // stays dropped.)
146
+ isOptional: m.isOptional ?? false,
132
147
  order: i,
133
148
  }))
134
149
 
135
- // Old devtools wants `responseMatches` and `responseRedactions` as PARALLEL
150
+ // Devtools wants `responseMatches` and `responseRedactions` as PARALLEL
136
151
  // arrays — same shape the builder uses, so pass them through directly. We do
137
152
  // NOT enforce any match/redaction count relationship here (and never pad —
138
153
  // empty placeholder matches break proof generation): the author keeps full
@@ -166,36 +181,38 @@ function requestDataFromProvider(provider: DraftRequest): OldRequestData {
166
181
 
167
182
  const requestData: OldRequestData = {
168
183
  url: provider.url,
169
- // `{{param}}` placeholders → TEMPLATE so the backend templates them;
170
- // otherwise a fixed CONSTANT url. (REGEX is never drafted.)
171
- urlType: provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT',
184
+ // Explicit override wins; otherwise `{{param}}` placeholders → TEMPLATE
185
+ // so the backend templates them, else a fixed CONSTANT url.
186
+ urlType: provider.urlType
187
+ ?? (provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT'),
172
188
  method: method,
173
189
  responseMatches,
174
190
  responseRedactions,
175
191
  // The verification client re-supplies cookies/auth at runtime; secrets
176
192
  // are never persisted (propose_provider already stripped them).
177
- credentials: 'include',
193
+ credentials: provider.credentials ?? 'include',
178
194
  }
179
195
  if(provider.body !== undefined) {
180
196
  requestData.bodySniff = { enabled: true, template: provider.body }
181
197
  }
182
198
 
183
- // NOTE: `responseVariables` is intentionally omitted — the old backend
199
+ // NOTE: `responseVariables` is intentionally omitted — the devtools backend
184
200
  // auto-extracts it from `{{var}}` placeholders in responseMatches. Sending
185
201
  // it ourselves would risk drifting from the server's own extraction.
186
202
  return requestData
187
203
  }
188
204
 
189
205
  /** One `requestData` entry per drafted provider. A multi-request provider
190
- * becomes parallel entries that the old backend runs together at verification
191
- * (the same way the builder version carries multiple `requests`). */
206
+ * becomes parallel entries that the devtools backend runs together at
207
+ * verification (the same way the builder version carries multiple
208
+ * `requests`). */
192
209
  function requestDataFrom(providers: ReclaimProvider[]): OldRequestData[] {
193
210
  return providers.map(requestDataFromProvider)
194
211
  }
195
212
 
196
213
  /** Translate fresh `allowedInjectedRequestData` drafts (the author's input
197
214
  * shape, `AllowedJsRequest[]` — same shape the builder translator uses for
198
- * its own `allowedJsRequests`) into the old backend's shape. A
215
+ * its own `allowedJsRequests`) into the devtools backend's shape. A
199
216
  * carried-forward value read back from an existing version is ALREADY in
200
217
  * this shape and should be passed straight through instead — don't run it
201
218
  * through this a second time. */
@@ -300,10 +317,16 @@ export function providerToConfigBody(
300
317
  providerConfig.extensionConfig = opts.extensionConfig
301
318
  }
302
319
 
320
+ // The config (add-version) controller persists providerConfig verbatim,
321
+ // so this lands as-is; the register controller ignores it entirely.
322
+ if(opts.disableRequestReplay !== undefined) {
323
+ providerConfig.disableRequestReplay = opts.disableRequestReplay
324
+ }
325
+
303
326
  return { providerConfig, version, versionInfo }
304
327
  }
305
328
 
306
- /** Minimal version triplet as the old backend stores it. */
329
+ /** Minimal version triplet as the devtools backend stores it. */
307
330
  export interface SemverTriplet {
308
331
  major: number
309
332
  minor: number
@@ -19,6 +19,16 @@ import {
19
19
 
20
20
  const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture'
21
21
 
22
+ /** Pull the new provider's id out of a register response, tolerant of the
23
+ * envelope (`{providerId}`, `{provider:{providerId}}`, `{id}`). */
24
+ function extractProviderId(resp: unknown): string | undefined {
25
+ const obj = resp as
26
+ { providerId?: string, provider?: { providerId?: string }, id?: string }
27
+ | null
28
+ | undefined
29
+ return obj?.providerId ?? obj?.provider?.providerId ?? obj?.id
30
+ }
31
+
22
32
  /**
23
33
  * The old backend lets you add a version only to a provider YOU own. A
24
34
  * `providerId` for a public provider (or anyone else's) comes back as
@@ -81,11 +91,12 @@ export function createVersionFromCaptureTool(
81
91
  stepsToFollow?: string
82
92
  useIncognitoWebview?: boolean
83
93
  extensionConfig?: unknown
94
+ disableRequestReplay?: boolean
84
95
  }>(
85
96
  {
86
97
  name: 'create_provider_version_from_capture',
87
98
  description:
88
- 'Publish drafted ReclaimProvider(s) to the OLD devtools backend. ' +
99
+ 'Publish drafted ReclaimProvider(s) to the devtools backend. ' +
89
100
  'Pass the `provider` object from propose_provider VERBATIM and the ' +
90
101
  '`initialUrl` the user landed on (the login/landing page, NOT the ' +
91
102
  'API url). \n\n' +
@@ -103,7 +114,7 @@ export function createVersionFromCaptureTool(
103
114
  "PUBLIC provider you don't own, omit `providerId` to register your " +
104
115
  'own copy (you cannot edit someone else\'s provider here). \n\n' +
105
116
  'For data gated behind login/navigation, pass `customInjection` — a ' +
106
- 'page-injection script (old-devtools\' name for the builder\'s ' +
117
+ 'page-injection script (the devtools name for the builder\'s ' +
107
118
  'jsUserScripts) that drives the page to where the request fires. \n\n' +
108
119
  'BY DESIGN, this backend only ever CREATES A NEW VERSION (POST) — ' +
109
120
  'there is no partial-patch/edit endpoint, and this tool does NOT ' +
@@ -120,7 +131,14 @@ export function createVersionFromCaptureTool(
120
131
  'values plus your change) back into this call. ' +
121
132
  '`allowedInjectedRequestData`/`useIncognitoWebview`/' +
122
133
  '`extensionConfig` only take effect on add-version — the register ' +
123
- 'endpoint (no `providerId`) ignores them. \n\n' +
134
+ 'endpoint (no `providerId`) ignores them. `disableRequestReplay` ' +
135
+ 'is also add-version-only, but when it is true the tool follows ' +
136
+ 'a register up with an add-version call automatically so it takes ' +
137
+ 'effect on new providers too. Per-request fields you may set on ' +
138
+ '`provider`/`providers`: `credentials` (omit/same-origin/include, ' +
139
+ 'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
140
+ 'derived from {{placeholders}}), and `isOptional` on each ' +
141
+ 'responseMatch (default false). \n\n' +
124
142
  'Captured requests must be GET/POST. OPRF redactions are supported.',
125
143
  inputSchema: {
126
144
  type: 'object',
@@ -136,6 +154,21 @@ export function createVersionFromCaptureTool(
136
154
  method: { type: 'string' },
137
155
  headers: { type: 'object' },
138
156
  body: { type: 'string' },
157
+ credentials: {
158
+ type: 'string',
159
+ enum: ['omit', 'same-origin', 'include'],
160
+ description:
161
+ 'How the verification client supplies cookies/auth ' +
162
+ 'when replaying this request. Default `include`.',
163
+ },
164
+ urlType: {
165
+ type: 'string',
166
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
167
+ description:
168
+ 'How the backend treats the URL. Default derived: ' +
169
+ 'TEMPLATE when the URL has {{placeholders}}, else ' +
170
+ 'CONSTANT.',
171
+ },
139
172
  responseMatches: { type: 'array' },
140
173
  responseRedactions: { type: 'array' },
141
174
  paramValues: { type: 'object' },
@@ -165,6 +198,14 @@ export function createVersionFromCaptureTool(
165
198
  method: { type: 'string' },
166
199
  headers: { type: 'object' },
167
200
  body: { type: 'string' },
201
+ credentials: {
202
+ type: 'string',
203
+ enum: ['omit', 'same-origin', 'include'],
204
+ },
205
+ urlType: {
206
+ type: 'string',
207
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
208
+ },
168
209
  responseMatches: { type: 'array' },
169
210
  responseRedactions: { type: 'array' },
170
211
  paramValues: { type: 'object' },
@@ -253,6 +294,14 @@ export function createVersionFromCaptureTool(
253
294
  properties: {
254
295
  url: { type: 'string' },
255
296
  method: { type: 'string' },
297
+ urlType: {
298
+ type: 'string',
299
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
300
+ description:
301
+ 'How the backend treats this URL. Default derived ' +
302
+ 'from {{placeholders}} (TEMPLATE) like a normal ' +
303
+ 'request entry.',
304
+ },
256
305
  body: {
257
306
  type: 'string',
258
307
  description:
@@ -375,6 +424,15 @@ export function createVersionFromCaptureTool(
375
424
  'Free-form JSON passed through to the client. Add-version ' +
376
425
  'ONLY.',
377
426
  },
427
+ disableRequestReplay: {
428
+ type: 'boolean',
429
+ description:
430
+ 'Skip replaying the main document request (HAWKEYE ' +
431
+ 'provider-level flag). Honored on add-version; for a ' +
432
+ 'provider (no `providerId`) the register endpoint ignores ' +
433
+ 'it, so when true the tool automatically follows the ' +
434
+ 'register up with an add-version call that applies it.',
435
+ },
378
436
  },
379
437
  required: ['initialUrl'],
380
438
  },
@@ -413,6 +471,7 @@ export function createVersionFromCaptureTool(
413
471
  stepsToFollow: args.stepsToFollow,
414
472
  useIncognitoWebview: args.useIncognitoWebview,
415
473
  extensionConfig: args.extensionConfig,
474
+ disableRequestReplay: args.disableRequestReplay,
416
475
  allowedInjectedRequestData: args.allowedInjectedRequestData
417
476
  ? injectedRequestDataFrom(
418
477
  args.allowedInjectedRequestData,
@@ -448,6 +507,12 @@ export function createVersionFromCaptureTool(
448
507
  )
449
508
  }
450
509
 
510
+ // disableRequestReplay is honored ONLY on add-version, so a
511
+ // register can't set it — when true we immediately follow the
512
+ // register with an add-version call (which also lands any other
513
+ // register-ignored fields).
514
+ const followUp = args.disableRequestReplay === true
515
+
451
516
  const REGISTER_IGNORED_KEYS = [
452
517
  'allowedInjectedRequestData',
453
518
  'useIncognitoWebview',
@@ -459,15 +524,50 @@ export function createVersionFromCaptureTool(
459
524
  notes.push(
460
525
  `${ignoredOnRegister.join(', ')} ` +
461
526
  `${ignoredOnRegister.length > 1 ? 'are' : 'is'} ` +
462
- 'ignored on register (no providerId) — the old backend only ' +
463
- 'reads them when adding a version. Publish once to create ' +
464
- 'the provider, then call this again WITH the returned ' +
465
- 'providerId to set them.',
527
+ 'ignored on register (no providerId) — the devtools backend only ' +
528
+ 'reads them when adding a version. ' +
529
+ (followUp
530
+ ? 'They were applied by the follow-up version the tool ' +
531
+ 'created to set disableRequestReplay.'
532
+ : 'Publish once to create the provider, then call this ' +
533
+ 'again WITH the returned providerId to set them.'),
466
534
  )
467
535
  }
468
536
 
469
537
  const body = providerToRegisterBody(providers, opts)
470
- return withNotes(await client.registerProvider(body))
538
+ const registered = await client.registerProvider(body)
539
+
540
+ if(followUp) {
541
+ const newProviderId = extractProviderId(registered)
542
+ if(newProviderId) {
543
+ const versionsResp = await client.listVersions(newProviderId)
544
+ const followVersion = bumpPatchString(
545
+ extractVersions(versionsResp),
546
+ )
547
+ const configBody = providerToConfigBody(
548
+ providers,
549
+ { ...opts, disableRequestReplay: true },
550
+ followVersion,
551
+ DEFAULT_VERSION_INFO,
552
+ )
553
+ await client.updateProviderConfig(newProviderId, configBody)
554
+ notes.push(
555
+ 'disableRequestReplay is ignored by the register endpoint — ' +
556
+ `applied it via a follow-up version (${followVersion}) on ` +
557
+ 'the new provider, which also lands any register-ignored ' +
558
+ 'fields you passed.',
559
+ )
560
+ } else {
561
+ notes.push(
562
+ 'Could not apply disableRequestReplay: the register ' +
563
+ 'response did not include the new providerId. Call this ' +
564
+ 'tool again WITH `providerId` and `disableRequestReplay: ' +
565
+ 'true` to set it.',
566
+ )
567
+ }
568
+ }
569
+
570
+ return withNotes(registered)
471
571
  }
472
572
 
473
573
  // New version of an existing provider — pick the next patch above
@@ -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)) {
@@ -14,9 +14,10 @@
14
14
  * It also covers the HAWKEYE-style live-interception path (`injectionType` /
15
15
  * `interceptorType`, `disableRequestReplay` / `isDocumentRequestReplayEnabled`)
16
16
  * and `bodySniff`, reverse-engineered from the old-devtools Prisma schema,
17
- * controller code, and dashboard field hints — neither publish tool exposes
18
- * these as inputs (both only build the standard replay flow), so this is
19
- * read-the-field-and-recognize-it documentation, not authoring guidance.
17
+ * controller code, and dashboard field hints. The replay flag IS settable via
18
+ * the publish tools (`disableRequestReplay` on old-devtools — add-version or
19
+ * the register follow-up; `interceptorOptions` on the builder), so this is
20
+ * recognition + authoring guidance for the remaining internals.
20
21
  */
21
22
  import { readFileSync } from 'node:fs'
22
23
  import { fileURLToPath } from 'node:url'
@@ -15,6 +15,25 @@ export type HttpMethod = HttpParams['method']
15
15
  */
16
16
  export type ResponseMatch = HttpParams['responseMatches'][number]
17
17
 
18
+ /**
19
+ * A response match as drafted for PUBLISH: the attestor's match plus
20
+ * `isOptional` — a backend-side matcher flag (both the old devtools backend
21
+ * and the builder store it; the attestor's own params have no such field, so
22
+ * `propose_provider` drafts never set it — only an explicit publish input
23
+ * does). Default false on both backends.
24
+ */
25
+ export type DraftResponseMatch = ResponseMatch & { isOptional?: boolean }
26
+
27
+ /** How the verification client supplies cookies/auth when replaying a
28
+ * request. Both backends accept the same enum (old devtools on
29
+ * `requestData`, builder on `RequestSelection`); default `include`. */
30
+ export type WebCredentials = 'omit' | 'same-origin' | 'include'
31
+
32
+ /** Old-devtools `urlType` for a requestData entry. The builder backend has
33
+ * no such field (templating is implicit in the URL's `{{param}}`
34
+ * placeholders), so the builder translator ignores it. */
35
+ export type OldUrlType = 'REGEX' | 'CONSTANT' | 'TEMPLATE'
36
+
18
37
  /**
19
38
  * Selector telling the prover which portion of the response to reveal:
20
39
  * `xPath`/`jsonPath` narrows the slice, `regex` (with named groups) matches
@@ -67,8 +86,15 @@ export interface ReclaimProvider {
67
86
  method: HttpMethod
68
87
  headers?: Record<string, string>
69
88
  body?: string
70
- responseMatches: ResponseMatch[]
89
+ responseMatches: DraftResponseMatch[]
71
90
  responseRedactions?: ResponseRedaction[]
91
+ /** How the verification client supplies cookies/auth on replay. Both
92
+ * backends default to `include` when unset. */
93
+ credentials?: WebCredentials
94
+ /** Old-devtools-only url type override (`REGEX`/`CONSTANT`/`TEMPLATE`).
95
+ * Auto-derived from `{{param}}` placeholders when unset; ignored by the
96
+ * builder backend. */
97
+ urlType?: OldUrlType
72
98
  /** Public param refs (templated into URL/body), populated by draft layer. */
73
99
  paramValues?: Record<string, string>
74
100
  /** Redaction mode for the transcript (attestor default: 'key-update'). */