@reclaimprotocol/agent 0.1.17 → 0.1.20

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 (171) hide show
  1. package/README.md +6 -2
  2. package/lib/authoring/backend.d.ts +4 -0
  3. package/lib/authoring/tools.js +4 -1
  4. package/lib/cdp/capture.d.ts +2 -2
  5. package/lib/cdp/capture.js +25 -21
  6. package/lib/cdp/client.d.ts +39 -1
  7. package/lib/cdp/client.js +125 -1
  8. package/lib/consts.d.ts +7 -0
  9. package/lib/consts.js +15 -0
  10. package/lib/mcp/api/operations.gen.js +33 -9
  11. package/lib/mcp/instructions.js +27 -3
  12. package/lib/mcp/start.js +12 -8
  13. package/lib/mcp/tools/agent/attach.d.ts +18 -4
  14. package/lib/mcp/tools/agent/attach.js +142 -52
  15. package/lib/mcp/tools/agent/build.d.ts +1 -1
  16. package/lib/mcp/tools/agent/build.js +5 -3
  17. package/lib/mcp/tools/agent/capture.d.ts +1 -0
  18. package/lib/mcp/tools/agent/capture.js +8 -1
  19. package/lib/mcp/tools/agent/eval.js +2 -3
  20. package/lib/mcp/tools/agent/how-it-works.d.ts +1 -1
  21. package/lib/mcp/tools/agent/how-it-works.js +1 -1
  22. package/lib/mcp/tools/agent/publish.js +55 -1
  23. package/lib/mcp/tools/agent/session.d.ts +2 -2
  24. package/lib/mcp/tools/agent/session.js +21 -5
  25. package/lib/mcp/tools/agent/shared.js +4 -6
  26. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  27. package/lib/mcp/tools/authenticate/authenticate.js +10 -4
  28. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -0
  29. package/lib/mcp/tools/authenticate/builder-access.js +31 -0
  30. package/lib/mcp/tools/authenticate/index.d.ts +1 -0
  31. package/lib/mcp/tools/authenticate/index.js +1 -0
  32. package/lib/mcp/tools/mode/mode.d.ts +3 -2
  33. package/lib/mcp/tools/mode/mode.js +24 -4
  34. package/lib/old/login-cdp.js +11 -3
  35. package/lib/old/to-register.d.ts +16 -8
  36. package/lib/old/to-register.js +29 -21
  37. package/lib/old/tools/publish.js +109 -9
  38. package/lib/provider/draft.d.ts +20 -0
  39. package/lib/provider/draft.js +75 -51
  40. package/lib/provider/index.d.ts +1 -1
  41. package/lib/provider/index.js +1 -1
  42. package/lib/provider/injection-doc.js +4 -3
  43. package/lib/provider/schema.d.ts +26 -1
  44. package/lib/provider/secrets.d.ts +37 -0
  45. package/lib/provider/secrets.js +147 -6
  46. package/lib/provider/to-version.d.ts +26 -2
  47. package/lib/provider/to-version.js +29 -5
  48. package/package.json +2 -2
  49. package/skill/SKILL.builder.md +43 -95
  50. package/skill/SKILL.common.md +102 -0
  51. package/skill/SKILL.old.md +20 -74
  52. package/skill/builder-mode.md +4 -2
  53. package/skill/injection-api.md +95 -10
  54. package/skill/old-mode.md +5 -3
  55. package/src/authoring/backend.ts +4 -0
  56. package/src/authoring/tools.ts +4 -1
  57. package/src/cdp/capture.ts +24 -24
  58. package/src/cdp/client.ts +197 -2
  59. package/src/consts.ts +20 -0
  60. package/src/mcp/api/operations.gen.ts +33 -9
  61. package/src/mcp/instructions.ts +36 -3
  62. package/src/mcp/start.ts +13 -8
  63. package/src/mcp/tools/agent/attach.ts +198 -57
  64. package/src/mcp/tools/agent/build.ts +9 -2
  65. package/src/mcp/tools/agent/capture.ts +10 -1
  66. package/src/mcp/tools/agent/eval.ts +2 -3
  67. package/src/mcp/tools/agent/how-it-works.ts +1 -1
  68. package/src/mcp/tools/agent/publish.ts +64 -1
  69. package/src/mcp/tools/agent/session.ts +29 -8
  70. package/src/mcp/tools/agent/shared.ts +5 -10
  71. package/src/mcp/tools/authenticate/authenticate.ts +14 -4
  72. package/src/mcp/tools/authenticate/builder-access.ts +42 -0
  73. package/src/mcp/tools/authenticate/index.ts +4 -0
  74. package/src/mcp/tools/mode/mode.ts +28 -4
  75. package/src/old/login-cdp.ts +13 -3
  76. package/src/old/to-register.ts +50 -27
  77. package/src/old/tools/publish.ts +131 -9
  78. package/src/provider/draft.ts +98 -56
  79. package/src/provider/index.ts +9 -1
  80. package/src/provider/injection-doc.ts +4 -3
  81. package/src/provider/schema.ts +27 -1
  82. package/src/provider/secrets.ts +171 -6
  83. package/src/provider/to-version.ts +53 -7
  84. package/lib/authoring/find.d.ts +0 -3
  85. package/lib/authoring/find.js +0 -54
  86. package/lib/authoring/propose.d.ts +0 -3
  87. package/lib/authoring/propose.js +0 -109
  88. package/lib/authoring/replay.d.ts +0 -10
  89. package/lib/authoring/replay.js +0 -34
  90. package/lib/cli/args.d.ts +0 -14
  91. package/lib/cli/args.js +0 -19
  92. package/lib/cli/commands.d.ts +0 -1
  93. package/lib/cli/commands.js +0 -19
  94. package/lib/cli.d.ts +0 -1
  95. package/lib/cli.js +0 -7
  96. package/lib/mcp/credentials/authenticate.d.ts +0 -3
  97. package/lib/mcp/credentials/authenticate.js +0 -108
  98. package/lib/mcp/credentials/build.d.ts +0 -5
  99. package/lib/mcp/credentials/build.js +0 -18
  100. package/lib/mcp/credentials/cache.d.ts +0 -70
  101. package/lib/mcp/credentials/cache.js +0 -131
  102. package/lib/mcp/credentials/env-file.d.ts +0 -25
  103. package/lib/mcp/credentials/env-file.js +0 -71
  104. package/lib/mcp/credentials/import.d.ts +0 -2
  105. package/lib/mcp/credentials/import.js +0 -52
  106. package/lib/mcp/credentials/issue.d.ts +0 -2
  107. package/lib/mcp/credentials/issue.js +0 -84
  108. package/lib/mcp/credentials/keypair.d.ts +0 -17
  109. package/lib/mcp/credentials/keypair.js +0 -48
  110. package/lib/mcp/credentials/link.d.ts +0 -3
  111. package/lib/mcp/credentials/link.js +0 -107
  112. package/lib/mcp/credentials/list-cached.d.ts +0 -2
  113. package/lib/mcp/credentials/list-cached.js +0 -55
  114. package/lib/mcp/credentials/owner-key.d.ts +0 -23
  115. package/lib/mcp/credentials/owner-key.js +0 -58
  116. package/lib/mcp/credentials/paths.d.ts +0 -12
  117. package/lib/mcp/credentials/paths.js +0 -35
  118. package/lib/mcp/credentials/poll.d.ts +0 -27
  119. package/lib/mcp/credentials/poll.js +0 -61
  120. package/lib/mcp/credentials/register.d.ts +0 -18
  121. package/lib/mcp/credentials/register.js +0 -30
  122. package/lib/mcp/credentials/resolve-owner-key.d.ts +0 -12
  123. package/lib/mcp/credentials/resolve-owner-key.js +0 -68
  124. package/lib/mcp/credentials/session-cache.d.ts +0 -14
  125. package/lib/mcp/credentials/session-cache.js +0 -42
  126. package/lib/mcp/credentials/status.d.ts +0 -3
  127. package/lib/mcp/credentials/status.js +0 -76
  128. package/lib/mcp/register.d.ts +0 -38
  129. package/lib/mcp/register.js +0 -80
  130. package/lib/mcp/tools/analyze.d.ts +0 -3
  131. package/lib/mcp/tools/analyze.js +0 -26
  132. package/lib/mcp/tools/attach.d.ts +0 -11
  133. package/lib/mcp/tools/attach.js +0 -72
  134. package/lib/mcp/tools/capture.d.ts +0 -10
  135. package/lib/mcp/tools/capture.js +0 -142
  136. package/lib/mcp/tools/eval.d.ts +0 -5
  137. package/lib/mcp/tools/eval.js +0 -127
  138. package/lib/mcp/tools/find.d.ts +0 -3
  139. package/lib/mcp/tools/find.js +0 -60
  140. package/lib/mcp/tools/owner-key.d.ts +0 -2
  141. package/lib/mcp/tools/owner-key.js +0 -63
  142. package/lib/mcp/tools/proof.d.ts +0 -3
  143. package/lib/mcp/tools/proof.js +0 -214
  144. package/lib/mcp/tools/propose.d.ts +0 -9
  145. package/lib/mcp/tools/propose.js +0 -75
  146. package/lib/mcp/tools/publish.d.ts +0 -3
  147. package/lib/mcp/tools/publish.js +0 -59
  148. package/lib/mcp/tools/replay.d.ts +0 -3
  149. package/lib/mcp/tools/replay.js +0 -46
  150. package/lib/mcp/tools/session.d.ts +0 -10
  151. package/lib/mcp/tools/session.js +0 -29
  152. package/lib/mcp/tools/verification/diagnose.d.ts +0 -11
  153. package/lib/mcp/tools/verification/diagnose.js +0 -132
  154. package/lib/mcp/verifications/build.d.ts +0 -5
  155. package/lib/mcp/verifications/build.js +0 -14
  156. package/lib/mcp/verifications/create.d.ts +0 -3
  157. package/lib/mcp/verifications/create.js +0 -57
  158. package/lib/mcp/verifications/find.d.ts +0 -3
  159. package/lib/mcp/verifications/find.js +0 -37
  160. package/lib/mcp/verifications/get.d.ts +0 -3
  161. package/lib/mcp/verifications/get.js +0 -26
  162. package/lib/mcp/verifications/update.d.ts +0 -3
  163. package/lib/mcp/verifications/update.js +0 -78
  164. package/lib/old/tools/get-version-config.d.ts +0 -17
  165. package/lib/old/tools/get-version-config.js +0 -47
  166. package/lib/routes.gen.d.ts +0 -271
  167. package/lib/routes.gen.js +0 -73
  168. package/lib/types/openapi.d.ts +0 -4
  169. package/lib/types/openapi.gen.d.ts +0 -3967
  170. package/lib/types/openapi.gen.js +0 -5
  171. package/lib/types/openapi.js +0 -1
@@ -0,0 +1,42 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client'
2
+ import { defineTool, type RegisteredTool } from '../../server.ts'
3
+ import { authenticateTool } from './authenticate.ts'
4
+
5
+ /** Legacy-devtools mode needs a distinct Builder login solely for allocating
6
+ * quota-accounted Popcorn sessions. Builder mode uses its native auth tool. */
7
+ export function buildBuilderAuthenticationTools(
8
+ client: ReclaimClient,
9
+ ): RegisteredTool[] {
10
+ return [authenticateTool(client, 'authenticate_builder')]
11
+ }
12
+
13
+ /** Legacy-devtools mode lacks generated Builder API tools, so it needs this
14
+ * narrow organization-discovery bridge for remote-browser allocation. */
15
+ export function buildBuilderOrganizationTools(
16
+ client: ReclaimClient,
17
+ ): RegisteredTool[] {
18
+ return [
19
+ defineTool<{ cursor?: string }>(
20
+ {
21
+ name: 'list_builder_organizations',
22
+ description:
23
+ 'List organizations available to the authenticated Builder user. '
24
+ + 'Use an organization id from this result when calling '
25
+ + 'attach_browser with mode="builder". Available only in '
26
+ + 'legacy-devtools publishing mode; Builder mode uses list_orgs.',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ cursor: { type: 'string', maxLength: 500 },
31
+ },
32
+ },
33
+ },
34
+ async({ cursor }) => {
35
+ const { data } = await client.call('ListOrgs', {
36
+ query: { ...(cursor ? { cursor } : {}) },
37
+ })
38
+ return data
39
+ },
40
+ ),
41
+ ]
42
+ }
@@ -1,4 +1,8 @@
1
1
  // Barrel for the credential tool group: local eth proof-owner key (env/PEM)
2
2
  // and the org-token-authenticated builder tools. Import from here.
3
+ export {
4
+ buildBuilderAuthenticationTools,
5
+ buildBuilderOrganizationTools,
6
+ } from './builder-access.ts'
3
7
  export { buildCredentialTools, buildLocalCredentialTools } from './build.ts'
4
8
  export { readSession } from './session-cache.ts'
@@ -1,10 +1,12 @@
1
1
  import type { ConfigStore, DevtoolsMode } from '../../../config/store.ts'
2
2
  import { defaultConfigPath } from '../../../config/store.ts'
3
+ import { oldModeSource } from '../../../consts.ts'
3
4
  import { defineTool, type RegisteredTool } from '../../server.ts'
4
5
 
5
6
  /**
6
- * The `set_devtools_mode` tool: persist which backend the MCP server targets
7
- * (legacy `old` devtools vs the new `builder`). The choice is written to
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
8
10
  * `~/.reclaim/config.json` and applied the NEXT time the server starts —
9
11
  * the registered tool set, server instructions, and backend client are all
10
12
  * fixed at connect time, so the switch requires a restart/reconnect. Follows
@@ -25,8 +27,8 @@ export function buildModeTools(
25
27
  {
26
28
  name: 'set_devtools_mode',
27
29
  description:
28
- 'Choose which backend the MCP server targets: "old" (legacy '
29
- + 'Reclaim devtools) or "builder" (the new builder backend). '
30
+ 'Choose which backend the MCP server targets: "old" (devtools) '
31
+ + 'or "builder" (the builder backend). '
30
32
  + 'Persists the choice to ~/.reclaim/config.json; it takes effect '
31
33
  + 'the next time the MCP server starts, so restart/reconnect after '
32
34
  + 'switching. An explicit USE_OLD_DEVTOOLS env var overrides this.',
@@ -54,5 +56,27 @@ export function buildModeTools(
54
56
  }
55
57
  },
56
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
+ ),
57
81
  ]
58
82
  }
@@ -138,15 +138,14 @@ export async function captureFirebaseTokenViaCdp(opts: {
138
138
 
139
139
  try {
140
140
  const session = await handles.attachToTab(targetId)
141
- const { Runtime } = session
142
141
  const deadline = Date.now() + timeoutMs
143
142
  while(Date.now() < deadline) {
144
- const r = await Runtime.evaluate({
143
+ const result = await session.send('Runtime.evaluate', {
145
144
  expression: EXTRACT_TOKEN_JS,
146
145
  awaitPromise: true,
147
146
  returnByValue: true,
148
147
  })
149
- const value = r.result?.value
148
+ const value = runtimeResultValue(result)
150
149
  if(typeof value === 'string' && value.length > 0) {
151
150
  return { token: value, closedBrowser: launchedByUs }
152
151
  }
@@ -173,3 +172,14 @@ export async function captureFirebaseTokenViaCdp(opts: {
173
172
  await handles.close().catch(() => {})
174
173
  }
175
174
  }
175
+
176
+ function runtimeResultValue(value: unknown): unknown {
177
+ if(typeof value !== 'object' || !value || !('result' in value)) {
178
+ return undefined
179
+ }
180
+
181
+ const result = value.result
182
+ return typeof result === 'object' && result && 'value' in result
183
+ ? result.value
184
+ : undefined
185
+ }
@@ -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' },
@@ -247,12 +288,42 @@ export function createVersionFromCaptureTool(
247
288
  'the whole payload), template just that part with ' +
248
289
  '`{{paramName}}` and keep the rest as fixed literal text; see ' +
249
290
  'the "bodySniff" section of the customInjection field\'s guide ' +
250
- 'for the exact mechanics and a worked example.',
291
+ 'for the exact mechanics and a worked example. ' +
292
+ '⚠️ BEFORE populating this to gate ' +
293
+ '`verifyProof(proof, { providerId })`: this whole entry (url, ' +
294
+ 'method, body, responseMatches, responseRedactions) feeds a ' +
295
+ 'client-side hash-equality check, and `url` is hashed VERBATIM ' +
296
+ '— no regex/template resolution, even with `urlType: REGEX` or ' +
297
+ '`TEMPLATE`. If the real captured URL contains ANY per-session ' +
298
+ 'part (a tracking nonce, CSRF token, cache-busting timestamp), ' +
299
+ 'no declared spec can EVER hash-match a real proof, no matter ' +
300
+ 'how correct the rest of this entry is — populating' +
301
+ 'it for such ' +
302
+ 'a provider makes verifyProof reject every real proof,' +
303
+ 'which is ' +
304
+ 'worse than leaving this field empty. Diff the same endpoint\'s' +
305
+ ' captured URL across two separate sessions before wiring this ' +
306
+ 'up. If provider-hash validation keeps causing problems ' +
307
+ '(unstable url, or any other persistent mismatch), the ' +
308
+ 'fallback on the verifying side is `verifyProof(proof, { ' +
309
+ 'dangerouslyDisableContentValidation: true })` — trusts only ' +
310
+ 'the attestor\'s signature, skips the hash-equality check ' +
311
+ 'entirely; see the "Provider-hash validation" section of the ' +
312
+ 'customInjection field\'s guide for the full mechanics and ' +
313
+ 'when to reach for it.',
251
314
  items: {
252
315
  type: 'object',
253
316
  properties: {
254
317
  url: { type: 'string' },
255
318
  method: { type: 'string' },
319
+ urlType: {
320
+ type: 'string',
321
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
322
+ description:
323
+ 'How the backend treats this URL. Default derived ' +
324
+ 'from {{placeholders}} (TEMPLATE) like a normal ' +
325
+ 'request entry.',
326
+ },
256
327
  body: {
257
328
  type: 'string',
258
329
  description:
@@ -375,6 +446,15 @@ export function createVersionFromCaptureTool(
375
446
  'Free-form JSON passed through to the client. Add-version ' +
376
447
  'ONLY.',
377
448
  },
449
+ disableRequestReplay: {
450
+ type: 'boolean',
451
+ description:
452
+ 'Skip replaying the main document request (HAWKEYE ' +
453
+ 'provider-level flag). Honored on add-version; for a ' +
454
+ 'provider (no `providerId`) the register endpoint ignores ' +
455
+ 'it, so when true the tool automatically follows the ' +
456
+ 'register up with an add-version call that applies it.',
457
+ },
378
458
  },
379
459
  required: ['initialUrl'],
380
460
  },
@@ -413,6 +493,7 @@ export function createVersionFromCaptureTool(
413
493
  stepsToFollow: args.stepsToFollow,
414
494
  useIncognitoWebview: args.useIncognitoWebview,
415
495
  extensionConfig: args.extensionConfig,
496
+ disableRequestReplay: args.disableRequestReplay,
416
497
  allowedInjectedRequestData: args.allowedInjectedRequestData
417
498
  ? injectedRequestDataFrom(
418
499
  args.allowedInjectedRequestData,
@@ -448,6 +529,12 @@ export function createVersionFromCaptureTool(
448
529
  )
449
530
  }
450
531
 
532
+ // disableRequestReplay is honored ONLY on add-version, so a
533
+ // register can't set it — when true we immediately follow the
534
+ // register with an add-version call (which also lands any other
535
+ // register-ignored fields).
536
+ const followUp = args.disableRequestReplay === true
537
+
451
538
  const REGISTER_IGNORED_KEYS = [
452
539
  'allowedInjectedRequestData',
453
540
  'useIncognitoWebview',
@@ -459,15 +546,50 @@ export function createVersionFromCaptureTool(
459
546
  notes.push(
460
547
  `${ignoredOnRegister.join(', ')} ` +
461
548
  `${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.',
549
+ 'ignored on register (no providerId) — the devtools backend only ' +
550
+ 'reads them when adding a version. ' +
551
+ (followUp
552
+ ? 'They were applied by the follow-up version the tool ' +
553
+ 'created to set disableRequestReplay.'
554
+ : 'Publish once to create the provider, then call this ' +
555
+ 'again WITH the returned providerId to set them.'),
466
556
  )
467
557
  }
468
558
 
469
559
  const body = providerToRegisterBody(providers, opts)
470
- return withNotes(await client.registerProvider(body))
560
+ const registered = await client.registerProvider(body)
561
+
562
+ if(followUp) {
563
+ const newProviderId = extractProviderId(registered)
564
+ if(newProviderId) {
565
+ const versionsResp = await client.listVersions(newProviderId)
566
+ const followVersion = bumpPatchString(
567
+ extractVersions(versionsResp),
568
+ )
569
+ const configBody = providerToConfigBody(
570
+ providers,
571
+ { ...opts, disableRequestReplay: true },
572
+ followVersion,
573
+ DEFAULT_VERSION_INFO,
574
+ )
575
+ await client.updateProviderConfig(newProviderId, configBody)
576
+ notes.push(
577
+ 'disableRequestReplay is ignored by the register endpoint — ' +
578
+ `applied it via a follow-up version (${followVersion}) on ` +
579
+ 'the new provider, which also lands any register-ignored ' +
580
+ 'fields you passed.',
581
+ )
582
+ } else {
583
+ notes.push(
584
+ 'Could not apply disableRequestReplay: the register ' +
585
+ 'response did not include the new providerId. Call this ' +
586
+ 'tool again WITH `providerId` and `disableRequestReplay: ' +
587
+ 'true` to set it.',
588
+ )
589
+ }
590
+ }
591
+
592
+ return withNotes(registered)
471
593
  }
472
594
 
473
595
  // New version of an existing provider — pick the next patch above