@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.
- package/lib/authoring/index.d.ts +1 -1
- package/lib/authoring/index.js +1 -1
- package/lib/authoring/util.js +3 -0
- package/lib/config/store.d.ts +7 -0
- package/lib/consts.d.ts +15 -0
- package/lib/consts.js +35 -0
- package/lib/index.js +1 -1
- package/lib/mcp/api/operations.gen.d.ts +8 -16
- package/lib/mcp/api/operations.gen.js +428 -479
- package/lib/mcp/remap_errors.js +7 -14
- package/lib/mcp/start.js +10 -3
- package/lib/mcp/tools/agent/proof.js +3 -3
- package/lib/mcp/tools/agent/publish.js +33 -0
- package/lib/mcp/tools/authenticate/authenticate.js +178 -93
- package/lib/mcp/tools/authenticate/eth-key.js +2 -3
- package/lib/mcp/tools/index.d.ts +1 -0
- package/lib/mcp/tools/index.js +1 -0
- package/lib/mcp/tools/mode/index.d.ts +1 -0
- package/lib/mcp/tools/mode/index.js +3 -0
- package/lib/mcp/tools/mode/mode.d.ts +17 -0
- package/lib/mcp/tools/mode/mode.js +69 -0
- package/lib/old/to-register.d.ts +16 -8
- package/lib/old/to-register.js +29 -21
- package/lib/old/tools/publish.js +86 -8
- package/lib/provider/injection-doc.js +4 -3
- package/lib/provider/schema.d.ts +26 -1
- package/lib/provider/to-version.d.ts +19 -2
- package/lib/provider/to-version.js +22 -4
- package/lib/tools/registry.d.ts +6 -13
- package/lib/tools/registry.js +29 -0
- package/package.json +5 -8
- package/scripts/prepend-shebang.js +1 -1
- package/skill/builder-mode.md +2 -0
- package/skill/old-mode.md +3 -1
- package/src/authoring/index.ts +1 -0
- package/src/authoring/util.ts +3 -0
- package/src/cdp/launcher.ts +1 -1
- package/src/config/store.ts +8 -0
- package/src/consts.ts +44 -0
- package/src/index.ts +1 -1
- package/src/mcp/api/operations.gen.ts +430 -487
- package/src/mcp/remap_errors.ts +8 -15
- package/src/mcp/start.ts +11 -2
- package/src/mcp/tools/agent/proof.ts +3 -3
- package/src/mcp/tools/agent/publish.ts +42 -0
- package/src/mcp/tools/authenticate/authenticate.ts +238 -108
- package/src/mcp/tools/authenticate/eth-key.ts +7 -5
- package/src/mcp/tools/index.ts +1 -0
- package/src/mcp/tools/mode/index.ts +3 -0
- package/src/mcp/tools/mode/mode.ts +82 -0
- package/src/old/to-register.ts +50 -27
- package/src/old/tools/publish.ts +108 -8
- package/src/proof/debug-response-dump.ts +1 -1
- package/src/proof/proof-core.ts +1 -1
- package/src/proof/zk-files.ts +2 -2
- package/src/provider/dom-xpath.ts +1 -1
- package/src/provider/draft.ts +3 -3
- package/src/provider/injection-doc.ts +4 -3
- package/src/provider/schema.ts +27 -1
- package/src/provider/to-version.ts +45 -6
- package/src/tools/registry.ts +19 -0
- package/src/cli/args.ts +0 -20
- package/src/cli/commands.ts +0 -27
- 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
|
+
}
|
package/src/old/to-register.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
-
* -
|
|
23
|
+
* - devtools keeps `responseMatches` and `responseRedactions` as
|
|
23
24
|
* PARALLEL arrays on each `requestData` entry.
|
|
24
25
|
*
|
|
25
|
-
*
|
|
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 —
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
170
|
-
//
|
|
171
|
-
urlType: provider.
|
|
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
|
|
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
|
|
191
|
-
* (the same way the builder version carries multiple
|
|
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
|
|
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
|
|
329
|
+
/** Minimal version triplet as the devtools backend stores it. */
|
|
307
330
|
export interface SemverTriplet {
|
|
308
331
|
major: number
|
|
309
332
|
minor: number
|
package/src/old/tools/publish.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
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.
|
|
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
|
|
463
|
-
'reads them when adding a version.
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
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()
|
|
29
|
+
export function installResponseDump() {
|
|
30
30
|
if(installed || !process.env['RECLAIM_AGENT_DUMP_RESPONSE']) {
|
|
31
31
|
return
|
|
32
32
|
}
|
package/src/proof/proof-core.ts
CHANGED
|
@@ -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()
|
|
23
|
+
export function ensureAttestorCrypto() {
|
|
24
24
|
if(cryptoInitialized) {
|
|
25
25
|
return
|
|
26
26
|
}
|
package/src/proof/zk-files.ts
CHANGED
|
@@ -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()
|
|
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()
|
|
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)
|
|
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)
|
package/src/provider/draft.ts
CHANGED
|
@@ -160,7 +160,7 @@ export function assertPortableMatches(
|
|
|
160
160
|
matches: ResponseMatch[],
|
|
161
161
|
redactions: ResponseRedaction[],
|
|
162
162
|
targets: DraftTarget[],
|
|
163
|
-
)
|
|
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)
|
|
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)
|
|
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
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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'
|
package/src/provider/schema.ts
CHANGED
|
@@ -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:
|
|
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'). */
|