@reclaimprotocol/agent 0.1.17 → 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/consts.d.ts +7 -0
- package/lib/consts.js +15 -0
- package/lib/mcp/tools/agent/publish.js +33 -0
- package/lib/mcp/tools/mode/mode.d.ts +3 -2
- package/lib/mcp/tools/mode/mode.js +24 -4
- 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 +18 -4
- package/package.json +2 -2
- package/skill/builder-mode.md +2 -0
- package/skill/old-mode.md +3 -1
- package/src/consts.ts +20 -0
- package/src/mcp/tools/agent/publish.ts +42 -0
- package/src/mcp/tools/mode/mode.ts +28 -4
- package/src/old/to-register.ts +50 -27
- package/src/old/tools/publish.ts +108 -8
- package/src/provider/injection-doc.ts +4 -3
- package/src/provider/schema.ts +27 -1
- package/src/provider/to-version.ts +41 -6
|
@@ -61,13 +61,13 @@ export function providerToRequestSelection(provider) {
|
|
|
61
61
|
const responseMatches = (provider.responseMatches ?? []).map((match) => ({
|
|
62
62
|
type: match.type,
|
|
63
63
|
value: match.value,
|
|
64
|
-
isOptional: false,
|
|
64
|
+
isOptional: match.isOptional ?? false,
|
|
65
65
|
}));
|
|
66
66
|
const responseRedactions = provider.responseRedactions ?? [];
|
|
67
67
|
return {
|
|
68
68
|
url: provider.url,
|
|
69
69
|
method: provider.method,
|
|
70
|
-
credentials: 'include',
|
|
70
|
+
credentials: provider.credentials ?? 'include',
|
|
71
71
|
...(provider.body ? { requestBodyTemplate: provider.body } : {}),
|
|
72
72
|
...(provider.writeRedactionMode
|
|
73
73
|
? { writeRedactionMode: provider.writeRedactionMode }
|
|
@@ -91,7 +91,7 @@ function pickGeoLocation(providers) {
|
|
|
91
91
|
return DYNAMIC_GEO;
|
|
92
92
|
}
|
|
93
93
|
export function providerToCreateVersionRequest(providers, initialUrl, opts = {}) {
|
|
94
|
-
const { notes, version, jsUserScripts, allowedJsRequests } = opts;
|
|
94
|
+
const { notes, version, jsUserScripts, allowedJsRequests, interceptorOptions, } = opts;
|
|
95
95
|
const requiredContext = deriveRequiredContext(providers, {
|
|
96
96
|
allowedJsRequests,
|
|
97
97
|
jsUserScripts,
|
|
@@ -118,7 +118,21 @@ export function providerToCreateVersionRequest(providers, initialUrl, opts = {})
|
|
|
118
118
|
// apply schema defaults to request bodies.
|
|
119
119
|
webSettings: {
|
|
120
120
|
geoLocation: pickGeoLocation(providers),
|
|
121
|
-
clientOptions: {
|
|
121
|
+
clientOptions: {
|
|
122
|
+
portal: { useProxy: true },
|
|
123
|
+
...(interceptorOptions
|
|
124
|
+
? {
|
|
125
|
+
inapp: {
|
|
126
|
+
interceptorOptions: {
|
|
127
|
+
// openapi `default: false` — the generated type
|
|
128
|
+
// requires the key even when the author omits it.
|
|
129
|
+
isDocumentRequestReplayEnabled: false,
|
|
130
|
+
...interceptorOptions,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
},
|
|
122
136
|
// Only set when non-empty; an empty string would store a no-op script.
|
|
123
137
|
...(script ? { jsUserScripts: script } : {}),
|
|
124
138
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reclaimprotocol/agent",
|
|
3
3
|
"title": "Reclaim Agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"description": "Reclaim Protocol's tools for agents — Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
85
|
"@reclaimprotocol/attestor-core": "^5.0.8",
|
|
86
|
-
"@reclaimprotocol/client": "0.1.
|
|
86
|
+
"@reclaimprotocol/client": "0.1.18",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
package/skill/builder-mode.md
CHANGED
|
@@ -13,3 +13,5 @@ This server targets the Reclaim BUILDER backend. It exposes three tool layers: g
|
|
|
13
13
|
**Discover & resolve (find providers for the user's app):** when a dev wants to add verification but doesn't know which providers to use, run the same flow the Discover UI does. Ask which website their users prove data from, then `discover_data_points({ domain })` to list what's verifiable there. Confirm the data points with the dev, then `discover_resolve({ domain, dataPoints })` → the smallest provider set (`providers`, greedy combine), the `requiredContext` inputs to collect from the dev, and any `uncovered` points (author those with the provider-authoring flow above, then re-resolve). Create the session with `create_verification_session({ providers: [{ providerId }, …], context, verificationClientUrl })`, passing EVERY resolved provider id and the collected context. `merge.mergeable` only signals the set could collapse into one provider — you still pass them all; the Builder runs them in one session.
|
|
14
14
|
|
|
15
15
|
**Verification sessions:** consumers authenticate with a per-org token (`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create sessions via `POST /verifications/sessions`. Results are ECIES-encrypted (secp256k1 + AES-256-GCM) to the org's eth public key when its `canEncryptResult` is enabled — set via `PUT /orgs/{orgId}/keypair` with an eth public key — and delivered to the org's callback subscriptions (plaintext otherwise). Use the org/session tools (`get_verification_session`, `list_verification_events`, `list_org_verification_sessions`) to troubleshoot.
|
|
16
|
+
|
|
17
|
+
**Backend mode:** this server can alternatively run against the devtools backend with a reduced tool set (no generated API tools, no orgs/review flows). `get_devtools_mode` reports the active mode and why it's in effect; `set_devtools_mode` persists a switch to `~/.reclaim/config.json` — it takes effect on the NEXT server start (restart/reconnect), and an explicit `USE_OLD_DEVTOOLS` env var overrides the persisted choice.
|
package/skill/old-mode.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
This server is running against the
|
|
1
|
+
This server is running against the devtools backend, so its capabilities are intentionally limited.
|
|
2
2
|
|
|
3
3
|
**Provider-authoring flow:**
|
|
4
4
|
|
|
@@ -15,3 +15,5 @@ Only GET/POST endpoints can be captured into providers here. There is no organiz
|
|
|
15
15
|
**Diagnosing a session:** `session_analytics_logs` fetches the analytics logs for a verification session by id (from the logs service) — use it to see what happened in a session (matches, claims, failures).
|
|
16
16
|
|
|
17
17
|
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) use the `{{context_<name>}}` form — this backend does not support the dotted `{{context.<name>}}` interpolation the new builder uses.
|
|
18
|
+
|
|
19
|
+
**Backend mode:** this is the reduced devtools surface — the BUILDER backend (generated API tools, orgs, verification sessions, public review) is one switch away. `get_devtools_mode` reports the active mode and why it's in effect; `set_devtools_mode` persists a switch to `~/.reclaim/config.json` — it takes effect on the NEXT server start (restart/reconnect), and an explicit `USE_OLD_DEVTOOLS` env var overrides the persisted choice.
|
package/src/consts.ts
CHANGED
|
@@ -96,6 +96,26 @@ export function resolveOldMode(persisted?: 'old' | 'builder'): boolean {
|
|
|
96
96
|
return true
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Where the effective mode came from — the same precedence as
|
|
101
|
+
* `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
|
|
102
|
+
* `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
|
|
103
|
+
* user can tell why the server is in the mode it reports.
|
|
104
|
+
*/
|
|
105
|
+
export function oldModeSource(
|
|
106
|
+
persisted?: 'old' | 'builder',
|
|
107
|
+
): 'env' | 'config' | 'default' {
|
|
108
|
+
if(oldDevtoolsEnvSet()) {
|
|
109
|
+
return 'env'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(persisted) {
|
|
113
|
+
return 'config'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return 'default'
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
// ---------------------------------------------------------------------------
|
|
100
120
|
// Chrome / CDP launcher.
|
|
101
121
|
// ---------------------------------------------------------------------------
|
|
@@ -24,6 +24,12 @@ const RESPONSE_MATCHES_SCHEMA: JSONSchema.Schema = {
|
|
|
24
24
|
properties: {
|
|
25
25
|
type: { type: 'string', enum: ['regex', 'contains'] },
|
|
26
26
|
value: { type: 'string' },
|
|
27
|
+
isOptional: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
description:
|
|
30
|
+
'Mark this match non-fatal (verification continues when it ' +
|
|
31
|
+
'fails). Default false.',
|
|
32
|
+
},
|
|
27
33
|
},
|
|
28
34
|
required: ['type', 'value'],
|
|
29
35
|
},
|
|
@@ -59,6 +65,13 @@ const REQUEST_DRAFT_PROPERTIES: Record<string, JSONSchema.Schema> = {
|
|
|
59
65
|
},
|
|
60
66
|
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH'] },
|
|
61
67
|
body: { type: 'string', description: 'Request body template, if any.' },
|
|
68
|
+
credentials: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
enum: ['omit', 'same-origin', 'include'],
|
|
71
|
+
description:
|
|
72
|
+
'How the verification client supplies cookies/auth when replaying ' +
|
|
73
|
+
'this request. Default `include`.',
|
|
74
|
+
},
|
|
62
75
|
responseMatches: RESPONSE_MATCHES_SCHEMA,
|
|
63
76
|
responseRedactions: RESPONSE_REDACTIONS_SCHEMA,
|
|
64
77
|
}
|
|
@@ -120,6 +133,11 @@ interface PublishArgs {
|
|
|
120
133
|
version?: { major: number, minor: number, patch: number }
|
|
121
134
|
jsUserScripts?: string
|
|
122
135
|
allowedJsRequests?: AllowedJsRequest[]
|
|
136
|
+
interceptorOptions?: {
|
|
137
|
+
interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP'
|
|
138
|
+
isDocumentRequestReplayEnabled?: boolean
|
|
139
|
+
interceptorSettings?: string
|
|
140
|
+
}
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
export function createVersionFromCaptureTool(
|
|
@@ -219,6 +237,29 @@ export function createVersionFromCaptureTool(
|
|
|
219
237
|
'`multiple`/`required` default to true.',
|
|
220
238
|
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
221
239
|
},
|
|
240
|
+
interceptorOptions: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
description:
|
|
243
|
+
'In-app interception settings, stored as ' +
|
|
244
|
+
'webSettings.clientOptions.inapp.interceptorOptions. ' +
|
|
245
|
+
'`isDocumentRequestReplayEnabled` is the INVERSE of ' +
|
|
246
|
+
'old-devtools\' `disableRequestReplay`: true replays the ' +
|
|
247
|
+
'main document request via the attestor (default false).',
|
|
248
|
+
properties: {
|
|
249
|
+
interceptorType: {
|
|
250
|
+
type: 'string',
|
|
251
|
+
enum: ['HAWKEYE', 'MSWJS', 'CDP'],
|
|
252
|
+
description: 'How traffic interception is implemented.',
|
|
253
|
+
},
|
|
254
|
+
isDocumentRequestReplayEnabled: { type: 'boolean' },
|
|
255
|
+
interceptorSettings: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
description:
|
|
258
|
+
'Free-form JSON config for the chosen interceptor.',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
required: ['interceptorType'],
|
|
262
|
+
},
|
|
222
263
|
},
|
|
223
264
|
required: ['providerId', 'initialUrl'],
|
|
224
265
|
},
|
|
@@ -244,6 +285,7 @@ export function createVersionFromCaptureTool(
|
|
|
244
285
|
version: ver,
|
|
245
286
|
jsUserScripts: args.jsUserScripts,
|
|
246
287
|
allowedJsRequests: args.allowedJsRequests,
|
|
288
|
+
interceptorOptions: args.interceptorOptions,
|
|
247
289
|
})
|
|
248
290
|
const result = await client.call('CreateProviderVersion', {
|
|
249
291
|
params: { providerId: args.providerId },
|
|
@@ -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`
|
|
7
|
-
* (
|
|
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" (
|
|
29
|
-
+ '
|
|
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
|
}
|
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
|
|
@@ -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'
|