@reclaimprotocol/agent 0.1.22 → 0.1.24

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 (163) hide show
  1. package/README.md +55 -20
  2. package/lib/authoring/backend.d.ts +13 -10
  3. package/lib/authoring/tools.js +106 -9
  4. package/lib/cdp/browser-proxy.d.ts +78 -0
  5. package/lib/cdp/browser-proxy.js +186 -0
  6. package/lib/cdp/capture-engine.js +8 -7
  7. package/lib/cdp/client.d.ts +10 -0
  8. package/lib/cdp/geo.d.ts +10 -0
  9. package/lib/cdp/geo.js +36 -0
  10. package/lib/cdp/index.d.ts +1 -0
  11. package/lib/cdp/index.js +1 -0
  12. package/lib/live/cloudflared.d.ts +15 -0
  13. package/lib/live/cloudflared.js +268 -0
  14. package/lib/live/page.d.ts +20 -0
  15. package/lib/live/page.js +134 -0
  16. package/lib/live/tunnel.d.ts +23 -0
  17. package/lib/live/tunnel.js +120 -0
  18. package/lib/live/viewer.d.ts +28 -0
  19. package/lib/live/viewer.js +232 -0
  20. package/lib/mcp/api/operations.gen.js +34 -26
  21. package/lib/mcp/api/to-zod.d.ts +1 -1
  22. package/lib/mcp/api/to-zod.js +1 -1
  23. package/lib/mcp/api/tools.js +1 -1
  24. package/lib/mcp/instructions.d.ts +15 -2
  25. package/lib/mcp/instructions.js +101 -38
  26. package/lib/mcp/start.js +4 -3
  27. package/lib/mcp/tools/agent/analyze.js +5 -1
  28. package/lib/mcp/tools/agent/attach.d.ts +8 -1
  29. package/lib/mcp/tools/agent/attach.js +166 -22
  30. package/lib/mcp/tools/agent/build.d.ts +13 -8
  31. package/lib/mcp/tools/agent/build.js +22 -11
  32. package/lib/mcp/tools/agent/capture.js +5 -1
  33. package/lib/mcp/tools/agent/eval.js +3 -1
  34. package/lib/mcp/tools/agent/find.js +6 -5
  35. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  36. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  37. package/lib/mcp/tools/agent/live-view.d.ts +21 -0
  38. package/lib/mcp/tools/agent/live-view.js +153 -0
  39. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  40. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  41. package/lib/mcp/tools/agent/propose.js +29 -3
  42. package/lib/mcp/tools/agent/proxy.d.ts +3 -0
  43. package/lib/mcp/tools/agent/proxy.js +199 -0
  44. package/lib/mcp/tools/agent/publish.js +49 -77
  45. package/lib/mcp/tools/agent/session.d.ts +2 -1
  46. package/lib/mcp/tools/agent/session.js +26 -7
  47. package/lib/mcp/tools/agent/shared.js +17 -8
  48. package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
  49. package/lib/mcp/tools/authenticate/authenticate.js +143 -121
  50. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
  51. package/lib/mcp/tools/authenticate/builder-access.js +8 -3
  52. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  53. package/lib/mcp/tools/mode/mode.js +26 -7
  54. package/lib/old/client.d.ts +9 -0
  55. package/lib/old/client.js +19 -1
  56. package/lib/old/instructions.d.ts +9 -1
  57. package/lib/old/instructions.js +4 -6
  58. package/lib/old/login-cdp.d.ts +2 -2
  59. package/lib/old/login-cdp.js +46 -6
  60. package/lib/old/to-register.d.ts +20 -8
  61. package/lib/old/to-register.js +7 -1
  62. package/lib/old/tools/authenticate.js +9 -1
  63. package/lib/old/tools/get-provider-info.js +6 -5
  64. package/lib/old/tools/issue-app-credentials.js +3 -2
  65. package/lib/old/tools/me-providers.js +3 -1
  66. package/lib/old/tools/publish.d.ts +1 -1
  67. package/lib/old/tools/publish.js +132 -129
  68. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  69. package/lib/old/tools/update-provider-metadata.js +1 -1
  70. package/lib/old/tools/verify-sdk-setup.js +1 -1
  71. package/lib/proof/attestor.d.ts +3 -3
  72. package/lib/proof/debug-response-dump.js +4 -4
  73. package/lib/proof/index.d.ts +1 -0
  74. package/lib/proof/index.js +1 -0
  75. package/lib/proof/page-replay.d.ts +36 -0
  76. package/lib/proof/page-replay.js +76 -0
  77. package/lib/proof/pod-prover.d.ts +47 -0
  78. package/lib/proof/pod-prover.js +155 -0
  79. package/lib/proof/replay.d.ts +28 -0
  80. package/lib/proof/replay.js +39 -18
  81. package/lib/provider/dom-xpath.js +11 -9
  82. package/lib/provider/draft.d.ts +13 -8
  83. package/lib/provider/draft.js +47 -13
  84. package/lib/provider/injection-doc.d.ts +23 -1
  85. package/lib/provider/injection-doc.js +34 -21
  86. package/lib/provider/match-redaction-warning.d.ts +2 -2
  87. package/lib/provider/match-redaction-warning.js +3 -3
  88. package/lib/provider/matcher.js +122 -12
  89. package/lib/provider/secrets.d.ts +3 -3
  90. package/lib/provider/secrets.js +3 -3
  91. package/lib/provider/to-version.d.ts +12 -9
  92. package/lib/provider/to-version.js +16 -4
  93. package/lib/tools/registry.d.ts +5 -5
  94. package/lib/tools/registry.js +5 -5
  95. package/package.json +4 -3
  96. package/samples/demo-server/README.md +7 -6
  97. package/skill/SKILL.builder.md +356 -189
  98. package/skill/SKILL.common.md +314 -79
  99. package/skill/SKILL.old.md +340 -155
  100. package/skill/builder-mode.md +58 -17
  101. package/skill/hash-validation.md +170 -0
  102. package/skill/interception.md +235 -0
  103. package/skill/js-sdk-integration.md +13 -7
  104. package/skill/old-mode.md +53 -12
  105. package/skill/user-script.md +61 -0
  106. package/src/authoring/backend.ts +13 -10
  107. package/src/authoring/tools.ts +128 -9
  108. package/src/cdp/capture-engine.ts +8 -7
  109. package/src/cdp/client.ts +8 -0
  110. package/src/cdp/geo.ts +52 -0
  111. package/src/cdp/index.ts +1 -0
  112. package/src/consts.ts +1 -0
  113. package/src/live/cloudflared.ts +348 -0
  114. package/src/live/page.ts +145 -0
  115. package/src/live/tunnel.ts +157 -0
  116. package/src/live/viewer.ts +293 -0
  117. package/src/mcp/api/operations.gen.ts +34 -26
  118. package/src/mcp/api/to-zod.ts +1 -1
  119. package/src/mcp/api/tools.ts +1 -1
  120. package/src/mcp/instructions.ts +121 -48
  121. package/src/mcp/start.ts +4 -3
  122. package/src/mcp/tools/agent/analyze.ts +5 -1
  123. package/src/mcp/tools/agent/attach.ts +194 -22
  124. package/src/mcp/tools/agent/build.ts +26 -15
  125. package/src/mcp/tools/agent/capture.ts +5 -1
  126. package/src/mcp/tools/agent/eval.ts +4 -1
  127. package/src/mcp/tools/agent/find.ts +6 -5
  128. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  129. package/src/mcp/tools/agent/live-view.ts +198 -0
  130. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  131. package/src/mcp/tools/agent/propose.ts +31 -3
  132. package/src/mcp/tools/agent/publish.ts +54 -78
  133. package/src/mcp/tools/agent/session.ts +26 -5
  134. package/src/mcp/tools/agent/shared.ts +19 -8
  135. package/src/mcp/tools/authenticate/authenticate.ts +167 -165
  136. package/src/mcp/tools/authenticate/builder-access.ts +8 -3
  137. package/src/mcp/tools/mode/mode.ts +28 -7
  138. package/src/old/client.ts +30 -1
  139. package/src/old/instructions.ts +4 -10
  140. package/src/old/login-cdp.ts +50 -6
  141. package/src/old/to-register.ts +28 -8
  142. package/src/old/tools/authenticate.ts +9 -1
  143. package/src/old/tools/get-provider-info.ts +6 -5
  144. package/src/old/tools/issue-app-credentials.ts +3 -2
  145. package/src/old/tools/me-providers.ts +3 -1
  146. package/src/old/tools/publish.ts +140 -130
  147. package/src/old/tools/update-provider-metadata.ts +1 -1
  148. package/src/old/tools/verify-sdk-setup.ts +1 -1
  149. package/src/proof/attestor.ts +3 -3
  150. package/src/proof/debug-response-dump.ts +4 -4
  151. package/src/proof/index.ts +8 -0
  152. package/src/proof/page-replay.ts +131 -0
  153. package/src/proof/pod-prover.ts +231 -0
  154. package/src/proof/replay.ts +60 -19
  155. package/src/provider/dom-xpath.ts +11 -9
  156. package/src/provider/draft.ts +56 -13
  157. package/src/provider/injection-doc.ts +37 -24
  158. package/src/provider/match-redaction-warning.ts +3 -3
  159. package/src/provider/matcher.ts +151 -10
  160. package/src/provider/secrets.ts +3 -3
  161. package/src/provider/to-version.ts +30 -13
  162. package/src/tools/registry.ts +5 -5
  163. package/skill/injection-api.md +0 -335
@@ -1,7 +1,10 @@
1
1
  import { Boom, isBoom } from '@hapi/boom'
2
2
  import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
3
3
  import type { AttachState } from '../../mcp/tools/agent/attach.ts'
4
- import { INJECTION_GUIDE } from '../../provider/injection-doc.ts'
4
+ import {
5
+ ALLOWED_REQUESTS_HINT,
6
+ USER_SCRIPT_HINT,
7
+ } from '../../provider/injection-doc.ts'
5
8
  import {
6
9
  countsFor,
7
10
  matchRedactionWarnings,
@@ -35,8 +38,8 @@ function extractProviderId(resp: unknown): string | undefined {
35
38
  * `providerId` for a public provider (or anyone else's) comes back as
36
39
  * 401/403/404 — the dev's likely intent is to publish their OWN version of that
37
40
  * site, which on this backend means registering a separate copy. Rethrow those
38
- * failures with that actionable suggestion; leave other errors (e.g. a 400
39
- * version collision) untouched so their real detail surfaces.
41
+ * failures with that actionable suggestion; leave other errors (for example, a
42
+ * 400 version collision) untouched so their real detail surfaces.
40
43
  */
41
44
  function rethrowOwnershipError(err: unknown, providerId: string): never {
42
45
  const status = isBoom(err) ? err.output.statusCode : undefined
@@ -62,7 +65,7 @@ function rethrowOwnershipError(err: unknown, providerId: string): never {
62
65
  /**
63
66
  * Publish a drafted provider to the OLD devtools backend. Keeps the builder
64
67
  * tool NAME (`create_provider_version_from_capture`) so the skill flow is
65
- * unchanged, but the behaviour differs:
68
+ * unchanged, but the behavior differs:
66
69
  *
67
70
  * - WITHOUT `providerId` → `POST /api/providers/register`: creates a
68
71
  * brand-new provider + version 1.0.0. Re-running creates a DUPLICATE.
@@ -99,59 +102,44 @@ export function createVersionFromCaptureTool(
99
102
  stepsToFollow?: string
100
103
  useIncognitoWebview?: boolean
101
104
  extensionConfig?: unknown
105
+ injectionType?: 'NONE' | 'MSWJS' | 'XHOOK' | 'CDP' | 'HAWKEYE'
102
106
  disableRequestReplay?: boolean
103
107
  }>(
104
108
  {
105
109
  name: 'create_provider_version_from_capture',
106
110
  description:
107
- 'Publish drafted ReclaimProvider(s) to the devtools backend. ' +
108
- 'Pass the `provider` object from propose_provider VERBATIM and the ' +
109
- '`initialUrl` the user landed on (the login/landing page, NOT the ' +
110
- 'API url). \n\n' +
111
- 'For a MULTI-REQUEST provider, pass `providers: [...]` (each entry a ' +
112
- 'proven draft from propose_provider) — every entry becomes one ' +
113
- '`requestData` entry on the SAME version, and all are verified ' +
114
- 'together. Pass a single `provider` for the one-request case ' +
115
- '(provide exactly one of `provider` / `providers`). \n\n' +
116
- 'Omit `providerId` to REGISTER A NEW provider (creates it + version ' +
117
- '1.0.0; re-running creates a duplicate). Pass `providerId` (from ' +
118
- 'get_me_providers) to add a NEW VERSION to that existing provider ' +
119
- '(the patch is auto-bumped). \n\n' +
120
- 'You can only add versions to a provider YOU own — the `providerId` ' +
121
- 'must come from get_me_providers. To publish your own version of a ' +
122
- "PUBLIC provider you don't own, omit `providerId` to register your " +
123
- 'own copy (you cannot edit someone else\'s provider here). \n\n' +
124
- 'For data gated behind login/navigation, pass `customInjection` — a ' +
125
- 'page-injection script (the devtools name for the builder\'s ' +
126
- 'jsUserScripts) that drives the page to where the request fires. \n\n' +
127
- 'BY DESIGN, this backend only ever CREATES A NEW VERSION (POST) — ' +
128
- 'there is no partial-patch/edit endpoint, and this tool does NOT ' +
129
- 'read or carry anything forward from the previous version. Every ' +
130
- 'new version replaces the WHOLE providerConfig with exactly what ' +
131
- 'you pass in THIS call. Therefore any field you want present in the ' +
132
- 'new version MUST be supplied here — `customInjection`, `userAgent`, ' +
133
- '`pageTitle`, `allowedInjectedRequestData`, `stepsToFollow`, ' +
134
- '`useIncognitoWebview`, `extensionConfig`, and `requestData` (via ' +
135
- '`provider`/`providers`). Anything you OMIT is published blank/' +
136
- 'default in the new version, NOT preserved. To change one field ' +
137
- 'while keeping the rest, first read the current version with ' +
138
- 'get_provider_info, then pass the full desired config (the existing ' +
139
- 'values plus your change) back into this call. ' +
140
- '`allowedInjectedRequestData`/`useIncognitoWebview`/' +
141
- '`extensionConfig` only take effect on add-version — the register ' +
142
- 'endpoint (no `providerId`) ignores them. `disableRequestReplay` ' +
143
- 'is also add-version-only, but when it is true the tool follows ' +
144
- 'a register up with an add-version call automatically so it takes ' +
145
- 'effect on new providers too. Per-request fields you may set on ' +
146
- '`provider`/`providers`: `credentials` (omit/same-origin/include, ' +
147
- 'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
148
- 'derived from {{placeholders}}), and `isOptional` on each ' +
149
- 'responseMatch (default false). \n\n' +
150
- 'Captured requests must be GET/POST. OPRF redactions are supported. ' +
151
- 'On success, if a browser is still attached, the response\'s ' +
152
- '`_notes` reminds you to confirm with the developer before ' +
153
- 'calling `dispose_browser` — never close it unprompted, they ' +
154
- 'may want it for further edits.',
111
+ 'Publish drafted ReclaimProvider(s) to the devtools backend. Needs '
112
+ + 'its OWN reclaim_authenticate login — authenticate_builder does NOT '
113
+ + 'authorize this call. Pass the `provider` object(s) from '
114
+ + 'propose_provider VERBATIM, plus the `initialUrl` the user landed '
115
+ + 'on (the login/landing page, NOT the API url). Exactly one of '
116
+ + '`provider` (one request) or `providers` (a multi-request '
117
+ + 'provider — every entry becomes a parallel requestData entry, all '
118
+ + 'verified together). Captured requests must be GET/POST; OPRF '
119
+ + 'redactions are supported.\n\n'
120
+ + 'Omit `providerId` to REGISTER A NEW provider (creates it + '
121
+ + 'version 1.0.0 — re-running creates a DUPLICATE). Pass '
122
+ + '`providerId` (from get_me_providers) to add a NEW VERSION to it; '
123
+ + 'the patch auto-bumps. You can only version a provider YOU own — '
124
+ + 'to publish your own take on one you don\'t own, omit '
125
+ + '`providerId` to register your own copy.\n\n'
126
+ + 'THIS BACKEND ONLY EVER CREATES A NEW VERSION — there is no '
127
+ + 'partial-patch endpoint, and this tool carries NOTHING forward '
128
+ + 'from the previous version: every version replaces the WHOLE '
129
+ + 'providerConfig with exactly what you pass in THIS call. Any '
130
+ + 'field you want to survive must be re-supplied here '
131
+ + '(`customInjection`, `userAgent`, `pageTitle`, '
132
+ + '`allowedInjectedRequestData`, `stepsToFollow`, '
133
+ + '`useIncognitoWebview`, `extensionConfig`, `requestData`); '
134
+ + 'anything omitted is published blank. To change one field, read '
135
+ + 'the current config with get_provider_info first and pass it back '
136
+ + 'with your edit. `allowedInjectedRequestData`/'
137
+ + '`useIncognitoWebview`/`extensionConfig` apply on add-version '
138
+ + 'only, so a fresh register auto-follows with an add-version call '
139
+ + 'that applies them (and disables document replay — see '
140
+ + '`disableRequestReplay`).\n\n'
141
+ + 'Always read the response\'s `_notes`. '
142
+ + 'Full walkthrough: how_it_works({ topic: "publish" }).',
155
143
  inputSchema: {
156
144
  type: 'object',
157
145
  properties: {
@@ -263,15 +251,11 @@ export function createVersionFromCaptureTool(
263
251
  customInjection: {
264
252
  type: 'string',
265
253
  description:
266
- 'Plain JavaScript injected into the provider page before every ' +
267
- 'page load — old-devtools\' name for what the builder calls ' +
268
- '`webSettings.jsUserScripts`. Use standard browser APIs ' +
269
- '(window.location, element.click(), forms) to drive login/' +
270
- 'navigation so the target request fires — window.Reclaim is ' +
271
- 'only for rare edge cases, not navigation. The backend rejects ' +
272
- 'a few deprecated injection APIs (e.g. window.payloadData, ' +
273
- 'window.ReclaimInjected) with a 400. Guide:\n\n' +
274
- INJECTION_GUIDE,
254
+ USER_SCRIPT_HINT
255
+ + ' Old-devtools\' name for what the builder calls '
256
+ + 'webSettings.jsUserScripts. The backend rejects a few '
257
+ + 'deprecated injection APIs (window.payloadData, '
258
+ + 'window.ReclaimInjected) with a 400.',
275
259
  },
276
260
  userAgent: {
277
261
  type: 'object',
@@ -290,39 +274,15 @@ export function createVersionFromCaptureTool(
290
274
  allowedInjectedRequestData: {
291
275
  type: 'array',
292
276
  description:
293
- 'Extra requests the customInjection script is allowed to ' +
294
- 'fire (e.g. pagination) — add-version ONLY, the register ' +
295
- 'endpoint ignores this. Each item has the same shape as ' +
296
- '`provider`, plus `multiple`/`required` (default true). ' +
297
- 'IMPORTANT whenever a claim\'s request HAS a body: set `body` ' +
298
- 'here (see its own description below) — if only a small part ' +
299
- 'of the real body varies per user/run (e.g. one id field, not ' +
300
- 'the whole payload), template just that part with ' +
301
- '`{{paramName}}` and keep the rest as fixed literal text; see ' +
302
- 'the "bodySniff" section of the customInjection field\'s guide ' +
303
- 'for the exact mechanics and a worked example. ' +
304
- '⚠️ BEFORE populating this to gate ' +
305
- '`verifyProof(proof, { providerId })`: this whole entry (url, ' +
306
- 'method, body, responseMatches, responseRedactions) feeds a ' +
307
- 'client-side hash-equality check, and `url` is hashed VERBATIM ' +
308
- '— no regex/template resolution, even with `urlType: REGEX` or ' +
309
- '`TEMPLATE`. If the real captured URL contains ANY per-session ' +
310
- 'part (a tracking nonce, CSRF token, cache-busting timestamp), ' +
311
- 'no declared spec can EVER hash-match a real proof, no matter ' +
312
- 'how correct the rest of this entry is — populating' +
313
- 'it for such ' +
314
- 'a provider makes verifyProof reject every real proof,' +
315
- 'which is ' +
316
- 'worse than leaving this field empty. Diff the same endpoint\'s' +
317
- ' captured URL across two separate sessions before wiring this ' +
318
- 'up. If provider-hash validation keeps causing problems ' +
319
- '(unstable url, or any other persistent mismatch), the ' +
320
- 'fallback on the verifying side is `verifyProof(proof, { ' +
321
- 'dangerouslyDisableContentValidation: true })` — trusts only ' +
322
- 'the attestor\'s signature, skips the hash-equality check ' +
323
- 'entirely; see the "Provider-hash validation" section of the ' +
324
- 'customInjection field\'s guide for the full mechanics and ' +
325
- 'when to reach for it.',
277
+ 'Extra requests the customInjection user script is allowed to '
278
+ + 'fire (for example, pagination) — add-version ONLY, the '
279
+ + 'register '
280
+ + 'endpoint ignores it. Each item has the same shape as '
281
+ + '`provider`, plus `multiple`/`required` (default true). Set '
282
+ + '`body` whenever the request has one (see its own description '
283
+ + 'below). The builder calls the same concept '
284
+ + '`allowedJsRequests`. '
285
+ + ALLOWED_REQUESTS_HINT,
326
286
  items: {
327
287
  type: 'object',
328
288
  properties: {
@@ -344,7 +304,8 @@ export function createVersionFromCaptureTool(
344
304
  'body at all (skip only for a genuinely bodyless ' +
345
305
  'request, typically GET). If the real body is FIXED ' +
346
306
  '(identical for every user/run), put that literal body ' +
347
- 'text here verbatim. If only PART of it varies (e.g. ' +
307
+ 'text here verbatim. If only PART of it varies (for ' +
308
+ 'example, ' +
348
309
  '`{"request":1,"type":"user_info","id":"12345"}` where ' +
349
310
  'only `id` differs per user), template ONLY that part: ' +
350
311
  '`{"request":1,"type":"user_info",' +
@@ -367,7 +328,7 @@ export function createVersionFromCaptureTool(
367
328
  'reproduces the real captured request body ' +
368
329
  'byte-for-byte. Fallback if per-field templating keeps ' +
369
330
  'mismatching (more fields vary than expected, unstable ' +
370
- 'key order, etc.) and you can\'t pin down every ' +
331
+ 'key order, and so on) and you can\'t pin down every ' +
371
332
  'variable field: template the WHOLE body as one ' +
372
333
  'placeholder instead, `{{REQ_BODY_GRD}}` — the `GRD` ' +
373
334
  'suffix makes it a greedy match (captures everything) ' +
@@ -385,7 +346,7 @@ export function createVersionFromCaptureTool(
385
346
  '`value` must literally appear (or match, if `regex`) ' +
386
347
  'somewhere in the response body for the claim to be ' +
387
348
  'accepted; it commonly embeds one or more ' +
388
- '`{{paramName}}` extraction placeholders (e.g. ' +
349
+ '`{{paramName}}` extraction placeholders (for example, ' +
389
350
  '`"name":"{{name_0}}"`) whose real value is redacted ' +
390
351
  'out and supplied separately as a witness/extracted ' +
391
352
  'param. Use `${var}` (not `{{var}}`) inside this string ' +
@@ -401,7 +362,8 @@ export function createVersionFromCaptureTool(
401
362
  'Each item: `{ jsonPath?: string, xPath?: string, ' +
402
363
  'regex?: string, hash?: string }`. Identifies WHICH ' +
403
364
  'part of the response the matching `{{paramName}}` in ' +
404
- '`responseMatches` actually redacts/extracts — e.g. ' +
365
+ '`responseMatches` actually redacts or extracts — for ' +
366
+ 'example, ' +
405
367
  '`jsonPath: "$.data.items[0].name"` for a JSON response. ' +
406
368
  'Order/count should line up with `responseMatches`\' ' +
407
369
  'placeholders one-to-one for a typical claim.',
@@ -458,14 +420,36 @@ export function createVersionFromCaptureTool(
458
420
  'Free-form JSON passed through to the client. Add-version ' +
459
421
  'ONLY.',
460
422
  },
423
+ injectionType: {
424
+ type: 'string',
425
+ enum: ['NONE', 'MSWJS', 'XHOOK', 'CDP', 'HAWKEYE'],
426
+ description:
427
+ 'Interception mechanism for this version. Omit to leave it ' +
428
+ 'unset — the devtools backend\'s own column default ' +
429
+ '(`HAWKEYE`) applies, which is inert and harmless for the ' +
430
+ 'standard capture→replay flow with no interceptor script ' +
431
+ 'installed. Only set `HAWKEYE`/`MSWJS`/`XHOOK` when you are ' +
432
+ 'ALSO supplying a `customInjection` script that calls ' +
433
+ '`window.reclaimInterceptor.addResponseMiddleware(...)` for ' +
434
+ 'genuine live interception, or set `NONE` explicitly to be ' +
435
+ 'certain none is configured. Builder calls the equivalent ' +
436
+ 'concept `interceptorType` (no `NONE` there — omitting the ' +
437
+ 'whole `interceptorOptions` object is its no-interception ' +
438
+ 'signal instead). What each value actually does: ' +
439
+ 'how_it_works({ topic: "interception" }).',
440
+ },
461
441
  disableRequestReplay: {
462
442
  type: 'boolean',
463
443
  description:
464
- 'Skip replaying the main document request (HAWKEYE ' +
465
- 'provider-level flag). Honored on add-version; for a ' +
466
- 'provider (no `providerId`) the register endpoint ignores ' +
467
- 'it, so when true the tool automatically follows the ' +
468
- 'register up with an add-version call that applies it.',
444
+ 'Whether the page\'s own document/navigation request is ' +
445
+ 'skipped (constructed locally) instead of genuinely replayed ' +
446
+ 'through the interceptor. Defaults to `true` (disabled) when ' +
447
+ 'omitted. Only meaningful for `injectionType` ' +
448
+ 'HAWKEYE/MSWJS/XHOOK — pass `false` to enable real document ' +
449
+ 'replay for those (for example, the target value is embedded ' +
450
+ 'in the page\'s own HTML). Inert when `injectionType` is ' +
451
+ '`CDP` or ' +
452
+ 'unset.',
469
453
  },
470
454
  },
471
455
  required: ['initialUrl'],
@@ -505,7 +489,13 @@ export function createVersionFromCaptureTool(
505
489
  stepsToFollow: args.stepsToFollow,
506
490
  useIncognitoWebview: args.useIncognitoWebview,
507
491
  extensionConfig: args.extensionConfig,
508
- disableRequestReplay: args.disableRequestReplay,
492
+ // Author-settable, no forced default — the backend's own HAWKEYE
493
+ // column default applies when omitted, same as before this field
494
+ // existed at all.
495
+ injectionType: args.injectionType,
496
+ // Author-settable; defaults to disabled (see tool description).
497
+ // Only meaningful for HAWKEYE/MSWJS/XHOOK — inert for CDP.
498
+ disableRequestReplay: args.disableRequestReplay ?? true,
509
499
  allowedInjectedRequestData: args.allowedInjectedRequestData
510
500
  ? injectedRequestDataFrom(
511
501
  args.allowedInjectedRequestData,
@@ -558,12 +548,12 @@ export function createVersionFromCaptureTool(
558
548
  )
559
549
  }
560
550
 
561
- // disableRequestReplay is honored ONLY on add-version, so a
562
- // register can't set it — when true we immediately follow the
563
- // register with an add-version call (which also lands any other
564
- // register-ignored fields).
565
- const followUp = args.disableRequestReplay === true
566
-
551
+ // disableRequestReplay is honored ONLY on add-version — so every
552
+ // register is immediately followed by an add-version call that
553
+ // actually applies it (whatever value — default or author-set;
554
+ // which also lands an explicit injectionType, when passed —
555
+ // unverified whether register persists it — and any other
556
+ // register-ignored fields the caller passed).
567
557
  const REGISTER_IGNORED_KEYS = [
568
558
  'allowedInjectedRequestData',
569
559
  'useIncognitoWebview',
@@ -576,53 +566,73 @@ export function createVersionFromCaptureTool(
576
566
  `${ignoredOnRegister.join(', ')} ` +
577
567
  `${ignoredOnRegister.length > 1 ? 'are' : 'is'} ` +
578
568
  'ignored on register (no providerId) — the devtools backend only ' +
579
- 'reads them when adding a version. ' +
580
- (followUp
581
- ? 'They were applied by the follow-up version the tool ' +
582
- 'created to set disableRequestReplay.'
583
- : 'Publish once to create the provider, then call this ' +
584
- 'again WITH the returned providerId to set them.'),
569
+ 'reads them when adding a version. They were applied by the ' +
570
+ 'automatic follow-up version this tool always creates to apply ' +
571
+ 'disableRequestReplay.',
585
572
  )
586
573
  }
587
574
 
588
575
  const body = providerToRegisterBody(providers, opts)
589
576
  const registered = await client.registerProvider(body)
590
577
 
591
- if(followUp) {
592
- const newProviderId = extractProviderId(registered)
593
- if(newProviderId) {
578
+ const newProviderId = extractProviderId(registered)
579
+ if(newProviderId) {
580
+ // The provider already exists as of the register call above —
581
+ // a failure past this point must NOT reject the tool and
582
+ // discard `registered`/`newProviderId`, or a retry without
583
+ // `providerId` would register a DUPLICATE provider. Catch and
584
+ // warn instead, telling the caller to resume WITH this
585
+ // providerId.
586
+ try {
594
587
  const versionsResp = await client.listVersions(newProviderId)
595
588
  const followVersion = bumpPatchString(
596
589
  extractVersions(versionsResp),
597
590
  )
598
591
  const configBody = providerToConfigBody(
599
592
  providers,
600
- { ...opts, disableRequestReplay: true },
593
+ opts,
601
594
  followVersion,
602
595
  DEFAULT_VERSION_INFO,
603
596
  )
604
597
  await client.updateProviderConfig(newProviderId, configBody)
605
598
  notes.push(
606
- 'disableRequestReplay is ignored by the register endpoint — ' +
607
- `applied it via a follow-up version (${followVersion}) on ` +
608
- 'the new provider, which also lands any register-ignored ' +
609
- 'fields you passed.',
599
+ `Created an automatic follow-up version (${followVersion}) to ` +
600
+ `apply disableRequestReplay (${opts.disableRequestReplay})` +
601
+ (opts.injectionType !== undefined
602
+ ? ` and set injectionType (${opts.injectionType})`
603
+ : '') +
604
+ ' — the register endpoint ignores/may ignore those fields, so ' +
605
+ 'every fresh register gets one. It also landed any other ' +
606
+ 'register-ignored fields you passed.',
610
607
  )
611
- } else {
608
+ } catch(err) {
609
+ const detail = isBoom(err) ? err.message : String(err)
612
610
  notes.push(
613
- 'Could not apply disableRequestReplay: the register ' +
614
- 'response did not include the new providerId. Call this ' +
615
- 'tool again WITH `providerId` and `disableRequestReplay: ' +
616
- 'true` to set it.',
611
+ `Provider ${newProviderId} WAS created, but the automatic ` +
612
+ `follow-up version (to force-disable document replay${
613
+ opts.injectionType !== undefined
614
+ ? ' and set injectionType'
615
+ : ''
616
+ }) failed: ${detail}. Do NOT retry without providerId — that ` +
617
+ 'would register a duplicate. Instead, re-run ' +
618
+ 'create_provider_version_from_capture WITH ' +
619
+ `providerId: "${newProviderId}" to add the follow-up version.`,
617
620
  )
618
621
  }
622
+ } else {
623
+ notes.push(
624
+ 'Could not apply disableRequestReplay: the ' +
625
+ 'register response did not include the new providerId. Call ' +
626
+ 'this tool again WITH `providerId` to create a version that ' +
627
+ 'applies them.',
628
+ )
619
629
  }
620
630
 
621
631
  return withNotes(registered)
622
632
  }
623
633
 
624
634
  // New version of an existing provider — pick the next patch above
625
- // the highest existing version. A provider you don't own (e.g. a
635
+ // the highest existing version. A provider you don't own (for example, a
626
636
  // public one) fails here with 401/403/404; surface the register-a-copy
627
637
  // workaround instead of a bare permission error.
628
638
  const providerId = args.providerId
@@ -8,7 +8,7 @@ import type { ReclaimOldClient, UpdateProviderMetadataBody } from '../client.ts'
8
8
  * `create_provider_version_from_capture` paths: it does NOT touch
9
9
  * `providerConfig`/versions at all, and only the fields you pass here
10
10
  * change — everything else on the provider is left untouched (no need to
11
- * re-supply `requestData`/`customInjection`/etc., and
11
+ * re-supply `requestData`/`customInjection`, and so on, and
12
12
  * no new version is created).
13
13
  *
14
14
  * The backend 403s if you don't own the provider (and aren't an admin) —
@@ -122,7 +122,7 @@ export function verifySdkSetupTool(client: ReclaimOldClient): RegisteredTool {
122
122
  : 'PUBLIC_URL not set — fine if you only read the proof from the ' +
123
123
  'frontend flow, but the async webhook callback needs a ' +
124
124
  'publicly-reachable URL or it will never fire. Get one via a ' +
125
- 'tunnel (e.g. `ngrok http <port>`) or by deploying to a ' +
125
+ 'tunnel (for example, `ngrok http <port>`) or by deploying to a ' +
126
126
  'dev/staging server you already have — ask the user which they ' +
127
127
  'prefer.',
128
128
  })
@@ -53,13 +53,13 @@ export interface RunAttestorProofOptions {
53
53
  /** When resolving the key from env/.env/file, the address the caller expects
54
54
  * to sign with (sanity check). Ignored when `ownerPrivateKey` is given. */
55
55
  ownerAddress?: string
56
- /** Sign with this key directly (e.g. the app/VC's ephemeral per-session key)
57
- * instead of resolving from env/.env/file. */
56
+ /** Sign with this key directly (for example, the app or VC's ephemeral
57
+ * per-session key) instead of resolving from env/.env/file. */
58
58
  ownerPrivateKey?: string
59
59
  /** ZK engine for `createClaimOnAttestor`. Defaults to `'stwo'` — a pure-WASM
60
60
  * prover that runs everywhere (no native shared library, so it works on
61
61
  * Windows where `'gnark'` fails to load its `.dll`). Server-side paths that
62
- * want gnark's speed pin it explicitly (e.g. the verify runner). */
62
+ * want gnark's speed pin it explicitly (for example, the verify runner). */
63
63
  zkEngine?: 'gnark' | 'snarkjs' | 'stwo'
64
64
  /** Extract the structured {@link AttestorClaim} (claim + attestor signature)
65
65
  * onto the result for nested-proof construction. */
@@ -1,10 +1,10 @@
1
1
  // Debug helper (gated by RECLAIM_AGENT_DUMP_RESPONSE): capture the EXACT
2
2
  // response bytes the attestor validates a claim against, plus a per-match
3
3
  // presence check. A "Response does not contain X" failure means the attestor's
4
- // server-side re-fetch differs from what the browser (replay) saw — e.g. GitHub
5
- // embeds nav/userMenu data for browser requests but not for the attestor's raw
6
- // fetch. Seeing the attestor's actual response is the only way to tell what it
7
- // really returned.
4
+ // server-side re-fetch differs from what the browser (replay) saw — for
5
+ // example, GitHub embeds nav/userMenu data for browser requests but not for the
6
+ // attestor's raw fetch. Seeing the attestor's actual response is the only way
7
+ // to tell what it really returned.
8
8
  //
9
9
  // On each getResponseRedactions call it writes the response to
10
10
  // `attestor-last-response.html` in the process CWD and logs the byte count +,
@@ -15,6 +15,14 @@ export {
15
15
  parseClaim,
16
16
  resolveAttestorUrl,
17
17
  } from './proof-core.ts'
18
+ export {
19
+ type HttpParams,
20
+ type HttpSecretParams,
21
+ type PopcornProveOptions,
22
+ popcornProve,
23
+ popcornValidateExtraction,
24
+ type ValidateExtractionResult,
25
+ } from './pod-prover.ts'
18
26
  export { ensureZkFiles } from './zk-files.ts'
19
27
  // Re-exported attestor-core claim primitive so the agent is the single
20
28
  // integration point with @reclaimprotocol/attestor-core across the builder.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Replay a drafted provider's AUTHENTICATED request FROM INSIDE the live
3
+ * attached browser tab, over CDP `Runtime.evaluate` — so cookies/session are
4
+ * always the real browser's, for every browser source the MCP can attach to
5
+ * (local Chrome, remote Popcorn, or a caller-supplied custom CDP endpoint all
6
+ * funnel through one `AuthoringBackend.sendCdp`). Ports the pattern already
7
+ * proven in the cloud/Popcorn authoring backend
8
+ * (`packages/app/src/author/backend.ts` `pageFetch`/`authenticatedFetch`),
9
+ * adapted to the MCP's single-page `sendCdp(method, params)` (no separate
10
+ * `pageSession` argument). The ANONYMOUS auth-bound check does NOT go through
11
+ * the page — see `replayProviderInPage`'s doc comment below for why.
12
+ */
13
+ import type { ReclaimProvider } from '../provider/schema.ts'
14
+ import {
15
+ buildReplayRequest,
16
+ evaluateReplay,
17
+ replayProvider,
18
+ type ReplayResult
19
+ } from './replay.ts'
20
+
21
+ /** Everything this module needs from an `AuthoringBackend` — just the raw CDP
22
+ * seam, so callers can pass a minimal object instead of a full backend. */
23
+ export interface PageReplayTarget {
24
+ sendCdp(method: string, params?: Record<string, unknown>): Promise<unknown>
25
+ }
26
+
27
+ interface PageFetchResult {
28
+ status?: number
29
+ body?: string
30
+ error?: string
31
+ }
32
+
33
+ /** Run `fetch(...)` for `provider`'s (already-templated) request inside the
34
+ * attached tab, via `Runtime.evaluate`. `credentials` selects the visitor:
35
+ * `'omit'` = anonymous (the auth-bound check), `'include'` = the logged-in
36
+ * user (the reproducibility gate). Same-origin only — the tab is already on
37
+ * the target site; a cross-origin/CORS failure comes back as `error`. */
38
+ async function pageFetch(
39
+ backend: PageReplayTarget,
40
+ provider: ReclaimProvider,
41
+ credentials: 'omit' | 'include',
42
+ extraSecrets: Record<string, string>,
43
+ ): Promise<PageFetchResult> {
44
+ const req = buildReplayRequest(provider, extraSecrets)
45
+ const expression = `(async () => {
46
+ try {
47
+ const r = await fetch(${JSON.stringify(req.url)}, {
48
+ method: ${JSON.stringify(req.method)},
49
+ headers: ${JSON.stringify(req.headers)},
50
+ credentials: ${JSON.stringify(credentials)},${req.body !== undefined
51
+ ? `\n\t\t\t\tbody: ${JSON.stringify(req.body)},`
52
+ : ''}
53
+ })
54
+ return { status: r.status, body: await r.text() }
55
+ } catch(e) { return { error: String((e && e.message) || e) } }
56
+ })()`
57
+ try {
58
+ const res = (await backend.sendCdp('Runtime.evaluate', {
59
+ expression,
60
+ awaitPromise: true,
61
+ returnByValue: true,
62
+ })) as {
63
+ result?: { value?: PageFetchResult }
64
+ exceptionDetails?: { exception?: { description?: string }, text?: string }
65
+ }
66
+ if(res.exceptionDetails) {
67
+ return {
68
+ error: res.exceptionDetails.exception?.description
69
+ ?? res.exceptionDetails.text
70
+ ?? 'page fetch evaluation error',
71
+ }
72
+ }
73
+
74
+ return res.result?.value ?? { error: 'page fetch returned no value' }
75
+ } catch(err) {
76
+ return { error: err instanceof Error ? err.message : String(err) }
77
+ }
78
+ }
79
+
80
+ /** Authenticated re-issue for the reproducibility gate. Cookies ride along
81
+ * via `credentials: 'include'` (a `Cookie` header can't be set from
82
+ * `fetch()` — it's a forbidden header name), so only NON-cookie secrets
83
+ * (Authorization, X-* tokens) are injected explicitly. */
84
+ function authenticatedPageFetch(
85
+ backend: PageReplayTarget,
86
+ provider: ReclaimProvider,
87
+ secrets: Record<string, string>,
88
+ ): Promise<PageFetchResult> {
89
+ const extra: Record<string, string> = {}
90
+ for(const [k, v] of Object.entries(secrets)) {
91
+ if(k.toLowerCase() !== 'cookie') {
92
+ extra[k] = v
93
+ }
94
+ }
95
+
96
+ return pageFetch(backend, provider, 'include', extra)
97
+ }
98
+
99
+ /**
100
+ * Replay `provider` and judge the response. Without `withoutSecrets`, replays
101
+ * as the logged-in session — run FROM INSIDE the live tab
102
+ * (`authenticatedPageFetch`) so cookies are the real browser's, which a
103
+ * detached call could never reproduce without manually re-deriving them.
104
+ * WITH `withoutSecrets` (the auth-bound check — "does this still work with
105
+ * no credentials, that is, is it actually public"), replay instead through the
106
+ * detached Node-side `replayProvider`: the whole point of that check is to
107
+ * prove NO secret rides along, and the live page's own global `fetch` can't
108
+ * give that guarantee — a site (or a script installed via test_user_script)
109
+ * can wrap `fetch` and re-attach a token from localStorage/a service worker
110
+ * even under `credentials:'omit'`. A fresh, page-uninvolved fetch has no
111
+ * such surface.
112
+ */
113
+ export async function replayProviderInPage(
114
+ backend: PageReplayTarget,
115
+ provider: ReclaimProvider,
116
+ secrets: Record<string, string>,
117
+ opts?: { withoutSecrets?: boolean },
118
+ ): Promise<ReplayResult> {
119
+ if(opts?.withoutSecrets) {
120
+ return replayProvider(provider, {})
121
+ }
122
+
123
+ const result = await authenticatedPageFetch(backend, provider, secrets)
124
+ if(result.error !== undefined || result.status === undefined) {
125
+ throw new Error(
126
+ `in-browser replay failed: ${result.error ?? 'no response'}`,
127
+ )
128
+ }
129
+
130
+ return evaluateReplay(result.status, result.body ?? '', provider)
131
+ }