@reclaimprotocol/agent 0.1.29 → 0.1.31
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/README.md +28 -9
- package/lib/consts.d.ts +5 -5
- package/lib/consts.js +7 -7
- package/lib/live/tunnel-watchdog.d.ts +1 -0
- package/lib/live/tunnel-watchdog.js +66 -0
- package/lib/live/tunnel.d.ts +30 -7
- package/lib/live/tunnel.js +217 -40
- package/lib/mcp/api/operations.gen.d.ts +12 -0
- package/lib/mcp/api/operations.gen.js +537 -59
- package/lib/mcp/api/tools.js +27 -5
- package/lib/mcp/instructions.js +23 -32
- package/lib/mcp/remap_errors.js +6 -0
- package/lib/mcp/server.js +2 -8
- package/lib/mcp/start.js +3 -5
- package/lib/mcp/success-response.d.ts +5 -0
- package/lib/mcp/success-response.js +21 -0
- package/lib/mcp/tools/agent/attach.js +9 -66
- package/lib/mcp/tools/agent/build.d.ts +0 -3
- package/lib/mcp/tools/agent/build.js +3 -4
- package/lib/mcp/tools/agent/how-it-works.d.ts +5 -15
- package/lib/mcp/tools/agent/how-it-works.js +9 -22
- package/lib/mcp/tools/agent/live-view.d.ts +1 -15
- package/lib/mcp/tools/agent/live-view.js +71 -96
- package/lib/mcp/tools/agent/propose.js +7 -23
- package/lib/mcp/tools/agent/publish.js +80 -18
- package/lib/mcp/tools/agent/session.d.ts +4 -7
- package/lib/mcp/tools/agent/session.js +8 -23
- package/lib/mcp/tools/verification/result.d.ts +2 -1
- package/lib/mcp/tools/verification/result.js +29 -28
- package/lib/old/client.d.ts +2 -2
- package/lib/old/flag.d.ts +2 -5
- package/lib/old/flag.js +2 -5
- package/lib/old/instructions.d.ts +1 -1
- package/lib/old/instructions.js +1 -1
- package/lib/old/to-register.d.ts +4 -3
- package/lib/old/tools/publish.js +11 -14
- package/lib/proof/attestor-auth.d.ts +9 -1
- package/lib/proof/attestor-auth.js +15 -2
- package/lib/proof/attestor.d.ts +2 -2
- package/lib/proof/attestor.js +20 -2
- package/lib/proof/pod-prover.js +1 -1
- package/lib/proof/proof-core.d.ts +4 -0
- package/lib/proof/proof-core.js +20 -0
- package/lib/provider/injection-doc.d.ts +3 -2
- package/lib/provider/injection-doc.js +12 -10
- package/lib/provider/matcher.js +19 -15
- package/lib/provider/to-version.d.ts +3 -0
- package/lib/provider/to-version.js +2 -1
- package/package.json +3 -3
- package/skill/SKILL.builder.md +30 -83
- package/skill/SKILL.common.md +38 -22
- package/skill/SKILL.old.md +17 -3
- package/skill/builder-mode.md +37 -4
- package/skill/community-contributions.md +12 -3
- package/skill/hash-validation.md +9 -9
- package/skill/interception.md +39 -46
- package/skill/old-mode.md +13 -0
- package/skill/user-script.md +24 -8
- package/skill/verification-client-integration.md +130 -0
- package/src/consts.ts +9 -7
- package/src/mcp/api/operations.gen.ts +539 -59
- package/src/mcp/api/tools.ts +38 -7
- package/src/mcp/instructions.ts +33 -32
- package/src/mcp/remap_errors.ts +6 -0
- package/src/mcp/server.ts +2 -8
- package/src/mcp/start.ts +3 -5
- package/src/mcp/success-response.ts +26 -0
- package/src/mcp/tools/agent/attach.ts +9 -71
- package/src/mcp/tools/agent/build.ts +4 -7
- package/src/mcp/tools/agent/how-it-works.ts +9 -22
- package/src/mcp/tools/agent/live-view.ts +76 -116
- package/src/mcp/tools/agent/propose.ts +7 -23
- package/src/mcp/tools/agent/publish.ts +89 -18
- package/src/mcp/tools/agent/session.ts +6 -23
- package/src/mcp/tools/verification/result.ts +30 -29
- package/src/old/client.ts +2 -2
- package/src/old/flag.ts +2 -5
- package/src/old/instructions.ts +1 -1
- package/src/old/to-register.ts +4 -3
- package/src/old/tools/publish.ts +16 -14
- package/src/proof/attestor-auth.ts +18 -1
- package/src/proof/attestor.ts +24 -3
- package/src/proof/pod-prover.ts +1 -1
- package/src/proof/proof-core.ts +28 -0
- package/src/provider/injection-doc.ts +12 -10
- package/src/provider/matcher.ts +25 -17
- package/src/provider/to-version.ts +7 -1
- package/src/live/cloudflared.ts +0 -348
- package/src/live/tunnel.ts +0 -254
package/lib/old/flag.js
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* `USE_OLD_DEVTOOLS` — single source of truth for which backend the MCP
|
|
3
3
|
* server targets.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* EXPLICITLY by setting `USE_OLD_DEVTOOLS=false`. Anything else — unset,
|
|
8
|
-
* empty, `true`, `1`, a typo — keeps old mode on, so a forgotten env var
|
|
9
|
-
* can never silently route a developer at the half-built builder backend.
|
|
5
|
+
* Builder is the default. Only `true` or `1` explicitly selects old devtools;
|
|
6
|
+
* an unset value or typo stays on Builder.
|
|
10
7
|
*
|
|
11
8
|
* The actual env read lives in `../consts.ts` (the single env-access module);
|
|
12
9
|
* this widely-imported path re-exports it for back-compat.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP server instructions shown to the LLM when running in OLD-devtools mode
|
|
3
|
-
* (`USE_OLD_DEVTOOLS`
|
|
3
|
+
* (`USE_OLD_DEVTOOLS=true` or `1`). Describes the REDUCED tool
|
|
4
4
|
* surface — there are no org/credential-link/review flows and no generated
|
|
5
5
|
* API tools here. Both modes' instructions load together in
|
|
6
6
|
* `mcp/instructions` (alongside the topic-split authoring guide); re-exported
|
package/lib/old/instructions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP server instructions shown to the LLM when running in OLD-devtools mode
|
|
3
|
-
* (`USE_OLD_DEVTOOLS`
|
|
3
|
+
* (`USE_OLD_DEVTOOLS=true` or `1`). Describes the REDUCED tool
|
|
4
4
|
* surface — there are no org/credential-link/review flows and no generated
|
|
5
5
|
* API tools here. Both modes' instructions load together in
|
|
6
6
|
* `mcp/instructions` (alongside the topic-split authoring guide); re-exported
|
package/lib/old/to-register.d.ts
CHANGED
|
@@ -61,9 +61,10 @@ export interface OldProviderOpts {
|
|
|
61
61
|
* .interceptorOptions.isDocumentRequestReplayEnabled`, is likewise always
|
|
62
62
|
* force-disabled by `providerToCreateVersionRequest`. */
|
|
63
63
|
disableRequestReplay?: boolean;
|
|
64
|
-
/** Interception mechanism
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
/** Interception mechanism. Omit it to use the backend's `HAWKEYE` default;
|
|
65
|
+
* use `NONE` only when disabling interception after diagnosis.
|
|
66
|
+
* `HAWKEYE`/`MSWJS`/`XHOOK`/`CDP` enable live interception. Old-devtools
|
|
67
|
+
* calls this
|
|
67
68
|
* `injectionType`; builder's equivalent is `webSettings.clientOptions
|
|
68
69
|
* .inapp.interceptorOptions.interceptorType` (no `NONE` value there —
|
|
69
70
|
* omitting the whole object is builder's no-interception signal
|
package/lib/old/tools/publish.js
CHANGED
|
@@ -373,20 +373,12 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
373
373
|
injectionType: {
|
|
374
374
|
type: 'string',
|
|
375
375
|
enum: ['NONE', 'MSWJS', 'XHOOK', 'CDP', 'HAWKEYE'],
|
|
376
|
-
description: '
|
|
377
|
-
'
|
|
378
|
-
'
|
|
379
|
-
'
|
|
380
|
-
'
|
|
381
|
-
|
|
382
|
-
'`window.reclaimInterceptor.addResponseMiddleware(...)` for ' +
|
|
383
|
-
'genuine live interception, or set `NONE` explicitly to be ' +
|
|
384
|
-
'certain none is configured. Builder calls the equivalent ' +
|
|
385
|
-
'concept `interceptorType` (no `NONE` there — omitting the ' +
|
|
386
|
-
'whole `interceptorOptions` object is its no-interception ' +
|
|
387
|
-
'signal instead). ' + COOKIE_ATTACHMENT_HINT +
|
|
388
|
-
'What each value actually does: ' +
|
|
389
|
-
'how_it_works({ topic: "interception" }).',
|
|
376
|
+
description: 'Live-interception mechanism. Omit it for the standard flow. ' +
|
|
377
|
+
'Use `NONE` only to confirm a suspected interception problem; ' +
|
|
378
|
+
'if that fixes the provider, keep `NONE` and use ' +
|
|
379
|
+
'`window.Reclaim.requestClaim(...)`. For selection rules, call ' +
|
|
380
|
+
'how_it_works({ topic: "interception" }). ' +
|
|
381
|
+
COOKIE_ATTACHMENT_HINT,
|
|
390
382
|
},
|
|
391
383
|
disableRequestReplay: {
|
|
392
384
|
type: 'boolean',
|
|
@@ -407,6 +399,11 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
407
399
|
assert(!(args.version && args.bump), badRequest('Pass either `version` or `bump`, not both.'));
|
|
408
400
|
assert(args.providerId || (!args.version && !args.bump), badRequest('`version` and `bump` require `providerId`; new-provider ' +
|
|
409
401
|
'registration starts at the backend-defined version.'));
|
|
402
|
+
assert(args.injectionType !== 'NONE'
|
|
403
|
+
|| !args.customInjection?.includes('addResponseMiddleware'), badRequest('`injectionType: NONE` cannot deliver ' +
|
|
404
|
+
'`addResponseMiddleware` traffic. Use an interceptor type, or ' +
|
|
405
|
+
'keep `NONE` and replace intercepted requests with ' +
|
|
406
|
+
'`window.Reclaim.requestClaim(...)`.'));
|
|
410
407
|
// `provider` / `providers` are just two spellings of the same input —
|
|
411
408
|
// fold them into one array. Zero drafts is allowed ONLY when adding a
|
|
412
409
|
// version to an EXISTING provider (`providerId` set): that publishes
|
|
@@ -9,5 +9,13 @@ export interface AttestorAuthentication {
|
|
|
9
9
|
/**
|
|
10
10
|
* Create legacy-compatible attestor authorization when the deployment has an
|
|
11
11
|
* auth private key. An absent key intentionally means authentication is off.
|
|
12
|
+
*
|
|
13
|
+
* `expiresInSeconds` overrides the attestor library's default lifetime. A
|
|
14
|
+
* zkFetch authorization uses a very short one: the attestor validates expiry
|
|
15
|
+
* once, when it authenticates the connection, and never again for the life of
|
|
16
|
+
* that connection — so the window only has to cover the caller receiving the
|
|
17
|
+
* token and opening its socket, not the proof that follows.
|
|
12
18
|
*/
|
|
13
|
-
export declare function createAttestorAuthentication(sessionId: string, privateKey: string | undefined
|
|
19
|
+
export declare function createAttestorAuthentication(sessionId: string, privateKey: string | undefined, opts?: {
|
|
20
|
+
expiresInSeconds?: number;
|
|
21
|
+
}): Promise<AttestorAuthentication | undefined>;
|
|
@@ -2,12 +2,25 @@ import { createAuthRequest } from '@reclaimprotocol/attestor-core';
|
|
|
2
2
|
/**
|
|
3
3
|
* Create legacy-compatible attestor authorization when the deployment has an
|
|
4
4
|
* auth private key. An absent key intentionally means authentication is off.
|
|
5
|
+
*
|
|
6
|
+
* `expiresInSeconds` overrides the attestor library's default lifetime. A
|
|
7
|
+
* zkFetch authorization uses a very short one: the attestor validates expiry
|
|
8
|
+
* once, when it authenticates the connection, and never again for the life of
|
|
9
|
+
* that connection — so the window only has to cover the caller receiving the
|
|
10
|
+
* token and opening its socket, not the proof that follows.
|
|
5
11
|
*/
|
|
6
|
-
export async function createAttestorAuthentication(sessionId, privateKey) {
|
|
12
|
+
export async function createAttestorAuthentication(sessionId, privateKey, opts = {}) {
|
|
7
13
|
if (!privateKey?.trim()) {
|
|
8
14
|
return undefined;
|
|
9
15
|
}
|
|
10
|
-
const request = await createAuthRequest({
|
|
16
|
+
const request = await createAuthRequest({
|
|
17
|
+
id: sessionId,
|
|
18
|
+
...(opts.expiresInSeconds === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: {
|
|
21
|
+
expiresAt: Math.floor(Date.now() / 1000) + opts.expiresInSeconds,
|
|
22
|
+
}),
|
|
23
|
+
}, privateKey);
|
|
11
24
|
const token = Buffer.from(JSON.stringify({
|
|
12
25
|
...request,
|
|
13
26
|
signature: Buffer.from(request.signature).toString('base64'),
|
package/lib/proof/attestor.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface RunAttestorProofOptions {
|
|
|
36
36
|
* want gnark's speed pin it explicitly (for example, the verify runner). */
|
|
37
37
|
zkEngine?: 'gnark' | 'snarkjs' | 'stwo';
|
|
38
38
|
/** Extract the structured {@link AttestorClaim} (claim + attestor signature)
|
|
39
|
-
* onto the result for
|
|
39
|
+
* onto the result for Proof construction. */
|
|
40
40
|
includeAttestorClaim?: boolean;
|
|
41
41
|
}
|
|
42
42
|
export interface ProofResult {
|
|
@@ -47,7 +47,7 @@ export interface ProofResult {
|
|
|
47
47
|
owner?: string;
|
|
48
48
|
verified: boolean;
|
|
49
49
|
error?: string;
|
|
50
|
-
/** Structured claim + signature for
|
|
50
|
+
/** Structured claim + signature for Proof verification. Set only when
|
|
51
51
|
* `includeAttestorClaim` was requested and the claim carries signatures. */
|
|
52
52
|
attestorClaim?: AttestorClaim;
|
|
53
53
|
/** Set to `'missing-owner-key'` when no credential was available to
|
package/lib/proof/attestor.js
CHANGED
|
@@ -14,7 +14,7 @@ import "./patch-domhandler.js";
|
|
|
14
14
|
import { agentTraceEnabled } from "../consts.js";
|
|
15
15
|
import { resolveEthKey } from "../mcp/tools/authenticate/eth-key.js";
|
|
16
16
|
import { installResponseDump } from "./debug-response-dump.js";
|
|
17
|
-
import { buildHttpParams, ensureAttestorCrypto, parseClaim, resolveAttestorUrl } from "./proof-core.js";
|
|
17
|
+
import { buildHttpParams, claimRejection, ensureAttestorCrypto, parseClaim, resolveAttestorUrl } from "./proof-core.js";
|
|
18
18
|
import { ensureZkFiles } from "./zk-files.js";
|
|
19
19
|
/** Trim a captured log stream to the last N entries — for shrinking the
|
|
20
20
|
* LLM-visible response while the full stream stays in the on-disk dump. */
|
|
@@ -180,6 +180,24 @@ export async function runAttestorProof(provider, secrets, opts = {}) {
|
|
|
180
180
|
}
|
|
181
181
|
return errorResult;
|
|
182
182
|
}
|
|
183
|
+
// The attestor reports a rejected claim (for example, an invalid ZK proof)
|
|
184
|
+
// in the response rather than by throwing, so a returned value alone is
|
|
185
|
+
// not a proof: it needs a claim, a claim signature, and no error.
|
|
186
|
+
const rejection = claimRejection(claim);
|
|
187
|
+
if (rejection !== undefined) {
|
|
188
|
+
const rejected = {
|
|
189
|
+
proof: claim,
|
|
190
|
+
extractedValue: '',
|
|
191
|
+
extractedParameters: {},
|
|
192
|
+
verified: false,
|
|
193
|
+
error: rejection,
|
|
194
|
+
sentParams: params,
|
|
195
|
+
};
|
|
196
|
+
if (attestorLogs !== undefined) {
|
|
197
|
+
rejected.attestorLogs = attestorLogs;
|
|
198
|
+
}
|
|
199
|
+
return rejected;
|
|
200
|
+
}
|
|
183
201
|
const { extractedValue, extractedParameters, identifier, owner, claimData } = parseClaim(claim);
|
|
184
202
|
const result = {
|
|
185
203
|
proof: claim,
|
|
@@ -194,7 +212,7 @@ export async function runAttestorProof(provider, secrets, opts = {}) {
|
|
|
194
212
|
result.owner = owner;
|
|
195
213
|
}
|
|
196
214
|
// Structured claim + signature so the result JWS can carry the exact
|
|
197
|
-
//
|
|
215
|
+
// Proof the Consumer verifies independently.
|
|
198
216
|
if (opts.includeAttestorClaim) {
|
|
199
217
|
const sigs = claim.signatures;
|
|
200
218
|
if (claimData
|
package/lib/proof/pod-prover.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* The pod API is unauthenticated (gateway/session is the trust boundary); call
|
|
28
28
|
* it over the same internal host used for CDP. Consumers verify
|
|
29
29
|
* `claim_signature` with the legacy SDK's `verifyProof`, so this hands back an
|
|
30
|
-
* {@link AttestorClaim} that the app places in the exact
|
|
30
|
+
* {@link AttestorClaim} that the app places in the exact `Proof` shape.
|
|
31
31
|
* `result_signature` is unused by consumers.
|
|
32
32
|
*/
|
|
33
33
|
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
@@ -29,6 +29,10 @@ export interface ParsedClaim {
|
|
|
29
29
|
identifier?: string;
|
|
30
30
|
owner?: string;
|
|
31
31
|
}
|
|
32
|
+
/** Why the attestor did not sign `claim`, or `undefined` when it did. The
|
|
33
|
+
* attestor returns rejections (for example, an invalid ZK proof) as a
|
|
34
|
+
* response with `error` set and no claim signature, not by throwing. */
|
|
35
|
+
export declare function claimRejection(claim: unknown): string | undefined;
|
|
32
36
|
/** Pull claimData + decoded `context.extractedParameters` (plus identifier /
|
|
33
37
|
* owner) out of a `createClaimOnAttestor` result. The full result still
|
|
34
38
|
* carries signatures the caller may need for nested-proof building. */
|
package/lib/proof/proof-core.js
CHANGED
|
@@ -46,6 +46,26 @@ export function buildHttpParams(provider) {
|
|
|
46
46
|
...(geoLocation !== undefined && { geoLocation }),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/** Why the attestor did not sign `claim`, or `undefined` when it did. The
|
|
50
|
+
* attestor returns rejections (for example, an invalid ZK proof) as a
|
|
51
|
+
* response with `error` set and no claim signature, not by throwing. */
|
|
52
|
+
export function claimRejection(claim) {
|
|
53
|
+
const response = claim;
|
|
54
|
+
const error = response?.error;
|
|
55
|
+
if (error?.message || error?.code) {
|
|
56
|
+
return typeof error.message === 'string' && error.message
|
|
57
|
+
? error.message
|
|
58
|
+
: `attestor error code ${String(error.code)}`;
|
|
59
|
+
}
|
|
60
|
+
if (!response?.claim) {
|
|
61
|
+
return 'attestor returned no claim';
|
|
62
|
+
}
|
|
63
|
+
const signature = response.signatures?.claimSignature;
|
|
64
|
+
if (!(signature instanceof Uint8Array) || !signature.length) {
|
|
65
|
+
return 'attestor did not sign the claim';
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
49
69
|
/** Pull claimData + decoded `context.extractedParameters` (plus identifier /
|
|
50
70
|
* owner) out of a `createClaimOnAttestor` result. The full result still
|
|
51
71
|
* carries signatures the caller may need for nested-proof building. */
|
|
@@ -20,6 +20,7 @@ export declare const COOKIE_ATTACHMENT_HINT: string;
|
|
|
20
20
|
export declare const USER_SCRIPT_HINT: string;
|
|
21
21
|
/** Prefixes the `allowedJsRequests` / `allowedInjectedRequestData` field. The
|
|
22
22
|
* trap it describes is silent and total — a mis-declared entry makes
|
|
23
|
-
* `
|
|
24
|
-
* itself stays in the schema even though its explanation moved to a
|
|
23
|
+
* `verifyRequestProof({ providerId })` reject EVERY real proof — so the
|
|
24
|
+
* warning itself stays in the schema even though its explanation moved to a
|
|
25
|
+
* topic. */
|
|
25
26
|
export declare const ALLOWED_REQUESTS_HINT: string;
|
|
@@ -28,16 +28,18 @@ export const USER_SCRIPT_HINT = 'Plain JavaScript injected into the provider pag
|
|
|
28
28
|
+ 'Full guide: how_it_works({ topic: "user-script" }).';
|
|
29
29
|
/** Prefixes the `allowedJsRequests` / `allowedInjectedRequestData` field. The
|
|
30
30
|
* trap it describes is silent and total — a mis-declared entry makes
|
|
31
|
-
* `
|
|
32
|
-
* itself stays in the schema even though its explanation moved to a
|
|
31
|
+
* `verifyRequestProof({ providerId })` reject EVERY real proof — so the
|
|
32
|
+
* warning itself stays in the schema even though its explanation moved to a
|
|
33
|
+
* topic. */
|
|
33
34
|
export const ALLOWED_REQUESTS_HINT = '⚠️ Only populate this if you intend to gate '
|
|
34
|
-
+ '`
|
|
35
|
-
+ 'hash-equality check and its `url` is hashed VERBATIM (no
|
|
36
|
-
+ 'resolution, even with urlType REGEX/TEMPLATE). If the
|
|
37
|
-
+ 'carries ANY per-session part (nonce, CSRF token,
|
|
38
|
-
+ 'timestamp), no declared spec can ever hash-match a real
|
|
39
|
-
+ 'populating it then makes
|
|
40
|
-
+ 'worse than leaving it empty. Diff the same endpoint\'s
|
|
41
|
-
+ 'across two sessions first. Mechanics, the
|
|
35
|
+
+ '`verifyRequestProof(proof, { providerId })`: each entry feeds a '
|
|
36
|
+
+ 'client-side hash-equality check and its `url` is hashed VERBATIM (no '
|
|
37
|
+
+ 'regex/template resolution, even with urlType REGEX/TEMPLATE). If the '
|
|
38
|
+
+ 'real captured URL carries ANY per-session part (nonce, CSRF token, '
|
|
39
|
+
+ 'cache-busting timestamp), no declared spec can ever hash-match a real '
|
|
40
|
+
+ 'proof — populating it then makes verifyRequestProof reject every real '
|
|
41
|
+
+ 'proof, which is worse than leaving it empty. Diff the same endpoint\'s '
|
|
42
|
+
+ 'captured URL across two sessions first. Mechanics, the '
|
|
43
|
+
+ 'body-templating rules and the '
|
|
42
44
|
+ 'dangerouslyDisableContentValidation fallback: '
|
|
43
45
|
+ 'how_it_works({ topic: "hash-validation" }).';
|
package/lib/provider/matcher.js
CHANGED
|
@@ -237,42 +237,46 @@ function findJsonPath(body, target) {
|
|
|
237
237
|
return undefined;
|
|
238
238
|
}
|
|
239
239
|
const candidates = normalizeCandidates(target);
|
|
240
|
-
|
|
240
|
+
const paths = [];
|
|
241
|
+
collectJsonPaths(parsed, '$', candidates, paths);
|
|
242
|
+
if (paths.length > 1) {
|
|
243
|
+
throw new Error(`target value "${target}" occurs at multiple JSON paths `
|
|
244
|
+
+ `(${paths.join(', ')}) — choose a value that identifies one field`);
|
|
245
|
+
}
|
|
246
|
+
return paths[0];
|
|
241
247
|
}
|
|
242
|
-
function
|
|
248
|
+
function collectJsonPaths(node, path, candidates, paths) {
|
|
249
|
+
// Two paths are enough to prove ambiguity and keep traversal bounded for a
|
|
250
|
+
// response containing the same primitive many times.
|
|
251
|
+
if (paths.length > 1) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
243
254
|
if (node === null || node === undefined) {
|
|
244
|
-
return
|
|
255
|
+
return;
|
|
245
256
|
}
|
|
246
257
|
if (typeof node === 'string' ||
|
|
247
258
|
typeof node === 'number' ||
|
|
248
259
|
typeof node === 'boolean') {
|
|
249
260
|
const str = String(node);
|
|
250
261
|
if (candidates.includes(str)) {
|
|
251
|
-
|
|
262
|
+
paths.push(path);
|
|
252
263
|
}
|
|
253
|
-
return
|
|
264
|
+
return;
|
|
254
265
|
}
|
|
255
266
|
if (Array.isArray(node)) {
|
|
256
267
|
for (const [i, element] of node.entries()) {
|
|
257
|
-
|
|
258
|
-
if (found) {
|
|
259
|
-
return found;
|
|
260
|
-
}
|
|
268
|
+
collectJsonPaths(element, `${path}[${i}]`, candidates, paths);
|
|
261
269
|
}
|
|
262
|
-
return
|
|
270
|
+
return;
|
|
263
271
|
}
|
|
264
272
|
if (typeof node === 'object') {
|
|
265
273
|
for (const [k, v] of Object.entries(node)) {
|
|
266
274
|
const childPath = /^[A-Za-z_][A-Za-z0-9_]*$/.test(k)
|
|
267
275
|
? `${path}.${k}`
|
|
268
276
|
: `${path}["${k}"]`;
|
|
269
|
-
|
|
270
|
-
if (found) {
|
|
271
|
-
return found;
|
|
272
|
-
}
|
|
277
|
+
collectJsonPaths(v, childPath, candidates, paths);
|
|
273
278
|
}
|
|
274
279
|
}
|
|
275
|
-
return undefined;
|
|
276
280
|
}
|
|
277
281
|
// ---------------------------------------------------------------------------
|
|
278
282
|
// HTML: element → xPath redaction + `contains "<tag …>{{v}}</tag>"`
|
|
@@ -57,6 +57,9 @@ export interface CreateVersionOptions {
|
|
|
57
57
|
jsUserScripts?: string;
|
|
58
58
|
/** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
|
|
59
59
|
allowedJsRequests?: AllowedJsRequest[];
|
|
60
|
+
/** Portal-only authenticated actions exposed as
|
|
61
|
+
* `window.Reclaim.browserRequest`. */
|
|
62
|
+
browserRequestActions?: NonNullable<CreateProviderVersionRequest['webSettings']['browserRequestActions']>;
|
|
60
63
|
/** In-app interception options, mapped to
|
|
61
64
|
* `webSettings.clientOptions.inapp.interceptorOptions` — ONLY when this
|
|
62
65
|
* is explicitly passed (builder's schema has no `NONE` interceptorType;
|
|
@@ -104,7 +104,7 @@ function pickGeoLocation(providers) {
|
|
|
104
104
|
return DYNAMIC_GEO;
|
|
105
105
|
}
|
|
106
106
|
export function providerToCreateVersionRequest(providers, initialUrl, opts = {}) {
|
|
107
|
-
const { notes, version, requiredContext: requestedContext, jsUserScripts, allowedJsRequests, interceptorOptions, } = opts;
|
|
107
|
+
const { notes, version, requiredContext: requestedContext, jsUserScripts, allowedJsRequests, browserRequestActions, interceptorOptions, } = opts;
|
|
108
108
|
const requiredContext = requestedContext ?? deriveRequiredContext(providers, { allowedJsRequests, jsUserScripts });
|
|
109
109
|
// Each injected request carries its template flags (default true/true per
|
|
110
110
|
// RequestSelectionTemplate) on top of the shared request shape.
|
|
@@ -157,6 +157,7 @@ export function providerToCreateVersionRequest(providers, initialUrl, opts = {})
|
|
|
157
157
|
},
|
|
158
158
|
// Only set when non-empty; an empty string would store a no-op script.
|
|
159
159
|
...(script ? { jsUserScripts: script } : {}),
|
|
160
|
+
...(browserRequestActions?.length ? { browserRequestActions } : {}),
|
|
160
161
|
},
|
|
161
162
|
// Derived from `{{context.<name>}}` placeholders only — a captured
|
|
162
163
|
// provider's `paramValues` are extracted response parameters, not
|
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.31",
|
|
5
5
|
"description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
"@hapi/boom": "^9.1.4",
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
84
84
|
"@noble/hashes": "^2.2.0",
|
|
85
|
-
"@reclaimprotocol/attestor-core": "^5.
|
|
86
|
-
"@reclaimprotocol/client": "0.1.
|
|
85
|
+
"@reclaimprotocol/attestor-core": "^5.1.5",
|
|
86
|
+
"@reclaimprotocol/client": "0.1.31",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
package/skill/SKILL.builder.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
For claimant-facing Builder-client integration, result verification, or optional
|
|
5
5
|
TEE binding, read topic `integration`. Keep URLs without `api=2` on the
|
|
6
|
-
existing legacy path.
|
|
6
|
+
existing legacy path. For an asynchronous volunteer contribution to a
|
|
7
|
+
public-contribution-enabled provider, read topic `contributions` before using
|
|
8
|
+
Builder.
|
|
7
9
|
|
|
8
10
|
<!-- common: terminology -->
|
|
9
11
|
|
|
@@ -37,14 +39,6 @@ Multi-request provider? Run steps 5–8 once per request, then publish every
|
|
|
37
39
|
proven draft in one call. See topic `publish`.
|
|
38
40
|
|
|
39
41
|
<!-- topic: contributions -->
|
|
40
|
-
## Public provider contributions
|
|
41
|
-
|
|
42
|
-
For an asynchronous volunteer contribution, read
|
|
43
|
-
[community-contributions.md](community-contributions.md) before using Builder.
|
|
44
|
-
It defines the history-first workflow, approved-field allowlist, fixed-US
|
|
45
|
-
egress, privacy boundaries, generalized recipe checks, and candidate review.
|
|
46
|
-
Never modify a published version in place; create a candidate and submit it
|
|
47
|
-
for review.
|
|
48
42
|
|
|
49
43
|
<!-- topic: auth -->
|
|
50
44
|
## Getting authenticated
|
|
@@ -114,8 +108,18 @@ integration. Prefer Builder for new integrations.
|
|
|
114
108
|
<!-- topic: draft -->
|
|
115
109
|
## Draft the provider
|
|
116
110
|
|
|
111
|
+
## Contents
|
|
112
|
+
|
|
113
|
+
- [Choose a privacy mode](#choose-a-privacy-mode)
|
|
114
|
+
- [Draft and inspect the request](#draft-and-inspect-the-request)
|
|
115
|
+
- [Preserve the canonical shape](#preserve-the-canonical-shape)
|
|
116
|
+
|
|
117
|
+
### Choose a privacy mode
|
|
118
|
+
|
|
117
119
|
<!-- common: oprf -->
|
|
118
120
|
|
|
121
|
+
### Draft and inspect the request
|
|
122
|
+
|
|
119
123
|
**Call `propose_provider`.** It returns a **`draftId`** — the handle for
|
|
120
124
|
`replay_request` and `run_proof` — plus the secret-free `provider` and
|
|
121
125
|
`secretRefs`. The recipe **and the captured secret values** live server-side
|
|
@@ -179,11 +183,22 @@ synthesizer does this for you.
|
|
|
179
183
|
With `hash: 'oprf-raw'`, the redaction gains `"hash": "oprf-raw"`. Nothing else
|
|
180
184
|
changes.
|
|
181
185
|
|
|
186
|
+
### Preserve the canonical shape
|
|
187
|
+
|
|
182
188
|
<!-- common: rewrite-shape-block -->
|
|
183
189
|
|
|
184
190
|
<!-- topic: params -->
|
|
185
191
|
## Parameters: extracted, consumer-supplied, and secret
|
|
186
192
|
|
|
193
|
+
## Contents
|
|
194
|
+
|
|
195
|
+
- [Choose the parameter source](#choose-the-parameter-source)
|
|
196
|
+
- [Protect secret values](#protect-secret-values)
|
|
197
|
+
- [Hide internal fields from claimant UI](#hide-internal-fields-from-claimant-ui)
|
|
198
|
+
- [Understand runtime resolution](#how-the-verification-client-resolves-these-at-runtime)
|
|
199
|
+
|
|
200
|
+
### Choose the parameter source
|
|
201
|
+
|
|
187
202
|
**Parameterize user-specific URL segments** so the provider isn't pinned to one
|
|
188
203
|
user. The matcher already puts the *captured* value in `paramValues`. If the
|
|
189
204
|
consumer knows a URL value before verification and the proof must be pinned to
|
|
@@ -328,8 +343,8 @@ or archive an existing version to apply provider changes.
|
|
|
328
343
|
drives login/waits/navigation so the target request fires), or
|
|
329
344
|
`allowedJsRequests` (extra requests the script may fire, for example,
|
|
330
345
|
pagination). → topic `user-script`. Before populating `allowedJsRequests` for
|
|
331
|
-
a `
|
|
332
|
-
session-unique URL there makes
|
|
346
|
+
a `verifyRequestProof({ providerId })` use case, read topic `hash-validation` — a
|
|
347
|
+
session-unique URL there makes verifyRequestProof reject every real proof.
|
|
333
348
|
|
|
334
349
|
**3. `requiredContext` is derived for you.** The publish tool scans the recipe's
|
|
335
350
|
url/body for `{{context.<name>}}` placeholders and emits a JSON Schema requiring
|
|
@@ -387,76 +402,6 @@ For client integration, result verification, or TEE binding, read topic
|
|
|
387
402
|
`integration`.
|
|
388
403
|
|
|
389
404
|
<!-- topic: integration -->
|
|
390
|
-
## Integrate a Builder Verification Client
|
|
391
|
-
|
|
392
|
-
**Verification sessions.** Consumers authenticate with a per-org token
|
|
393
|
-
(`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create
|
|
394
|
-
sessions via `POST /verifications/sessions`. Results are ECIES-encrypted
|
|
395
|
-
(secp256k1 + AES-256-GCM) to the org's eth public key when `canEncryptResult` is
|
|
396
|
-
enabled (set via `PUT /orgs/{orgId}/keypair`) and delivered to the org's
|
|
397
|
-
callback subscriptions; plaintext otherwise. Troubleshoot with
|
|
398
|
-
`get_verification_session`, `list_verification_events`,
|
|
399
|
-
`list_org_verification_sessions`.
|
|
400
|
-
|
|
401
|
-
For a claimant-facing Builder-mode integration, share the returned
|
|
402
|
-
`verificationUrl` (it contains the session id and `api=2`). Append client-owned
|
|
403
|
-
redirect parameters only after session creation. Append `diag=1` only for a
|
|
404
|
-
deliberate troubleshooting launch; it can enable logs containing personal data.
|
|
405
|
-
The registered client resolves its own `x-reclaim-vc-id`; do not invent a
|
|
406
|
-
client token or put callback/redirect fields in the session request. Consumers
|
|
407
|
-
should verify terminal deliveries with `verifyResultFull` and the session's
|
|
408
|
-
trusted issuer.
|
|
409
|
-
|
|
410
|
-
An optional `themeId` on session creation selects an active theme owned by the
|
|
411
|
-
organization. Omit it to snapshot the organization's most recently updated
|
|
412
|
-
active theme. A theme's localized `consent` block makes supported clients show
|
|
413
|
-
consent before the first provider; no block means no consent screen. Theme and
|
|
414
|
-
branding data never affect proof trust.
|
|
415
|
-
|
|
416
|
-
**Browser extension.** For the exact `api=2` URL, use
|
|
417
|
-
`reclaimExtensionSDK.fromVerificationUrl` or `initBuilder` with the session's
|
|
418
|
-
`verificationClientId`, then call `startVerification()`. The extension
|
|
419
|
-
generates and persists `claimantClientId` when omitted, resolves recipes from
|
|
420
|
-
bootstrap, runs them sequentially, and sends `x-reclaim-vc-id` to every bridge
|
|
421
|
-
route, including session-bound `attestor-auth`. It forwards raw legacy proofs
|
|
422
|
-
inside the Builder result and does not perform client-side `allowedJsRequests`
|
|
423
|
-
validation. Builder recipes support dotted `{{context.key}}` templates; the
|
|
424
|
-
extension forwards matching session context to claim creation. Builder mode
|
|
425
|
-
skips legacy offscreen session-status calls and uses the Builder bridge for
|
|
426
|
-
session lifecycle updates. It emits `verification_result_submitting` and
|
|
427
|
-
`verification_result_submission_failed` when applicable. It does not send
|
|
428
|
-
terminal success or error as a separate best-effort event; the results request
|
|
429
|
-
makes the bridge record the matching terminal event strongly before signing
|
|
430
|
-
and storing result deliveries. Missing or unknown `api` values stay on the
|
|
431
|
-
existing `init` / `fromJsonString` / direct-callback path.
|
|
432
|
-
|
|
433
|
-
**Builder client bridge.** In `api=2` mode, the registered client UUID is sent
|
|
434
|
-
in `x-reclaim-vc-id` to these routes:
|
|
435
|
-
|
|
436
|
-
| Route | Purpose |
|
|
437
|
-
| --- | --- |
|
|
438
|
-
| `GET /api/sdk/builder/v2/sessions/{sessionId}/bootstrap` | Load the session and resolved recipe. |
|
|
439
|
-
| `PATCH /api/sdk/builder/v2/sessions/{sessionId}/claimant` | Update claimant diagnostics. |
|
|
440
|
-
| `POST /api/sdk/builder/v2/sessions/{sessionId}/events` | Report canonical lower-snake-case events. |
|
|
441
|
-
| `POST /api/sdk/builder/v2/sessions/{sessionId}/attestor-auth` | Request session-bound attestor authentication. Do not send a legacy app secret or provider signature. |
|
|
442
|
-
| `POST /api/sdk/builder/v2/sessions/{sessionId}/results` | Submit exact legacy proof objects; the bridge records the matching terminal event strongly before signing and storing result deliveries. |
|
|
443
|
-
|
|
444
|
-
The bridge preserves each legacy proof and signs only the outer Verification
|
|
445
|
-
Client JWS. `verifyResultFull` validates the issuer, JWS, session, audience,
|
|
446
|
-
and expiry, then passes each exact proof to `verifyProof`. Keep TEE settings in
|
|
447
|
-
the legacy verifier configuration; do not add verification hooks. Treat outer
|
|
448
|
-
`extracted_parameters` and `content_hash` as diagnostics, not trust inputs.
|
|
449
|
-
|
|
450
|
-
**Optional TEE binding.** A consumer can pass
|
|
451
|
-
`teeAttestation: { appSecret }` to `sessions.create`. The SDK derives the
|
|
452
|
-
hash nonce locally with the pinned `@reclaimprotocol/js-sdk` formula, signs
|
|
453
|
-
the binding payload with EIP-191, and registers it with
|
|
454
|
-
`PUT /verifications/sessions/{sessionId}/tee-nonce` using the organization
|
|
455
|
-
token. The private key stays in the consumer process. Bind before proof
|
|
456
|
-
generation. The binding is idempotent for the same values and immutable
|
|
457
|
-
afterward; this helper registers only `RECLAIM_TEE_NONCE_V1` (`v3`). Legacy
|
|
458
|
-
`verifyProof` accepts the older signature nonce, but this helper does not
|
|
459
|
-
generate or register it.
|
|
460
405
|
|
|
461
406
|
<!-- topic: never-do -->
|
|
462
407
|
## Things to never do
|
|
@@ -491,8 +436,10 @@ turn
|
|
|
491
436
|
document replay off and keep the interceptor (`disableRequestReplay: true`, or
|
|
492
437
|
`isDocumentRequestReplayEnabled: false` on builder) — often that alone is what
|
|
493
438
|
breaks the page, and it's the smallest change. Only if that doesn't help, turn
|
|
494
|
-
interception off
|
|
495
|
-
`interceptorOptions`).
|
|
439
|
+
interception off temporarily to confirm it is the cause (`injectionType: NONE`,
|
|
440
|
+
or omit `interceptorOptions`). If that fixes the provider, keep interception
|
|
441
|
+
off and replace intercepted claims with `window.Reclaim.requestClaim`. Full
|
|
442
|
+
checklist and remedies: topic `interception`.
|
|
496
443
|
|
|
497
444
|
**Proof failures / attestor errors** → topic `prove`.
|
|
498
445
|
**Browser won't launch or attach** → topic `browser`.
|