@reclaimprotocol/agent 0.1.5 → 0.1.7
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/backend.d.ts +0 -24
- package/lib/authoring/find.d.ts +3 -0
- package/lib/authoring/find.js +54 -0
- package/lib/authoring/index.d.ts +3 -3
- package/lib/authoring/index.js +4 -3
- package/lib/authoring/propose.d.ts +3 -0
- package/lib/authoring/propose.js +109 -0
- package/lib/authoring/replay.d.ts +10 -0
- package/lib/authoring/replay.js +34 -0
- package/lib/authoring/tools.d.ts +13 -6
- package/lib/authoring/tools.js +108 -293
- package/lib/authoring/util.d.ts +27 -0
- package/lib/authoring/util.js +21 -0
- package/lib/consts.d.ts +6 -1
- package/lib/consts.js +9 -2
- package/lib/mcp/server.d.ts +2 -41
- package/lib/mcp/server.js +6 -34
- package/lib/mcp/start.js +4 -2
- package/lib/mcp/tools/agent/analyze.js +3 -3
- package/lib/mcp/tools/agent/build.d.ts +4 -4
- package/lib/mcp/tools/agent/build.js +13 -15
- package/lib/mcp/tools/agent/capture.d.ts +13 -1
- package/lib/mcp/tools/agent/capture.js +18 -85
- package/lib/mcp/tools/agent/eval.d.ts +5 -2
- package/lib/mcp/tools/agent/eval.js +11 -121
- package/lib/mcp/tools/agent/find.d.ts +10 -3
- package/lib/mcp/tools/agent/find.js +25 -31
- package/lib/mcp/tools/agent/proof.d.ts +13 -3
- package/lib/mcp/tools/agent/proof.js +81 -132
- package/lib/mcp/tools/agent/propose.d.ts +10 -9
- package/lib/mcp/tools/agent/propose.js +87 -64
- package/lib/mcp/tools/agent/replay.d.ts +10 -3
- package/lib/mcp/tools/agent/replay.js +29 -36
- package/lib/mcp/tools/agent/session.d.ts +1 -3
- package/lib/mcp/tools/agent/session.js +5 -5
- package/lib/mcp/tools/agent/shared.d.ts +5 -3
- package/lib/mcp/tools/agent/shared.js +29 -19
- package/lib/old/client.d.ts +39 -0
- package/lib/old/client.js +18 -0
- package/lib/old/login-cdp.d.ts +24 -5
- package/lib/old/login-cdp.js +29 -15
- package/lib/old/sdk-client.d.ts +39 -0
- package/lib/old/sdk-client.js +85 -0
- package/lib/old/to-register.d.ts +69 -0
- package/lib/old/to-register.js +55 -2
- package/lib/old/tools/authenticate.js +3 -7
- package/lib/old/tools/build.d.ts +6 -1
- package/lib/old/tools/build.js +11 -1
- package/lib/old/tools/get-provider-info.d.ts +16 -0
- package/lib/old/tools/get-provider-info.js +73 -0
- package/lib/old/tools/get-version-config.d.ts +17 -0
- package/lib/old/tools/get-version-config.js +47 -0
- package/lib/old/tools/publish.js +208 -2
- package/lib/old/tools/update-provider-metadata.d.ts +16 -0
- package/lib/old/tools/update-provider-metadata.js +61 -0
- package/lib/proof/attestor.d.ts +6 -3
- package/lib/proof/attestor.js +10 -1
- package/lib/proof/debug-response-dump.d.ts +4 -0
- package/lib/proof/debug-response-dump.js +55 -0
- package/lib/proof/patch-domhandler.d.ts +1 -0
- package/lib/proof/patch-domhandler.js +59 -0
- package/lib/proof/zk-files.js +7 -6
- package/lib/provider/dom-xpath.d.ts +17 -0
- package/lib/provider/dom-xpath.js +187 -0
- package/lib/provider/draft.d.ts +30 -2
- package/lib/provider/draft.js +117 -28
- package/lib/provider/index.d.ts +4 -0
- package/lib/provider/index.js +7 -0
- package/lib/provider/injection-doc.js +6 -0
- package/lib/provider/matcher.d.ts +17 -20
- package/lib/provider/matcher.js +394 -280
- package/lib/provider/schema.d.ts +7 -5
- package/lib/tools/registry.d.ts +14 -0
- package/lib/tools/registry.js +32 -0
- package/package.json +9 -1
- package/skill/SKILL.builder.md +42 -23
- package/skill/SKILL.old.md +6 -2
- package/skill/builder-mode.md +1 -1
- package/skill/injection-api.md +117 -0
- package/src/authoring/backend.ts +98 -0
- package/src/authoring/index.ts +20 -0
- package/src/authoring/tools.ts +273 -0
- package/src/authoring/util.ts +42 -0
- package/src/consts.ts +15 -2
- package/src/mcp/server.ts +11 -78
- package/src/mcp/start.ts +4 -1
- package/src/mcp/tools/agent/analyze.ts +3 -3
- package/src/mcp/tools/agent/build.ts +13 -21
- package/src/mcp/tools/agent/capture.ts +31 -115
- package/src/mcp/tools/agent/eval.ts +19 -155
- package/src/mcp/tools/agent/find.ts +33 -36
- package/src/mcp/tools/agent/proof.ts +87 -160
- package/src/mcp/tools/agent/propose.ts +93 -85
- package/src/mcp/tools/agent/replay.ts +32 -42
- package/src/mcp/tools/agent/session.ts +4 -5
- package/src/mcp/tools/agent/shared.ts +152 -0
- package/src/old/client.ts +60 -0
- package/src/old/login-cdp.ts +29 -23
- package/src/old/sdk-client.ts +105 -0
- package/src/old/to-register.ts +102 -6
- package/src/old/tools/authenticate.ts +3 -11
- package/src/old/tools/build.ts +12 -0
- package/src/old/tools/get-provider-info.ts +91 -0
- package/src/old/tools/get-version-config.ts +56 -0
- package/src/old/tools/publish.ts +264 -1
- package/src/old/tools/update-provider-metadata.ts +72 -0
- package/src/proof/attestor.ts +16 -4
- package/src/proof/debug-response-dump.ts +70 -0
- package/src/proof/patch-domhandler.ts +68 -0
- package/src/proof/zk-files.ts +7 -6
- package/src/provider/dom-xpath.ts +225 -0
- package/src/provider/draft.ts +168 -35
- package/src/provider/index.ts +8 -0
- package/src/provider/injection-doc.ts +6 -0
- package/src/provider/matcher.ts +477 -357
- package/src/provider/schema.ts +7 -5
- package/src/tools/registry.ts +92 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isBoom } from '@hapi/boom';
|
|
2
|
+
import { defineTool } from "../../mcp/server.js";
|
|
3
|
+
/** Best-effort call: resolve to the response, or to an error string instead
|
|
4
|
+
* of throwing — one endpoint 404ing (e.g. a bad `versionNumber`) shouldn't
|
|
5
|
+
* hide a result the other endpoint DID have. */
|
|
6
|
+
async function attempt(label, call) {
|
|
7
|
+
try {
|
|
8
|
+
return { ok: true, value: await call() };
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
const message = isBoom(err) ? err.message : String(err);
|
|
12
|
+
return { ok: false, error: `${label}: ${message}` };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Read provider info from the PUBLIC production SDK backend
|
|
17
|
+
* (`api.reclaimprotocol.org`) — the service the verification SDK/attestor
|
|
18
|
+
* itself calls at runtime, NOT the old-devtools dashboard backend
|
|
19
|
+
* (`get_provider_version_config`, `devapi.reclaimprotocol.org`, requires
|
|
20
|
+
* auth). Both underlying reads are unauthenticated.
|
|
21
|
+
*
|
|
22
|
+
* Fetches BOTH shapes in one call, since they're complementary:
|
|
23
|
+
* - `provider`: full provider + providerConfig, `customInjection` included.
|
|
24
|
+
* - `configs`: the trimmed runtime recipe the attestor actually consumes —
|
|
25
|
+
* `customInjection` deliberately omitted, `isScriptRequestingClaim`
|
|
26
|
+
* computed instead.
|
|
27
|
+
*/
|
|
28
|
+
export function getProviderInfoTool(client) {
|
|
29
|
+
return defineTool({
|
|
30
|
+
name: 'get_provider_info',
|
|
31
|
+
description: 'Read a PUBLISHED provider from the public production SDK backend ' +
|
|
32
|
+
'(api.reclaimprotocol.org — what the verification SDK/attestor ' +
|
|
33
|
+
'itself fetches at runtime), for ANY provider, no auth required. ' +
|
|
34
|
+
'Different from get_provider_version_config, which reads the old ' +
|
|
35
|
+
'devtools DASHBOARD backend and requires auth. \n\n' +
|
|
36
|
+
'Returns two complementary reads for the given version (latest ' +
|
|
37
|
+
'when `versionNumber` is omitted): `provider` (the full provider ' +
|
|
38
|
+
'+ providerConfig, including `customInjection`) and `configs` (the ' +
|
|
39
|
+
'trimmed recipe the attestor actually consumes — `customInjection` ' +
|
|
40
|
+
'is deliberately OMITTED there, replaced by a computed ' +
|
|
41
|
+
'`isScriptRequestingClaim` flag). One endpoint failing (e.g. an ' +
|
|
42
|
+
'invalid `versionNumber`) is reported under `errors` without ' +
|
|
43
|
+
'hiding a result the other endpoint did return.',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
providerId: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'UUID of the provider.',
|
|
50
|
+
},
|
|
51
|
+
versionNumber: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Specific version, e.g. "1.0.2". Omit for the ' +
|
|
54
|
+
"provider's current latest version.",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['providerId'],
|
|
58
|
+
},
|
|
59
|
+
}, async ({ providerId, versionNumber }) => {
|
|
60
|
+
const [provider, configs] = await Promise.all([
|
|
61
|
+
attempt('provider', () => client.getProvider(providerId, versionNumber)),
|
|
62
|
+
attempt('configs', () => client.getProviderConfigs(providerId, versionNumber)),
|
|
63
|
+
]);
|
|
64
|
+
const errors = [provider, configs]
|
|
65
|
+
.filter((r) => !r.ok)
|
|
66
|
+
.map((r) => r.error);
|
|
67
|
+
return {
|
|
68
|
+
...(provider.ok ? { provider: provider.value } : {}),
|
|
69
|
+
...(configs.ok ? { configs: configs.value } : {}),
|
|
70
|
+
...(errors.length ? { errors } : {}),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RegisteredTool } from '../../mcp/server.ts';
|
|
2
|
+
import type { ReclaimOldClient } from '../client.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Read the full `providerConfig` (loginUrl, customInjection, requestData,
|
|
5
|
+
* geoLocation, ...) of one provider version on the old devtools backend —
|
|
6
|
+
* the latest version when `version` is omitted.
|
|
7
|
+
*
|
|
8
|
+
* Exists so a dev/agent can see what's CURRENTLY published before adding a
|
|
9
|
+
* new version with `create_provider_version_from_capture`. That tool always
|
|
10
|
+
* takes a FULL replacement `providerConfig` (no partial-patch endpoint on
|
|
11
|
+
* this backend), so a version added without knowing the current
|
|
12
|
+
* `customInjection` would silently publish it blank — wiping a working
|
|
13
|
+
* login script instead of preserving it. Read it here first, then pass it
|
|
14
|
+
* straight through (or omit `customInjection` on publish to have it carried
|
|
15
|
+
* forward automatically).
|
|
16
|
+
*/
|
|
17
|
+
export declare function getProviderVersionConfigTool(client: ReclaimOldClient): RegisteredTool;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineTool } from "../../mcp/server.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read the full `providerConfig` (loginUrl, customInjection, requestData,
|
|
4
|
+
* geoLocation, ...) of one provider version on the old devtools backend —
|
|
5
|
+
* the latest version when `version` is omitted.
|
|
6
|
+
*
|
|
7
|
+
* Exists so a dev/agent can see what's CURRENTLY published before adding a
|
|
8
|
+
* new version with `create_provider_version_from_capture`. That tool always
|
|
9
|
+
* takes a FULL replacement `providerConfig` (no partial-patch endpoint on
|
|
10
|
+
* this backend), so a version added without knowing the current
|
|
11
|
+
* `customInjection` would silently publish it blank — wiping a working
|
|
12
|
+
* login script instead of preserving it. Read it here first, then pass it
|
|
13
|
+
* straight through (or omit `customInjection` on publish to have it carried
|
|
14
|
+
* forward automatically).
|
|
15
|
+
*/
|
|
16
|
+
export function getProviderVersionConfigTool(client) {
|
|
17
|
+
return defineTool({
|
|
18
|
+
name: 'get_provider_version_config',
|
|
19
|
+
description: 'Fetch the full providerConfig (loginUrl, customInjection, ' +
|
|
20
|
+
'requestData, geoLocation, ...) of one version on the old devtools ' +
|
|
21
|
+
'backend — the latest version when `version` is omitted. Readable ' +
|
|
22
|
+
'for any non-archived provider, not just ones you own. Use this ' +
|
|
23
|
+
'BEFORE calling create_provider_version_from_capture with a ' +
|
|
24
|
+
'`providerId` to see the currently-published customInjection/' +
|
|
25
|
+
'requestData so you know what you would be changing — that tool ' +
|
|
26
|
+
'replaces the whole providerConfig, so a field you never see and ' +
|
|
27
|
+
"never pass comes out blank in the new version, even if it's set " +
|
|
28
|
+
'today.',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
providerId: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'UUID of the provider (from get_me_providers).',
|
|
35
|
+
},
|
|
36
|
+
version: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Specific version to fetch, e.g. "1.0.2". Omit for the ' +
|
|
39
|
+
"provider's current latest version.",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['providerId'],
|
|
43
|
+
},
|
|
44
|
+
}, async ({ providerId, version }) => {
|
|
45
|
+
return client.getProviderConfig(providerId, version);
|
|
46
|
+
});
|
|
47
|
+
}
|
package/lib/old/tools/publish.js
CHANGED
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
import { Boom, isBoom } from '@hapi/boom';
|
|
2
2
|
import { defineTool } from "../../mcp/server.js";
|
|
3
|
+
import { INJECTION_GUIDE } from "../../provider/injection-doc.js";
|
|
3
4
|
import { countsFor, matchRedactionWarnings, } from "../../provider/match-redaction-warning.js";
|
|
4
|
-
import { bumpPatchString, extractVersions, providerToConfigBody, providerToRegisterBody, } from "../to-register.js";
|
|
5
|
+
import { bumpPatchString, extractVersions, injectedRequestDataFrom, providerToConfigBody, providerToRegisterBody, } from "../to-register.js";
|
|
5
6
|
const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture';
|
|
7
|
+
/** Fields with no partial-patch equivalent on this backend: a version always
|
|
8
|
+
* replaces the WHOLE `providerConfig`, so omitting one of these on
|
|
9
|
+
* add-version must carry forward the current value rather than publish it
|
|
10
|
+
* blank. `allowedInjectedRequestData` is handled alongside these (further
|
|
11
|
+
* down, inline) since it needs translation when freshly authored, unlike
|
|
12
|
+
* these — which pass through unchanged either way. */
|
|
13
|
+
const PRESERVABLE_KEYS = [
|
|
14
|
+
'customInjection',
|
|
15
|
+
'userAgent',
|
|
16
|
+
'pageTitle',
|
|
17
|
+
'stepsToFollow',
|
|
18
|
+
'useIncognitoWebview',
|
|
19
|
+
'extensionConfig',
|
|
20
|
+
];
|
|
21
|
+
/** Pull `providerConfig` out of a `get_provider_version_config` /
|
|
22
|
+
* `getProviderConfig` response, tolerant of a missing/malformed shape. */
|
|
23
|
+
function extractProviderConfig(resp) {
|
|
24
|
+
if (resp && typeof resp === 'object') {
|
|
25
|
+
const cfg = resp.providerConfig;
|
|
26
|
+
if (cfg && typeof cfg === 'object') {
|
|
27
|
+
return cfg;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Prefer the caller's explicit value; otherwise fall back to `current`'s
|
|
33
|
+
* value (if any) and record `fieldName` as carried forward.
|
|
34
|
+
*
|
|
35
|
+
* `current === null` is treated the SAME as `undefined` (nothing to carry),
|
|
36
|
+
* not as a real value to preserve. These are all optional fields with no
|
|
37
|
+
* Prisma default — a version where one was never set reads back as
|
|
38
|
+
* `null`, not a missing key, because Prisma normalizes an absent optional
|
|
39
|
+
* field to `null` on read. Carrying that `null` "forward" would falsely
|
|
40
|
+
* claim the field was preserved when really nothing was ever there — it
|
|
41
|
+
* most likely means an EARLIER version already lost it (e.g. published
|
|
42
|
+
* before this field had carry-forward support at all), not that this call
|
|
43
|
+
* is doing anything wrong. */
|
|
44
|
+
function resolveField(explicit, current, fieldName, carried) {
|
|
45
|
+
if (explicit !== undefined) {
|
|
46
|
+
return explicit;
|
|
47
|
+
}
|
|
48
|
+
if (current !== undefined && current !== null) {
|
|
49
|
+
carried.push(fieldName);
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
6
54
|
/**
|
|
7
55
|
* The old backend lets you add a version only to a provider YOU own. A
|
|
8
56
|
* `providerId` for a public provider (or anyone else's) comes back as
|
|
@@ -60,6 +108,29 @@ export function createVersionFromCaptureTool(client) {
|
|
|
60
108
|
'must come from get_me_providers. To publish your own version of a ' +
|
|
61
109
|
"PUBLIC provider you don't own, omit `providerId` to register your " +
|
|
62
110
|
'own copy (you cannot edit someone else\'s provider here). \n\n' +
|
|
111
|
+
'For data gated behind login/navigation, pass `customInjection` — a ' +
|
|
112
|
+
'page-injection script (old-devtools\' name for the builder\'s ' +
|
|
113
|
+
'jsUserScripts) that drives the page to where the request fires. \n\n' +
|
|
114
|
+
'This backend has NO partial-patch endpoint — adding a version ' +
|
|
115
|
+
'always replaces the WHOLE providerConfig. When `providerId` is ' +
|
|
116
|
+
'set, any of `customInjection`, `userAgent`, `pageTitle`, ' +
|
|
117
|
+
'`allowedInjectedRequestData`, `stepsToFollow`, ' +
|
|
118
|
+
'`useIncognitoWebview`, `extensionConfig` that you OMIT is carried ' +
|
|
119
|
+
'forward unchanged from the currently-published version (so ' +
|
|
120
|
+
're-publishing requestData never wipes a working login script or ' +
|
|
121
|
+
'other config someone set via the dashboard). This can only carry ' +
|
|
122
|
+
'forward what the CURRENT version actually has — if an earlier ' +
|
|
123
|
+
'version already lost a field (e.g. published before this tool ' +
|
|
124
|
+
'carried it forward at all), there is nothing left to preserve and ' +
|
|
125
|
+
'the result legitimately comes back empty/null for that field; ' +
|
|
126
|
+
'that is not this call failing to carry it forward. Pass an ' +
|
|
127
|
+
'explicit value (`""`, `[]`, `false`, ...) to intentionally clear ' +
|
|
128
|
+
'one of them instead, or call get_provider_version_config FIRST ' +
|
|
129
|
+
'to see exactly what is live today, rather than assuming this ' +
|
|
130
|
+
'call recovered something that may already be gone. ' +
|
|
131
|
+
'`allowedInjectedRequestData`/`useIncognitoWebview`/' +
|
|
132
|
+
'`extensionConfig` only take effect on add-version — the register ' +
|
|
133
|
+
'endpoint (no `providerId`) ignores them. \n\n' +
|
|
63
134
|
'Captured requests must be GET/POST. OPRF redactions are supported.',
|
|
64
135
|
inputSchema: {
|
|
65
136
|
type: 'object',
|
|
@@ -132,6 +203,71 @@ export function createVersionFromCaptureTool(client) {
|
|
|
132
203
|
description: 'Change note for the new version (update only; must be ≥10 ' +
|
|
133
204
|
'chars or a default is used).',
|
|
134
205
|
},
|
|
206
|
+
customInjection: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
description: 'Plain JavaScript injected into the provider page before every ' +
|
|
209
|
+
'page load — old-devtools\' name for what the builder calls ' +
|
|
210
|
+
'`webSettings.jsUserScripts`. Use standard browser APIs ' +
|
|
211
|
+
'(window.location, element.click(), forms) to drive login/' +
|
|
212
|
+
'navigation so the target request fires — window.Reclaim is ' +
|
|
213
|
+
'only for rare edge cases, not navigation. The backend rejects ' +
|
|
214
|
+
'a few deprecated injection APIs (e.g. window.payloadData, ' +
|
|
215
|
+
'window.ReclaimInjected) with a 400. Guide:\n\n' +
|
|
216
|
+
INJECTION_GUIDE,
|
|
217
|
+
},
|
|
218
|
+
userAgent: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
description: 'Per-platform user-agent override for the verification ' +
|
|
221
|
+
'webview. Accepted on register too.',
|
|
222
|
+
properties: {
|
|
223
|
+
ios: { type: 'string' },
|
|
224
|
+
android: { type: 'string' },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
pageTitle: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
description: 'Page title metadata. Accepted on register too.',
|
|
230
|
+
},
|
|
231
|
+
allowedInjectedRequestData: {
|
|
232
|
+
type: 'array',
|
|
233
|
+
description: 'Extra requests the customInjection script is allowed to ' +
|
|
234
|
+
'fire (e.g. pagination) — add-version ONLY, the register ' +
|
|
235
|
+
'endpoint ignores this. Each item has the same shape as ' +
|
|
236
|
+
'`provider`, plus `multiple`/`required` (default true).',
|
|
237
|
+
items: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
url: { type: 'string' },
|
|
241
|
+
method: { type: 'string' },
|
|
242
|
+
body: { type: 'string' },
|
|
243
|
+
responseMatches: { type: 'array' },
|
|
244
|
+
responseRedactions: { type: 'array' },
|
|
245
|
+
multiple: {
|
|
246
|
+
type: 'boolean',
|
|
247
|
+
description: 'Allow matching many requests. Default true.',
|
|
248
|
+
},
|
|
249
|
+
required: {
|
|
250
|
+
type: 'boolean',
|
|
251
|
+
description: 'Fail validation if none match. Default true.',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ['url', 'method'],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
stepsToFollow: {
|
|
258
|
+
type: 'string',
|
|
259
|
+
description: 'Free-text setup/manual-verification steps shown to the ' +
|
|
260
|
+
'dev/verifier. Accepted on register too.',
|
|
261
|
+
},
|
|
262
|
+
useIncognitoWebview: {
|
|
263
|
+
type: 'boolean',
|
|
264
|
+
description: 'Run the verification webview in incognito mode. ' +
|
|
265
|
+
'Add-version ONLY.',
|
|
266
|
+
},
|
|
267
|
+
extensionConfig: {
|
|
268
|
+
description: 'Free-form JSON passed through to the client. Add-version ' +
|
|
269
|
+
'ONLY.',
|
|
270
|
+
},
|
|
135
271
|
},
|
|
136
272
|
required: ['initialUrl'],
|
|
137
273
|
},
|
|
@@ -158,6 +294,9 @@ export function createVersionFromCaptureTool(client) {
|
|
|
158
294
|
if (args.providerType !== undefined) {
|
|
159
295
|
opts.providerType = args.providerType;
|
|
160
296
|
}
|
|
297
|
+
if (args.customInjection !== undefined) {
|
|
298
|
+
opts.customInjection = args.customInjection;
|
|
299
|
+
}
|
|
161
300
|
// Non-blocking advisory per request — never alters or blocks the
|
|
162
301
|
// publish; the author keeps full control. Attaches under `_notes`.
|
|
163
302
|
const notes = matchRedactionWarnings(providers.map((p, i) => countsFor(`Request ${i + 1}`, p)));
|
|
@@ -167,8 +306,39 @@ export function createVersionFromCaptureTool(client) {
|
|
|
167
306
|
}
|
|
168
307
|
return result;
|
|
169
308
|
};
|
|
170
|
-
// New provider.
|
|
309
|
+
// New provider — no prior version to carry anything forward from.
|
|
310
|
+
// `allowedInjectedRequestData` / `useIncognitoWebview` /
|
|
311
|
+
// `extensionConfig` are accepted on the schema but the register
|
|
312
|
+
// controller never reads them from the body — warn rather than
|
|
313
|
+
// silently drop, so a dev doesn't think they took effect.
|
|
171
314
|
if (!args.providerId) {
|
|
315
|
+
if (opts.customInjection?.trim()) {
|
|
316
|
+
notes.push('customInjection will run in the verification client before ' +
|
|
317
|
+
'every page load (before navigating to initialUrl), matching ' +
|
|
318
|
+
'the builder\'s jsUserScripts.');
|
|
319
|
+
}
|
|
320
|
+
const REGISTER_IGNORED_KEYS = [
|
|
321
|
+
'allowedInjectedRequestData',
|
|
322
|
+
'useIncognitoWebview',
|
|
323
|
+
'extensionConfig',
|
|
324
|
+
];
|
|
325
|
+
const ignoredOnRegister = REGISTER_IGNORED_KEYS
|
|
326
|
+
.filter((k) => args[k] !== undefined);
|
|
327
|
+
if (ignoredOnRegister.length) {
|
|
328
|
+
notes.push(`${ignoredOnRegister.join(', ')} ${ignoredOnRegister.length > 1 ? 'are' : 'is'} ignored on register (no providerId) — the old backend only ` +
|
|
329
|
+
'reads them when adding a version. Publish once to create ' +
|
|
330
|
+
'the provider, then call this again WITH the returned ' +
|
|
331
|
+
'providerId to set them.');
|
|
332
|
+
}
|
|
333
|
+
if (args.userAgent !== undefined) {
|
|
334
|
+
opts.userAgent = args.userAgent;
|
|
335
|
+
}
|
|
336
|
+
if (args.pageTitle !== undefined) {
|
|
337
|
+
opts.pageTitle = args.pageTitle;
|
|
338
|
+
}
|
|
339
|
+
if (args.stepsToFollow !== undefined) {
|
|
340
|
+
opts.stepsToFollow = args.stepsToFollow;
|
|
341
|
+
}
|
|
172
342
|
const body = providerToRegisterBody(providers, opts);
|
|
173
343
|
return withNotes(await client.registerProvider(body));
|
|
174
344
|
}
|
|
@@ -178,6 +348,42 @@ export function createVersionFromCaptureTool(client) {
|
|
|
178
348
|
// workaround instead of a bare permission error.
|
|
179
349
|
const providerId = args.providerId;
|
|
180
350
|
try {
|
|
351
|
+
// This backend has no partial-patch endpoint — a version always
|
|
352
|
+
// replaces the WHOLE providerConfig. Anything the caller didn't
|
|
353
|
+
// pass at all is carried forward from the currently-published
|
|
354
|
+
// version instead of going out blank. Pass an explicit empty
|
|
355
|
+
// value ("", [], false) to intentionally clear one.
|
|
356
|
+
const needsCurrent = PRESERVABLE_KEYS.some((k) => args[k] === undefined)
|
|
357
|
+
|| args.allowedInjectedRequestData === undefined;
|
|
358
|
+
const current = needsCurrent
|
|
359
|
+
? extractProviderConfig(await client.getProviderConfig(providerId))
|
|
360
|
+
: undefined;
|
|
361
|
+
const carried = [];
|
|
362
|
+
for (const key of PRESERVABLE_KEYS) {
|
|
363
|
+
const resolved = resolveField(args[key], current?.[key], key, carried);
|
|
364
|
+
if (resolved !== undefined) {
|
|
365
|
+
opts[key] = resolved;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const freshInjected = args.allowedInjectedRequestData !== undefined
|
|
369
|
+
? injectedRequestDataFrom(args.allowedInjectedRequestData)
|
|
370
|
+
: undefined;
|
|
371
|
+
const resolvedInjected = resolveField(freshInjected, current?.allowedInjectedRequestData, 'allowedInjectedRequestData', carried);
|
|
372
|
+
if (resolvedInjected !== undefined) {
|
|
373
|
+
opts.allowedInjectedRequestData = resolvedInjected;
|
|
374
|
+
}
|
|
375
|
+
if (carried.length) {
|
|
376
|
+
notes.push('Not passed, carried forward unchanged from the current ' +
|
|
377
|
+
`published version: ${carried.join(', ')}. Pass an explicit ` +
|
|
378
|
+
'value ("", [], false, ...) to override any of them, or ' +
|
|
379
|
+
'call get_provider_version_config first to see what is ' +
|
|
380
|
+
'live today.');
|
|
381
|
+
}
|
|
382
|
+
if (args.customInjection !== undefined && opts.customInjection?.trim()) {
|
|
383
|
+
notes.push('customInjection will run in the verification client before ' +
|
|
384
|
+
'every page load (before navigating to initialUrl), matching ' +
|
|
385
|
+
'the builder\'s jsUserScripts.');
|
|
386
|
+
}
|
|
181
387
|
const versionsResp = await client.listVersions(providerId);
|
|
182
388
|
const version = bumpPatchString(extractVersions(versionsResp));
|
|
183
389
|
const versionInfo = args.notes && args.notes.trim().length >= 10
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RegisteredTool } from '../../mcp/server.ts';
|
|
2
|
+
import type { ReclaimOldClient } from '../client.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Edit provider-level metadata on the old devtools backend
|
|
5
|
+
* (`POST /api/providers/:providerId`) — a genuine PARTIAL update on the
|
|
6
|
+
* PROVIDER document itself, distinct from both
|
|
7
|
+
* `create_provider_version_from_capture` paths: it does NOT touch
|
|
8
|
+
* `providerConfig`/versions at all, and only the fields you pass here
|
|
9
|
+
* change — everything else on the provider is left untouched (no need to
|
|
10
|
+
* re-supply `requestData`/`customInjection`/etc., and
|
|
11
|
+
* no new version is created).
|
|
12
|
+
*
|
|
13
|
+
* The backend 403s if you don't own the provider (and aren't an admin) —
|
|
14
|
+
* surfaced verbatim, same as any other auth failure from this client.
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateProviderMetadataTool(client: ReclaimOldClient): RegisteredTool;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineTool } from "../../mcp/server.js";
|
|
2
|
+
/**
|
|
3
|
+
* Edit provider-level metadata on the old devtools backend
|
|
4
|
+
* (`POST /api/providers/:providerId`) — a genuine PARTIAL update on the
|
|
5
|
+
* PROVIDER document itself, distinct from both
|
|
6
|
+
* `create_provider_version_from_capture` paths: it does NOT touch
|
|
7
|
+
* `providerConfig`/versions at all, and only the fields you pass here
|
|
8
|
+
* change — everything else on the provider is left untouched (no need to
|
|
9
|
+
* re-supply `requestData`/`customInjection`/etc., and
|
|
10
|
+
* no new version is created).
|
|
11
|
+
*
|
|
12
|
+
* The backend 403s if you don't own the provider (and aren't an admin) —
|
|
13
|
+
* surfaced verbatim, same as any other auth failure from this client.
|
|
14
|
+
*/
|
|
15
|
+
export function updateProviderMetadataTool(client) {
|
|
16
|
+
return defineTool({
|
|
17
|
+
name: 'update_provider_metadata',
|
|
18
|
+
description: 'Rename, redescribe, retag, flip providerType (PRIVATE/PUBLIC), or ' +
|
|
19
|
+
'toggle isActive on a provider YOU own — WITHOUT creating a new ' +
|
|
20
|
+
'version. Only the fields you pass change; everything else ' +
|
|
21
|
+
'(requestData, customInjection, versions, ...) is untouched. Pass ' +
|
|
22
|
+
'at least one of the optional fields. Get a `providerId` from ' +
|
|
23
|
+
'get_me_providers.',
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
providerId: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'UUID of the provider to update.',
|
|
30
|
+
},
|
|
31
|
+
name: { type: 'string', description: 'New provider name.' },
|
|
32
|
+
description: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'New provider description.',
|
|
35
|
+
},
|
|
36
|
+
providerType: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['PRIVATE', 'PUBLIC'],
|
|
39
|
+
description: 'Change PRIVATE/PUBLIC visibility.',
|
|
40
|
+
},
|
|
41
|
+
tags: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: { type: 'string' },
|
|
44
|
+
description: 'Replace the provider\'s tag list.',
|
|
45
|
+
},
|
|
46
|
+
isActive: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Whether the provider is active.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['providerId'],
|
|
52
|
+
},
|
|
53
|
+
}, async ({ providerId, ...body }) => {
|
|
54
|
+
const hasAnyField = Object.values(body).some((v) => v !== undefined);
|
|
55
|
+
if (!hasAnyField) {
|
|
56
|
+
throw new Error('Pass at least one of: name, description, providerType, tags, ' +
|
|
57
|
+
'isActive.');
|
|
58
|
+
}
|
|
59
|
+
return client.updateProviderMetadata(providerId, body);
|
|
60
|
+
});
|
|
61
|
+
}
|
package/lib/proof/attestor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import './patch-domhandler.ts';
|
|
1
2
|
import type { ReclaimProvider } from '../provider/schema.ts';
|
|
2
3
|
export interface AttestorLogEntry {
|
|
3
4
|
level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
@@ -26,9 +27,11 @@ export interface RunAttestorProofOptions {
|
|
|
26
27
|
/** Sign with this key directly (e.g. the app/VC's ephemeral per-session key)
|
|
27
28
|
* instead of resolving from env/.env/file. */
|
|
28
29
|
ownerPrivateKey?: string;
|
|
29
|
-
/** ZK engine for `createClaimOnAttestor`. Defaults to `'
|
|
30
|
-
*
|
|
31
|
-
|
|
30
|
+
/** ZK engine for `createClaimOnAttestor`. Defaults to `'stwo'` — a pure-WASM
|
|
31
|
+
* prover that runs everywhere (no native shared library, so it works on
|
|
32
|
+
* Windows where `'gnark'` fails to load its `.dll`). Server-side paths that
|
|
33
|
+
* want gnark's speed pin it explicitly (e.g. the verify runner). */
|
|
34
|
+
zkEngine?: 'gnark' | 'snarkjs' | 'stwo';
|
|
32
35
|
/** Extract the structured {@link AttestorClaim} (claim + attestor signature)
|
|
33
36
|
* onto the result for nested-proof construction. */
|
|
34
37
|
includeAttestorClaim?: boolean;
|
package/lib/proof/attestor.js
CHANGED
|
@@ -7,8 +7,13 @@
|
|
|
7
7
|
// reveals), never on responseMatches.
|
|
8
8
|
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
9
9
|
import { createClaimOnAttestor, } from '@reclaimprotocol/attestor-core';
|
|
10
|
+
// Side-effect: make external domhandler nodes DOM-like so attestor-core's
|
|
11
|
+
// bundled `xpath` accepts them (see patch-domhandler.ts). Must load before any
|
|
12
|
+
// proof runs its response-redaction extraction.
|
|
13
|
+
import "./patch-domhandler.js";
|
|
10
14
|
import { agentTraceEnabled } from "../consts.js";
|
|
11
15
|
import { resolveEthKey } from "../mcp/tools/authenticate/eth-key.js";
|
|
16
|
+
import { installResponseDump } from "./debug-response-dump.js";
|
|
12
17
|
import { buildHttpParams, ensureAttestorCrypto, parseClaim, resolveAttestorUrl, } from "./proof-core.js";
|
|
13
18
|
import { ensureZkFiles } from "./zk-files.js";
|
|
14
19
|
/** Trim a captured log stream to the last N entries — for shrinking the
|
|
@@ -140,6 +145,10 @@ export async function runAttestorProof(provider, secrets, opts = {}) {
|
|
|
140
145
|
const traceEnabled = agentTraceEnabled();
|
|
141
146
|
const capture = traceEnabled ? makeCaptureLogger() : undefined;
|
|
142
147
|
const attestorLogs = capture?.entries;
|
|
148
|
+
// Debug (RECLAIM_AGENT_DUMP_RESPONSE): dump the exact response the
|
|
149
|
+
// attestor validates against, so "Response does not contain X" is
|
|
150
|
+
// diagnosable against the attestor's real server-side fetch.
|
|
151
|
+
installResponseDump();
|
|
143
152
|
let claim;
|
|
144
153
|
try {
|
|
145
154
|
claim = await createClaimOnAttestor({
|
|
@@ -150,7 +159,7 @@ export async function runAttestorProof(provider, secrets, opts = {}) {
|
|
|
150
159
|
},
|
|
151
160
|
ownerPrivateKey,
|
|
152
161
|
client: { url: resolveAttestorUrl(opts.attestorUrl) },
|
|
153
|
-
zkEngine: opts.zkEngine ?? '
|
|
162
|
+
zkEngine: opts.zkEngine ?? 'stwo',
|
|
154
163
|
...(capture !== undefined && { logger: capture.logger }),
|
|
155
164
|
});
|
|
156
165
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Debug helper (gated by RECLAIM_AGENT_DUMP_RESPONSE): capture the EXACT
|
|
2
|
+
// response bytes the attestor validates a claim against, plus a per-match
|
|
3
|
+
// presence check. A "Response does not contain X" failure means the attestor's
|
|
4
|
+
// server-side re-fetch differs from what the browser (replay) saw — e.g. GitHub
|
|
5
|
+
// embeds nav/userMenu data for browser requests but not for the attestor's raw
|
|
6
|
+
// fetch. Seeing the attestor's actual response is the only way to tell what it
|
|
7
|
+
// really returned.
|
|
8
|
+
//
|
|
9
|
+
// On each getResponseRedactions call it writes the response to
|
|
10
|
+
// `attestor-last-response.html` in the process CWD and logs the byte count +,
|
|
11
|
+
// for every responseMatch (paramValues substituted), whether it's present.
|
|
12
|
+
import { providers } from '@reclaimprotocol/attestor-core';
|
|
13
|
+
import { writeFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
let installed = false;
|
|
16
|
+
/** Wrap attestor-core's http `getResponseRedactions` to dump the response it
|
|
17
|
+
* runs against. Idempotent; no-op unless RECLAIM_AGENT_DUMP_RESPONSE is
|
|
18
|
+
* set. */
|
|
19
|
+
export function installResponseDump() {
|
|
20
|
+
if (installed || !process.env['RECLAIM_AGENT_DUMP_RESPONSE']) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const http = providers['http'];
|
|
24
|
+
const original = http.getResponseRedactions;
|
|
25
|
+
if (typeof original !== 'function') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
installed = true;
|
|
29
|
+
http.getResponseRedactions = function (arg) {
|
|
30
|
+
try {
|
|
31
|
+
const bytes = Buffer.from(arg.response);
|
|
32
|
+
const text = bytes.toString('utf8');
|
|
33
|
+
const paramValues = arg.params.paramValues ?? {};
|
|
34
|
+
const matches = (arg.params.responseMatches ?? []).map((m) => {
|
|
35
|
+
let value = m.value ?? '';
|
|
36
|
+
for (const [key, val] of Object.entries(paramValues)) {
|
|
37
|
+
value = value.split(`{{${key}}}`).join(val);
|
|
38
|
+
}
|
|
39
|
+
return { value, present: text.includes(value) };
|
|
40
|
+
});
|
|
41
|
+
const path = join(process.cwd(), 'attestor-last-response.html');
|
|
42
|
+
writeFileSync(path, text);
|
|
43
|
+
process.stderr.write(`${JSON.stringify({
|
|
44
|
+
tag: 'attestor-response-dump',
|
|
45
|
+
bytes: bytes.length,
|
|
46
|
+
path,
|
|
47
|
+
matches,
|
|
48
|
+
})}\n`);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// diagnostic only — never break proving
|
|
52
|
+
}
|
|
53
|
+
return original.call(this, arg);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Side-effect module: make `domhandler` nodes DOM-like for the `xpath` engine.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS EXISTS
|
|
4
|
+
// `@reclaimprotocol/attestor-core`'s published `lib` bundles its own inlined
|
|
5
|
+
// copy of `domhandler` and applies a DOM-compat patch (nodeName / localName +
|
|
6
|
+
// a NamedNodeMap `item()` shim) to THAT copy. But at proof time the HTML tree
|
|
7
|
+
// is built by the *external* `parse5-htmlparser2-tree-adapter`, which uses the
|
|
8
|
+
// separately-installed `domhandler` — a different, UNPATCHED copy. The `xpath`
|
|
9
|
+
// engine then fails `isNodeLike()` on every node (it needs a string `nodeName`)
|
|
10
|
+
// and throws "Context node does not appear to be a valid DOM node", so ALL
|
|
11
|
+
// xPath / xPath+jsonPath response redactions fail during `runAttestorProof`'s
|
|
12
|
+
// receipt validation. (Regex-only redactions escape it — they never touch the
|
|
13
|
+
// parse5/xpath path.) Running attestor-core from source works because there is
|
|
14
|
+
// then a single shared `domhandler`; the bug is only in the bundled `lib`.
|
|
15
|
+
//
|
|
16
|
+
// Rather than fork attestor-core, apply the identical patch to the external
|
|
17
|
+
// `domhandler` the adapter actually uses, so its tree nodes are DOM-like. This
|
|
18
|
+
// mirrors attestor-core's own `patch-parse5-tree`. Idempotent and
|
|
19
|
+
// version-agnostic. Import it before any proving path runs.
|
|
20
|
+
//
|
|
21
|
+
// Remove once attestor-core externalizes `domhandler` in its build (one shared
|
|
22
|
+
// copy → its own patch suffices).
|
|
23
|
+
import { Element, Node } from 'domhandler';
|
|
24
|
+
const PATCH_FLAG = Symbol.for('reclaim.domhandler.domCompatPatched');
|
|
25
|
+
const proto = Node.prototype;
|
|
26
|
+
if (!proto[PATCH_FLAG]) {
|
|
27
|
+
// `xpath`'s isNodeLike() requires a string `nodeName`; domhandler nodes carry
|
|
28
|
+
// the tag under `.name`.
|
|
29
|
+
Object.defineProperty(Node.prototype, 'nodeName', {
|
|
30
|
+
configurable: true,
|
|
31
|
+
get() {
|
|
32
|
+
return this.name;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(Node.prototype, 'localName', {
|
|
36
|
+
configurable: true,
|
|
37
|
+
get() {
|
|
38
|
+
return this.name;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
// Attribute-predicate steps (`[@id=…]`, `contains(@class,…)`) walk the
|
|
42
|
+
// NamedNodeMap via `.item(i)`; give each returned attribute node the DOM
|
|
43
|
+
// shape `xpath` expects (nodeType 2 + localName).
|
|
44
|
+
const attrsGetter = Object.getOwnPropertyDescriptor(Element.prototype, 'attributes')?.get;
|
|
45
|
+
if (attrsGetter) {
|
|
46
|
+
Object.defineProperty(Element.prototype, 'attributes', {
|
|
47
|
+
configurable: true,
|
|
48
|
+
get() {
|
|
49
|
+
const attrs = attrsGetter.call(this);
|
|
50
|
+
attrs.item = (i) => {
|
|
51
|
+
const attr = attrs[i];
|
|
52
|
+
return { ...attr, nodeType: 2, localName: attr.name };
|
|
53
|
+
};
|
|
54
|
+
return attrs;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
proto[PATCH_FLAG] = true;
|
|
59
|
+
}
|