@reclaimprotocol/agent 0.1.18 → 0.1.21
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 +6 -2
- package/lib/authoring/backend.d.ts +4 -0
- package/lib/authoring/tools.js +4 -1
- package/lib/cdp/capture.d.ts +2 -2
- package/lib/cdp/capture.js +25 -21
- package/lib/cdp/client.d.ts +39 -1
- package/lib/cdp/client.js +125 -1
- package/lib/mcp/api/operations.gen.js +33 -9
- package/lib/mcp/instructions.js +27 -3
- package/lib/mcp/remap_errors.js +2 -1
- package/lib/mcp/start.js +16 -9
- package/lib/mcp/tools/agent/attach.d.ts +18 -4
- package/lib/mcp/tools/agent/attach.js +142 -52
- package/lib/mcp/tools/agent/build.d.ts +9 -2
- package/lib/mcp/tools/agent/build.js +15 -7
- package/lib/mcp/tools/agent/capture.d.ts +1 -0
- package/lib/mcp/tools/agent/capture.js +8 -1
- package/lib/mcp/tools/agent/eval.js +2 -3
- package/lib/mcp/tools/agent/how-it-works.d.ts +1 -1
- package/lib/mcp/tools/agent/how-it-works.js +1 -1
- package/lib/mcp/tools/agent/index.d.ts +1 -1
- package/lib/mcp/tools/agent/index.js +1 -1
- package/lib/mcp/tools/agent/publish.d.ts +4 -1
- package/lib/mcp/tools/agent/publish.js +42 -3
- package/lib/mcp/tools/agent/session.d.ts +2 -2
- package/lib/mcp/tools/agent/session.js +21 -5
- package/lib/mcp/tools/agent/shared.js +4 -6
- package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
- package/lib/mcp/tools/authenticate/authenticate.js +13 -6
- package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -0
- package/lib/mcp/tools/authenticate/builder-access.js +31 -0
- package/lib/mcp/tools/authenticate/index.d.ts +1 -0
- package/lib/mcp/tools/authenticate/index.js +1 -0
- package/lib/old/login-cdp.js +11 -3
- package/lib/old/tools/build.d.ts +8 -1
- package/lib/old/tools/build.js +6 -2
- package/lib/old/tools/publish.d.ts +10 -1
- package/lib/old/tools/publish.js +49 -3
- package/lib/provider/draft.d.ts +20 -0
- package/lib/provider/draft.js +75 -51
- package/lib/provider/index.d.ts +1 -1
- package/lib/provider/index.js +1 -1
- package/lib/provider/secrets.d.ts +37 -0
- package/lib/provider/secrets.js +147 -6
- package/lib/provider/to-version.d.ts +7 -0
- package/lib/provider/to-version.js +11 -1
- package/package.json +2 -2
- package/skill/SKILL.builder.md +44 -94
- package/skill/SKILL.common.md +102 -0
- package/skill/SKILL.old.md +21 -73
- package/skill/builder-mode.md +2 -2
- package/skill/injection-api.md +95 -10
- package/skill/old-mode.md +2 -2
- package/src/authoring/backend.ts +4 -0
- package/src/authoring/tools.ts +4 -1
- package/src/cdp/capture.ts +24 -24
- package/src/cdp/client.ts +197 -2
- package/src/mcp/api/operations.gen.ts +33 -9
- package/src/mcp/instructions.ts +36 -3
- package/src/mcp/remap_errors.ts +2 -1
- package/src/mcp/start.ts +24 -9
- package/src/mcp/tools/agent/attach.ts +198 -57
- package/src/mcp/tools/agent/build.ts +20 -6
- package/src/mcp/tools/agent/capture.ts +10 -1
- package/src/mcp/tools/agent/eval.ts +2 -3
- package/src/mcp/tools/agent/how-it-works.ts +1 -1
- package/src/mcp/tools/agent/index.ts +1 -1
- package/src/mcp/tools/agent/publish.ts +46 -2
- package/src/mcp/tools/agent/session.ts +29 -8
- package/src/mcp/tools/agent/shared.ts +5 -10
- package/src/mcp/tools/authenticate/authenticate.ts +17 -9
- package/src/mcp/tools/authenticate/builder-access.ts +42 -0
- package/src/mcp/tools/authenticate/index.ts +4 -0
- package/src/old/login-cdp.ts +13 -3
- package/src/old/tools/build.ts +7 -1
- package/src/old/tools/publish.ts +55 -4
- package/src/provider/draft.ts +98 -56
- package/src/provider/index.ts +9 -1
- package/src/provider/secrets.ts +171 -6
- package/src/provider/to-version.ts +12 -1
- package/lib/authoring/find.d.ts +0 -3
- package/lib/authoring/find.js +0 -54
- package/lib/authoring/propose.d.ts +0 -3
- package/lib/authoring/propose.js +0 -109
- package/lib/authoring/replay.d.ts +0 -10
- package/lib/authoring/replay.js +0 -34
- package/lib/cli/args.d.ts +0 -14
- package/lib/cli/args.js +0 -19
- package/lib/cli/commands.d.ts +0 -1
- package/lib/cli/commands.js +0 -19
- package/lib/cli.d.ts +0 -1
- package/lib/cli.js +0 -7
- package/lib/mcp/credentials/authenticate.d.ts +0 -3
- package/lib/mcp/credentials/authenticate.js +0 -108
- package/lib/mcp/credentials/build.d.ts +0 -5
- package/lib/mcp/credentials/build.js +0 -18
- package/lib/mcp/credentials/cache.d.ts +0 -70
- package/lib/mcp/credentials/cache.js +0 -131
- package/lib/mcp/credentials/env-file.d.ts +0 -25
- package/lib/mcp/credentials/env-file.js +0 -71
- package/lib/mcp/credentials/import.d.ts +0 -2
- package/lib/mcp/credentials/import.js +0 -52
- package/lib/mcp/credentials/issue.d.ts +0 -2
- package/lib/mcp/credentials/issue.js +0 -84
- package/lib/mcp/credentials/keypair.d.ts +0 -17
- package/lib/mcp/credentials/keypair.js +0 -48
- package/lib/mcp/credentials/link.d.ts +0 -3
- package/lib/mcp/credentials/link.js +0 -107
- package/lib/mcp/credentials/list-cached.d.ts +0 -2
- package/lib/mcp/credentials/list-cached.js +0 -55
- package/lib/mcp/credentials/owner-key.d.ts +0 -23
- package/lib/mcp/credentials/owner-key.js +0 -58
- package/lib/mcp/credentials/paths.d.ts +0 -12
- package/lib/mcp/credentials/paths.js +0 -35
- package/lib/mcp/credentials/poll.d.ts +0 -27
- package/lib/mcp/credentials/poll.js +0 -61
- package/lib/mcp/credentials/register.d.ts +0 -18
- package/lib/mcp/credentials/register.js +0 -30
- package/lib/mcp/credentials/resolve-owner-key.d.ts +0 -12
- package/lib/mcp/credentials/resolve-owner-key.js +0 -68
- package/lib/mcp/credentials/session-cache.d.ts +0 -14
- package/lib/mcp/credentials/session-cache.js +0 -42
- package/lib/mcp/credentials/status.d.ts +0 -3
- package/lib/mcp/credentials/status.js +0 -76
- package/lib/mcp/register.d.ts +0 -38
- package/lib/mcp/register.js +0 -80
- package/lib/mcp/tools/analyze.d.ts +0 -3
- package/lib/mcp/tools/analyze.js +0 -26
- package/lib/mcp/tools/attach.d.ts +0 -11
- package/lib/mcp/tools/attach.js +0 -72
- package/lib/mcp/tools/capture.d.ts +0 -10
- package/lib/mcp/tools/capture.js +0 -142
- package/lib/mcp/tools/eval.d.ts +0 -5
- package/lib/mcp/tools/eval.js +0 -127
- package/lib/mcp/tools/find.d.ts +0 -3
- package/lib/mcp/tools/find.js +0 -60
- package/lib/mcp/tools/owner-key.d.ts +0 -2
- package/lib/mcp/tools/owner-key.js +0 -63
- package/lib/mcp/tools/proof.d.ts +0 -3
- package/lib/mcp/tools/proof.js +0 -214
- package/lib/mcp/tools/propose.d.ts +0 -9
- package/lib/mcp/tools/propose.js +0 -75
- package/lib/mcp/tools/publish.d.ts +0 -3
- package/lib/mcp/tools/publish.js +0 -59
- package/lib/mcp/tools/replay.d.ts +0 -3
- package/lib/mcp/tools/replay.js +0 -46
- package/lib/mcp/tools/session.d.ts +0 -10
- package/lib/mcp/tools/session.js +0 -29
- package/lib/mcp/tools/verification/diagnose.d.ts +0 -11
- package/lib/mcp/tools/verification/diagnose.js +0 -132
- package/lib/mcp/verifications/build.d.ts +0 -5
- package/lib/mcp/verifications/build.js +0 -14
- package/lib/mcp/verifications/create.d.ts +0 -3
- package/lib/mcp/verifications/create.js +0 -57
- package/lib/mcp/verifications/find.d.ts +0 -3
- package/lib/mcp/verifications/find.js +0 -37
- package/lib/mcp/verifications/get.d.ts +0 -3
- package/lib/mcp/verifications/get.js +0 -26
- package/lib/mcp/verifications/update.d.ts +0 -3
- package/lib/mcp/verifications/update.js +0 -78
- package/lib/old/tools/get-version-config.d.ts +0 -17
- package/lib/old/tools/get-version-config.js +0 -47
- package/lib/routes.gen.d.ts +0 -271
- package/lib/routes.gen.js +0 -73
- package/lib/types/openapi.d.ts +0 -4
- package/lib/types/openapi.gen.d.ts +0 -3967
- package/lib/types/openapi.gen.js +0 -5
- package/lib/types/openapi.js +0 -1
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.21",
|
|
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.21",
|
|
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
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> **Terminology note:** A **provider** is the recipe you author here — the
|
|
4
|
-
> instructions for extracting a data point from a website (the
|
|
5
|
-
> `responseMatches` / `responseRedactions` spec, identified by a
|
|
6
|
-
> `providerId`). Always call it a **provider**; never call a provider a
|
|
7
|
-
> "verification." A **verification** (or **verification session**) is the
|
|
8
|
-
> separate runtime act in which an end-user proves a data point *using* a
|
|
9
|
-
> provider — reserve that word for the session/result, never for the recipe.
|
|
10
|
-
> If a developer calls the recipe a "verification," gently use "provider"
|
|
11
|
-
> yourself rather than correcting them.
|
|
12
|
-
|
|
13
|
-
You drive the browser through the capture → draft → replay → prove loop using the `reclaim-agent` MCP server. **Your default stance is driver, not orchestrator.** Only pause to ask the dev for things that genuinely require a human: typing credentials, completing 2FA, solving a captcha, confirming an ambiguous value. Navigation, reloads, DOM inspection, request capture — all yours.
|
|
1
|
+
<!-- common: header -->
|
|
14
2
|
|
|
15
3
|
## Getting authenticated
|
|
16
4
|
|
|
@@ -26,11 +14,7 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
26
14
|
|
|
27
15
|
## The loop
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
- `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest path; works out of the box.
|
|
31
|
-
- `attach` — connect to a Chrome the dev is already running. **Important Chrome 136+ gotcha:** the dev's Chrome must be launched with BOTH `--remote-debugging-port=9222` AND `--user-data-dir=<some-non-default-path>`. Since Chrome 136 (May 2025), the debug-port flag is silently ignored when the default user profile is in use. If `attach` fails, tell them this verbatim and offer to switch to `dedicated`.
|
|
32
|
-
|
|
33
|
-
Re-call with the chosen `attachMode`.
|
|
17
|
+
<!-- common: step-attach -->
|
|
34
18
|
|
|
35
19
|
2. **Pick a tab and start a capture.** Call `list_tabs` and pick a tab (if one is already on the right origin use it; otherwise the first is usually fine). Then call `start_capture(tabId)` — it returns a **`captureId`** that is your session handle for the rest of the flow: `navigate`, `eval_in_page`, `wait_for_page`, `get_cookies`, `list_requests`, `find_requests_containing`, `propose_provider` all take it as their first argument. Capturing from the start is harmless (it just also records the login navigation). Then `navigate(captureId, url)` to the site you inferred from the dev's request (e.g. "GitHub username" → `https://github.com`). Don't ask the dev to navigate.
|
|
36
20
|
|
|
@@ -53,18 +37,9 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
53
37
|
|
|
54
38
|
5. **Trigger the request.** Capture has been live since step 2, so just make the relevant API call fire: issue a reload with `eval_in_page(captureId, "location.reload()")`. For some pages you may need to click a specific element or call a SPA route — use `eval_in_page` to do that programmatically rather than asking the dev to click.
|
|
55
39
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
7. **Find the request.** Call `find_requests_containing(captureId, value)`. For each top candidate (limit ≤ 5), call `analyze_request_constraints` and surface its `concerns` to the dev verbatim. Skip candidates flagged `signedRequest` or `botChallenge` unless none others are available. Concerns flagged `csrfDependency`, `shortLivedToken`, or `graphql` are informational — surface them and proceed.
|
|
59
|
-
|
|
60
|
-
**Strongly prefer authenticated, user-specific endpoints over public pages.** A proof against a public endpoint (e.g. `https://github.com/<username>`) attests only that "this URL returned this value at this time" — anyone in the world could fetch the same URL and prove the same thing. The proof says nothing about who the prover _is_. To prove a fact about the dev, the captured request needs to be one whose **response varies with authentication** — typically an API endpoint that returns the logged-in user's data (e.g. `GET https://api.github.com/user`, `https://twitter.com/i/api/.../UserByScreenName?...&forCurrentUser=true`).
|
|
40
|
+
<!-- common: step-identify-value -->
|
|
61
41
|
|
|
62
|
-
|
|
63
|
-
- Have `Cookie`, `Authorization`, or other secret headers (analyze_request_constraints will surface these via `splitSecrets`).
|
|
64
|
-
- Have URL paths suggesting authenticated data: `/me`, `/user`, `/settings`, `/api/...`, `/graphql` with viewer queries.
|
|
65
|
-
- Return JSON keyed to the dev's identity (login/username/id fields next to the target value).
|
|
66
|
-
|
|
67
|
-
If the only candidates are public-page HTML responses, tell the dev: "The value appears on a public page, so a proof against this endpoint won't bind to your identity — anyone could prove the same fact. I can still build it if you just want to attest the value, but for an identity-bound proof we need to capture an authenticated request. Want to keep digging, or proceed anyway?" Then wait for their call.
|
|
42
|
+
<!-- common: step-find-request -->
|
|
68
43
|
|
|
69
44
|
8. **Decide whether to OPRF the value** (privacy from the attestor). Before calling `propose_provider`, decide if the captured value should be hashed end-to-end so the attestor never sees it in cleartext. Four options:
|
|
70
45
|
- **No hash** (default). Value travels via `paramValues` + `{{name}}` template; attestor sees the cleartext substring. Simplest, fewest moving parts. Right call for non-sensitive values (public IPs from `ifconfig.me`, public usernames the dev is happy to expose, public repo names).
|
|
@@ -83,6 +58,8 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
83
58
|
|
|
84
59
|
**Hard constraint:** OPRF-hashed values are capped at **62 bytes**. If the value is longer and the dev picks any hash, surface that and propose either a tighter `target` (e.g. just the email local-part) or no hash. `propose_provider` will return a clean error if you try anyway.
|
|
85
60
|
|
|
61
|
+
**Hard constraint: OPRF and the request URL are mutually exclusive for the same value.** The entire point of `hash` is that the attestor never sees the value in cleartext — but any value templated into the request URL (`{{name}}`, `{{context.<name>}}`, or left literal) is always signed into the attestor's context in the clear, regardless of hashing. So if the target's value ALSO appears anywhere in the request URL, hashing it is not just pointless, it doesn't verify. Check this before requesting `hash` for a target: if that value shows up in the captured URL too, either drop `hash` for that target or capture a different request whose URL doesn't carry the value. `propose_provider` throws a clean error if you try anyway — same as the byte cap above.
|
|
62
|
+
|
|
86
63
|
9. **Draft.** Call `propose_provider`. It returns a **`draftId`** (the handle you pass to `replay_request` and `run_proof`), the secret-free `provider`, and `secretRefs`. The recipe **and the captured secret values** are held server-side under that `draftId` — the secret values never enter the transcript, so you carry the `draftId`, not the provider JSON, into replay/prove.
|
|
87
64
|
|
|
88
65
|
**Extracting MORE THAN ONE value?** Prefer a **single request whose response contains ALL of them**, and draft them together — the provider gets one `responseMatches` + `responseRedactions` entry per value. First locate that request: run `find_requests_containing` for each value and pick a `requestId` that appears for all. Then pass a `targets` array — one entry per value, each with its `value` (exactly as it appears) and a distinct `name` (its parameter):
|
|
@@ -106,16 +83,16 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
106
83
|
|
|
107
84
|
```json
|
|
108
85
|
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
"responseMatches": [
|
|
87
|
+
{ "type": "contains", "value": "\"login\":\"{{username}}\"" }
|
|
88
|
+
],
|
|
89
|
+
"responseRedactions": [
|
|
90
|
+
{
|
|
91
|
+
"xPath": "//script[@id=\"client-env\"]",
|
|
92
|
+
"regex": "\"login\":\"(?<username>[A-Za-z0-9_-]+)\""
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"paramValues": { "username": "Scratch-net" }
|
|
119
96
|
}
|
|
120
97
|
```
|
|
121
98
|
|
|
@@ -123,28 +100,21 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
123
100
|
|
|
124
101
|
```json
|
|
125
102
|
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
"responseMatches": [
|
|
104
|
+
{ "type": "contains", "value": "\"login\":\"{{username}}\"" }
|
|
105
|
+
],
|
|
106
|
+
"responseRedactions": [
|
|
107
|
+
{
|
|
108
|
+
"xPath": "//script[@id=\"client-env\"]",
|
|
109
|
+
"regex": "\"login\":\"(?<username>[A-Za-z0-9_-]+)\"",
|
|
110
|
+
"hash": "oprf-raw"
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"paramValues": { "username": "Scratch-net" }
|
|
137
114
|
}
|
|
138
115
|
```
|
|
139
116
|
|
|
140
|
-
|
|
141
|
-
- Do not replace the `contains` template with a hand-crafted regex.
|
|
142
|
-
- Do not hand-write a named-capture regex unless `hash` was requested AND the synthesizer's output doesn't already have one. (It will, for any digit/slug/email-shaped target.)
|
|
143
|
-
- Do not remove `paramValues` — that's how the template resolves at proof time.
|
|
144
|
-
- The redaction's xPath/jsonPath is what the prover reveals; the match's template is what the attestor verifies post-substitution. They serve different purposes — don't conflate them.
|
|
145
|
-
- Do not prefix the match-template's param with `context.`/`context_` — that namespace is reserved for consumer-supplied URL/body params (step 10). Match templates are extraction params and stay bare.
|
|
146
|
-
|
|
147
|
-
Permitted edits: narrowing the `contains` template, picking a more distinctive xPath class/id if the synthesizer's pick is too generic, fixing the `name` field, adding/removing secret headers. If you're tempted to rewrite the provider from scratch, call `propose_provider` again with different inputs instead.
|
|
117
|
+
<!-- common: rewrite-shape-block -->
|
|
148
118
|
|
|
149
119
|
10. **Parameterize user-specific URL segments** so the provider isn't user-specific at the URL level. The matcher already populates `paramValues` with the _captured value_ (e.g. `{username: "Scratch-net"}` for a username target). If the URL ALSO contains user-identifying segments, those are **consumer-supplied context params** — at verification time their values come from the Consumer's session `context`, not from extraction. They use a namespaced placeholder, and the namespace differs by mode:
|
|
150
120
|
|
|
@@ -153,7 +123,13 @@ replace with `{{context.<name>}}`. E.g. `https://github.com/Scratch-net/repos`
|
|
|
153
123
|
- Generic segments (`/api/v3`, `/users`, `/repos`) stay literal.
|
|
154
124
|
- If the URL has no user-identifying segments (e.g. `/settings/profile`), leave it untouched — the matcher already handled the value via paramValues.
|
|
155
125
|
|
|
156
|
-
**
|
|
126
|
+
**Marking a value secret:** name ANY param with `SECRET` anywhere in the key (case-insensitive) — a bare `{{SECRET_x}}` (e.g. a value extracted earlier in a multi-request flow) just as much as a consumer-supplied `{{context.SECRET_apiKey}}` — and it's routed to `secretParams.paramValues` (`classifyParamName`/`splitSecretParamValues`), same naming convention `reclaim-inapp-sdk`/`reclaim-portal` use for their `paramValues`. The `context.` prefix is a BUILDER-only namespace (consumer-supplied vs extracted-from-traffic); the attestor itself just looks up `{{whatever}}` in `paramValues`/`secretParams.paramValues` and doesn't care about the prefix. Headers are secret by the OPPOSITE rule — an ALLOWLIST, not a name check: any header not in a small fixed public list (`user-agent`, `accept`, `accept-language`, `accept-encoding`, `sec-fetch-*`, `origin`, `x-requested-with`, `sec-ch-ua*`) is secret by default, including `Cookie`/`Authorization` and anything custom (`classifyHeader`/`splitSecrets`).
|
|
127
|
+
|
|
128
|
+
A secret value CAN be used in the URL or `geoLocation` — write-redaction genuinely hides it there, but only up to a small character budget, and that budget is a TOTAL summed across every secret value used in that URL, not a per-value allowance (`secretUrlCharBudget`): 2 characters under `key-update` (the default), 12 under `zk`. Over budget, the value is effectively still visible to the attestor no matter how it's marked. Since the actual value doesn't exist until a verification session supplies one, this can only be checked once that value is known — at verification time, not at publish time (`assertSecretParamValuesFitUrlBudget` in `verify/run.ts`); a captured secret HEADER whose value happens to also sit in the URL gets the same budget check at DRAFT time instead, since its value is already known then (`assertNoSecretUrlConflict`). Using longer than limit secrets in url actually causes the claim creation from attestor to fail.
|
|
129
|
+
|
|
130
|
+
The value of any parameter marked secret is not included in the final proof, omitted from it.
|
|
131
|
+
|
|
132
|
+
<!-- common: no-rename-param -->
|
|
157
133
|
|
|
158
134
|
**How the verification client resolves these at runtime**:
|
|
159
135
|
|
|
@@ -180,20 +156,7 @@ replace with `{{context.<name>}}`. E.g. `https://github.com/Scratch-net/repos`
|
|
|
180
156
|
|
|
181
157
|
**Missing signing credential (`errorKind: "missing-owner-key"`)** — the dev hasn't set up an eth proof-owner key yet. Tell them what's happening, then drive the bootstrap yourself: call `resolve_owner_key`; if it returns no key, ask once _"shall I mint a fresh eth key via `issue_credentials` (or `import_credentials` if you already hold one)? It's written to the project `.env` (mode 0600, gitignored) for this and future sessions."_; on yes, call `issue_credentials` (or `import_credentials`) and retry the same `run_proof` call. **No restart needed** — the key is read live from `.env` on each `run_proof`, and the existing `draftId` from `propose_provider` is still valid.
|
|
182
158
|
|
|
183
|
-
|
|
184
|
-
- `error` — the SDK's thrown error message verbatim
|
|
185
|
-
- `sentParams` — the exact `params` object the SDK saw, post-template substitution (lets you spot mutation / substitution surprises)
|
|
186
|
-
|
|
187
|
-
**Deeper tracing (opt-in).** If the dev launches Claude Code with `RECLAIM_AGENT_TRACE=1` in the MCP server's env, `run_proof` ALSO captures the SDK's full pino log stream into `result.attestorLogs` and persists a per-call dump to `~/.reclaim/proof-runs/<timestamp>.json` (surfaced as `runLogPath`). Off by default because the trace stream is verbose. Suggest turning it on when investigating a recurring proof failure.
|
|
188
|
-
|
|
189
|
-
**The attestor is reliable. If it errors, the cause is in the provider, the network, or the prover-side code — not "the attestor can't handle this site".** Do not invent failure modes like "HTML is too large", "TLS records are chunked", "the attestor matches against gzipped bytes" (it doesn't if the provider has `Accept-Encoding: identity`, which `propose_provider` always sets), or "xPath doesn't work on this DOM". These are wrong and they waste the dev's time. Instead:
|
|
190
|
-
1. Show the `error` field to the dev verbatim.
|
|
191
|
-
2. If `sentParams` differs from what you expected — e.g. `{{name}}` substituted to the wrong value, headers got mutated, redactions look different from `propose_provider`'s output — surface that.
|
|
192
|
-
3. Only then propose a hypothesis, and tie it to that evidence. If the diagnosis is unclear, suggest the dev set `RECLAIM_AGENT_TRACE=1` and rerun for full attestor logs.
|
|
193
|
-
|
|
194
|
-
**Do not auto-retry with a different `attestorUrl`.** There is one live public attestor right now; backup hostnames (`attestor-2.*`, etc.) **do not exist**. `attestorUrl` is only for when the dev explicitly supplies a private/staging URL.
|
|
195
|
-
|
|
196
|
-
**Missing ZK circuit files:** if the error says "ZK circuit resources are missing" and points at a `circuit.wasm` path, the npm postinstall didn't run (commonly because the dev used `npm install --ignore-scripts` or a corp policy disabled it). Tell the dev to run `npm run download:zk-files` from the project root and then retry `run_proof` — no need to redo capture or propose. The MCP server doesn't need a restart either; the next `run_proof` call will see the files.
|
|
159
|
+
<!-- common: prove-tail -->
|
|
197
160
|
|
|
198
161
|
13. **Publish the proved provider.** `provider` is the exact JSON from step 9 with the step-10 URL parameterisation applied, and `initialUrl` is the page where the user logs in / the value is displayed (e.g. `https://github.com/Syed`) — **not** the API endpoint URL inside the provider.
|
|
199
162
|
|
|
@@ -210,33 +173,20 @@ replace with `{{context.<name>}}`. E.g. `https://github.com/Scratch-net/repos`
|
|
|
210
173
|
|
|
211
174
|
When done, tell the developer the version is live (PRIVATE) or submitted for review (PUBLIC), and that they can integrate using the `providerId`.
|
|
212
175
|
|
|
213
|
-
|
|
176
|
+
- **If a browser is still attached**, `create_provider_version_from_capture`'s response carries a `_notes` reminder about it. Surface that to the dev and ask whether they want to keep the session open for further edits — only call `dispose_browser` once they confirm they're done. Never close it unprompted; a `builder`-mode session is quota-accounted, but the dev may still be mid-workflow.
|
|
214
177
|
|
|
215
|
-
|
|
178
|
+
<!-- common: concerns-section -->
|
|
216
179
|
|
|
217
180
|
## Things to never do
|
|
218
181
|
|
|
219
|
-
|
|
220
|
-
- Never call `run_proof` before `replay_request` succeeds. Attestor calls are expensive.
|
|
221
|
-
- Never pretend a proof succeeded when `verified: false` came back. Show the error and iterate.
|
|
182
|
+
<!-- common: never-do-common -->
|
|
222
183
|
- **Never hand-craft `responseMatches` or `responseRedactions`.** If you find yourself typing a regex by hand, OR writing `(?<name>...)`-style patterns by hand, OR copying the captured value literally into the match — stop. The synthesizer in `propose_provider` already produces the canonical shape: `contains` template + paramValues for the match, xPath/jsonPath for the redaction — plus a value-isolating named-capture `regex` (for every xPath/jsonPath anchor — the reveal is a byte range, never the bare value). The named-capture regex in the redaction is the synthesizer's job, not yours.
|
|
223
|
-
- Never write a literal value into `responseMatches.value` — the synthesizer always emits `{{name}}` templates with paramValues. A literal value bakes the original user into the provider.
|
|
224
|
-
- Never pass `hash` to `propose_provider` for a free-form (unicode / spaces / punctuation) target — the synthesizer will error, and the right move is either to use a tighter `target` or accept cleartext (no `hash`).
|
|
225
184
|
- Never accept "the synthesizer's xPath looks too narrow, let me write a regex" — the synthesizer already pairs the xPath with a structure-anchored named-capture regex (for HTML) that both reveals the element and isolates the value. If extraction still looks wrong, re-run `propose_provider` with a better request/target — don't hand-write patterns.
|
|
185
|
+
- Mark ANY param secret (not just consumer-supplied `context.*` ones) by naming it with `SECRET` (e.g. `{{SECRET_x}}` or `{{context.SECRET_apiKey}}`). It CAN be used in the URL, geoLocation, request body, response match, response redactions — write-redaction hides it there. Any value of a parameter marked secret is not included in the proof. There's also a limitation for secret marked variables secrets in url: only up to a small TOTAL character budget across every secret value in a URL (2 chars under `key-update`, the default; 12 under `zk`). That's checked at verification time against the consumer's real value, not at publish time (see step 10). Headers are secret by allowlist, not by name — anything not on a small fixed public list is secret by default.
|
|
226
186
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
If `attach_chrome` errors, times out, or `waitForCdpReady` reports the port isn't answering, walk through these in order before retrying:
|
|
230
|
-
|
|
231
|
-
- **Browser binary not found.** If the error lists candidate paths it tried (Program Files Chrome, LOCALAPPDATA Chrome, Edge), every one missed. Either the dev has Chrome installed somewhere unusual, or doesn't have Chrome/Edge at all. Walk them through setting `RECLAIM_AGENT_CHROME_PATH` to the actual binary path:
|
|
232
|
-
```powershell
|
|
233
|
-
setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"
|
|
234
|
-
```
|
|
235
|
-
`setx` (not `$env:`) writes to the user-level Windows env, so future shells and future Claude Code launches inherit it. **Then fully restart Claude Code** — MCP servers snapshot env vars at launch, so the running reclaim-agent has the old empty value. After restart, the new MCP process picks up the env. On macOS/Linux: `export RECLAIM_AGENT_CHROME_PATH=/path/to/chrome` in the shell, then restart Claude Code from that same shell so the var propagates.
|
|
236
|
-
- **Pre-existing `chrome.exe` processes hijack the new launch (Windows).** If any Chrome is already running, a new launch with `--remote-debugging-port` gets delegated to the existing process group and the debug port silently doesn't open. Tell the dev to kill all `chrome.exe` first (`taskkill /F /IM chrome.exe` in cmd/PowerShell, or via Task Manager), then retry. Affects both `attach` and `dedicated` modes when the existing instance was launched with the same profile dir.
|
|
237
|
-
- **Chrome 136+ silently disables remote debugging on the default user profile.** Already noted in step 1, but worth raising again here: the only fix is a non-default `--user-data-dir`. `dedicated` mode is always safe; `attach` mode needs the dev to have launched with both flags.
|
|
187
|
+
<!-- common: first-time-issues-intro -->
|
|
238
188
|
|
|
239
|
-
|
|
189
|
+
<!-- common: credential-intro -->
|
|
240
190
|
|
|
241
191
|
1. **Call `resolve_owner_key`.** It checks `RECLAIM_PRIVATE_KEY` (MCP env) → the project `.env` → `RECLAIM_PRIVATE_KEY_FILE`, and returns only the derived address.
|
|
242
192
|
- `{ ready: true, address }` → call `run_proof(captureId, draftId, address)`. No prompts.
|
|
@@ -245,9 +195,9 @@ If `attach_chrome` errors, times out, or `waitForCdpReady` reports the port isn'
|
|
|
245
195
|
- **Create a new one** → `issue_credentials({})`. (Fine default for test proofs — owner identity doesn't matter.)
|
|
246
196
|
- Either way the key is written to the project `.env` (mode 0600; `.env` is added to `.gitignore`) — never to `~/.reclaim`.
|
|
247
197
|
- Then call `resolve_owner_key` again and `run_proof` with the returned address.
|
|
248
|
-
|
|
198
|
+
<!-- common: credential-step2 -->
|
|
249
199
|
- **Advanced:** setting `RECLAIM_PRIVATE_KEY` directly in the MCP server's env also works, but env vars are snapshotted at Claude Code startup, so that route needs a full restart (which invalidates every `draftId`). The `.env` flow above avoids that — prefer it.
|
|
250
|
-
|
|
200
|
+
<!-- common: mcp-config-bullet -->
|
|
251
201
|
|
|
252
202
|
If `replay_request` or `run_proof` errors with `unknown draftId` (or `no capture`):
|
|
253
203
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<!-- section: header -->
|
|
2
|
+
# Authoring a Reclaim Provider
|
|
3
|
+
|
|
4
|
+
> **Terminology note:** A **provider** is the recipe you author here — the
|
|
5
|
+
> instructions for extracting a data point from a website (the
|
|
6
|
+
> `responseMatches` / `responseRedactions` spec, identified by a
|
|
7
|
+
> `providerId`). Always call it a **provider**; never call a provider a
|
|
8
|
+
> "verification." A **verification** (or **verification session**) is the
|
|
9
|
+
> separate runtime act in which an end-user proves a data point *using* a
|
|
10
|
+
> provider — reserve that word for the session/result, never for the recipe.
|
|
11
|
+
> If a developer calls the recipe a "verification," gently use "provider"
|
|
12
|
+
> yourself rather than correcting them.
|
|
13
|
+
|
|
14
|
+
You drive the browser through the capture → draft → replay → prove loop using the `reclaim-agent` MCP server. **Your default stance is driver, not orchestrator.** Only pause to ask the dev for things that genuinely require a human: typing credentials, completing 2FA, solving a captcha, confirming an ambiguous value. Navigation, reloads, DOM inspection, request capture — all yours.
|
|
15
|
+
|
|
16
|
+
<!-- section: step-attach -->
|
|
17
|
+
1. **Attach.** Call `attach_browser`. If it returns `needsChoice: true`, ask the dev which browser source they want:
|
|
18
|
+
- `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest local path; works out of the box.
|
|
19
|
+
- `attach` — connect to a Chrome the dev is already running. **Important Chrome 136+ gotcha:** the dev's Chrome must be launched with BOTH `--remote-debugging-port=9222` AND `--user-data-dir=<some-non-default-path>`. Since Chrome 136 (May 2025), the debug-port flag is silently ignored when the default user profile is in use. If `attach` fails, tell them this verbatim and offer to switch to `dedicated`.
|
|
20
|
+
- `builder` — allocate a quota-accounted remote Popcorn browser. Obtain an `orgId` with `list_builder_organizations` in old-devtools mode or the generated `list_orgs` tool in Builder mode. If Builder answers 401, authenticate with `authenticate_builder` in old-devtools mode or the existing `authenticate` flow in Builder mode, then retry. Return the FULL `liveViewUrl` to the dev verbatim — on its own line, never abbreviated, elided, or wrapped in prose that truncates it — so it stays one clean, copy-pasteable string; they'll share it with the volunteer operating the browser. This works in both publishing modes and consumes one VERIFICATION-pool use under the chosen organization's normal plan restrictions.
|
|
21
|
+
- `custom` — connect to a `ws://` or `wss://` CDP URL supplied by the dev. The URL is a credential: never repeat it, log it, or persist it. An optional `liveViewUrl` may be returned — if so, share it FULL and verbatim, same as `builder` above.
|
|
22
|
+
|
|
23
|
+
Re-call with the chosen `mode` and its required fields.
|
|
24
|
+
|
|
25
|
+
<!-- section: step-identify-value -->
|
|
26
|
+
6. **Identify the value from the DOM.** `eval_in_page` to read the displayed value (e.g. the logged-in username from `meta[name="user-login"]`'s `content` attribute). If the value is unambiguous, proceed without asking. Only confirm with the dev when there are multiple plausible interpretations (e.g. the page shows both a display name "John Doe" and a handle "@jdoe" and you need to pick which to prove).
|
|
27
|
+
|
|
28
|
+
<!-- section: step-find-request -->
|
|
29
|
+
7. **Find the request.** Call `find_requests_containing(captureId, value)`. For each top candidate (limit ≤ 5), call `analyze_request_constraints` and surface its `concerns` to the dev verbatim. Skip candidates flagged `signedRequest` or `botChallenge` unless none others are available. Concerns flagged `csrfDependency`, `shortLivedToken`, or `graphql` are informational — surface them and proceed.
|
|
30
|
+
|
|
31
|
+
**Strongly prefer authenticated, user-specific endpoints over public pages.** A proof against a public endpoint (e.g. `https://github.com/<username>`) attests only that "this URL returned this value at this time" — anyone in the world could fetch the same URL and prove the same thing. The proof says nothing about who the prover _is_. To prove a fact about the dev, the captured request needs to be one whose **response varies with authentication** — typically an API endpoint that returns the logged-in user's data (e.g. `GET https://api.github.com/user`, `https://twitter.com/i/api/.../UserByScreenName?...&forCurrentUser=true`).
|
|
32
|
+
|
|
33
|
+
When ranking candidates from `find_requests_containing`, prefer ones that:
|
|
34
|
+
- Have `Cookie`, `Authorization`, or other secret headers (analyze_request_constraints will surface these via `splitSecrets`).
|
|
35
|
+
- Have URL paths suggesting authenticated data: `/me`, `/user`, `/settings`, `/api/...`, `/graphql` with viewer queries.
|
|
36
|
+
- Return JSON keyed to the dev's identity (login/username/id fields next to the target value).
|
|
37
|
+
|
|
38
|
+
If the only candidates are public-page HTML responses, tell the dev: "The value appears on a public page, so a proof against this endpoint won't bind to your identity — anyone could prove the same fact. I can still build it if you just want to attest the value, but for an identity-bound proof we need to capture an authenticated request. Want to keep digging, or proceed anyway?" Then wait for their call.
|
|
39
|
+
|
|
40
|
+
<!-- section: rewrite-shape-block -->
|
|
41
|
+
**Do NOT rewrite the shape.** The synthesizer's output is the canonical Reclaim shape — `contains` template + paramValues + xPath/jsonPath redaction (optionally + regex+hash). Specifically:
|
|
42
|
+
- Do not replace the `contains` template with a hand-crafted regex.
|
|
43
|
+
- Do not hand-write a named-capture regex unless `hash` was requested AND the synthesizer's output doesn't already have one. (It will, for any digit/slug/email-shaped target.)
|
|
44
|
+
- Do not remove `paramValues` — that's how the template resolves at proof time.
|
|
45
|
+
- The redaction's xPath/jsonPath is what the prover reveals; the match's template is what the attestor verifies post-substitution. They serve different purposes — don't conflate them.
|
|
46
|
+
- Do not prefix the match-template's param with `context.`/`context_` — that namespace is reserved for consumer-supplied URL/body params (step 10). Match templates are extraction params and stay bare.
|
|
47
|
+
|
|
48
|
+
Permitted edits: narrowing the `contains` template, picking a more distinctive xPath class/id if the synthesizer's pick is too generic, fixing the `name` field, adding/removing secret headers. If you're tempted to rewrite the provider from scratch, call `propose_provider` again with different inputs instead.
|
|
49
|
+
|
|
50
|
+
<!-- section: no-rename-param -->
|
|
51
|
+
**Do not rename the match-template param.** The synthesizer's `{{name}}` in `responseMatches[].value` is an _extraction_ template (the attestor pulls the value out of the response) — it stays bare and never gets the `context.` prefix. Only URL/body placeholders whose values the Consumer must supply are context params.
|
|
52
|
+
|
|
53
|
+
<!-- section: prove-tail -->
|
|
54
|
+
**On any `verified: false` response, the result includes (inline):**
|
|
55
|
+
- `error` — the SDK's thrown error message verbatim
|
|
56
|
+
- `sentParams` — the exact `params` object the SDK saw, post-template substitution (lets you spot mutation / substitution surprises)
|
|
57
|
+
|
|
58
|
+
**Deeper tracing (opt-in).** If the dev launches Claude Code with `RECLAIM_AGENT_TRACE=1` in the MCP server's env, `run_proof` ALSO captures the SDK's full pino log stream into `result.attestorLogs` and persists a per-call dump to `~/.reclaim/proof-runs/<timestamp>.json` (surfaced as `runLogPath`). Off by default because the trace stream is verbose. Suggest turning it on when investigating a recurring proof failure.
|
|
59
|
+
|
|
60
|
+
**The attestor is reliable. If it errors, the cause is in the provider, the network, or the prover-side code — not "the attestor can't handle this site".** Do not invent failure modes like "HTML is too large", "TLS records are chunked", "the attestor matches against gzipped bytes" (it doesn't if the provider has `Accept-Encoding: identity`, which `propose_provider` always sets), or "xPath doesn't work on this DOM". These are wrong and they waste the dev's time. Instead:
|
|
61
|
+
1. Show the `error` field to the dev verbatim.
|
|
62
|
+
2. If `sentParams` differs from what you expected — e.g. `{{name}}` substituted to the wrong value, headers got mutated, redactions look different from `propose_provider`'s output — surface that.
|
|
63
|
+
3. Only then propose a hypothesis, and tie it to that evidence. If the diagnosis is unclear, suggest the dev set `RECLAIM_AGENT_TRACE=1` and rerun for full attestor logs.
|
|
64
|
+
|
|
65
|
+
**Do not auto-retry with a different `attestorUrl`.** There is one live public attestor right now; backup hostnames (`attestor-2.*`, etc.) **do not exist**. `attestorUrl` is only for when the dev explicitly supplies a private/staging URL.
|
|
66
|
+
|
|
67
|
+
**Missing ZK circuit files:** if the error says "ZK circuit resources are missing" and points at a `circuit.wasm` path, the npm postinstall didn't run (commonly because the dev used `npm install --ignore-scripts` or a corp policy disabled it). Tell the dev to run `npm run download:zk-files` from the project root and then retry `run_proof` — no need to redo capture or propose. The MCP server doesn't need a restart either; the next `run_proof` call will see the files.
|
|
68
|
+
|
|
69
|
+
<!-- section: concerns-section -->
|
|
70
|
+
## Concerns the developer should see verbatim
|
|
71
|
+
|
|
72
|
+
When `analyze_request_constraints` returns concerns, repeat each `message` to the dev exactly. Don't paraphrase, don't omit. These are the dev's signal that a provider is or isn't worth pursuing.
|
|
73
|
+
|
|
74
|
+
<!-- section: never-do-common -->
|
|
75
|
+
- Never echo a raw `Authorization`, `Cookie`, or `X-*-Token` value back to the dev or write one to any file. The MCP guarantees these don't appear in tool responses — keep that guarantee.
|
|
76
|
+
- Never call `run_proof` before `replay_request` succeeds. Attestor calls are expensive.
|
|
77
|
+
- Never pretend a proof succeeded when `verified: false` came back. Show the error and iterate.
|
|
78
|
+
- Never write a literal value into `responseMatches.value` — the synthesizer always emits `{{name}}` templates with paramValues. A literal value bakes the original user into the provider.
|
|
79
|
+
- Never pass `hash` to `propose_provider` for a free-form (unicode / spaces / punctuation) target — the synthesizer will error, and the right move is either to use a tighter `target` or accept cleartext (no `hash`).
|
|
80
|
+
- Never request `hash` for a target whose value also appears in the request URL — `propose_provider` throws (see step 8); the fix is dropping `hash` or capturing a request whose URL doesn't carry the value, not retrying the same call.
|
|
81
|
+
|
|
82
|
+
<!-- section: first-time-issues-intro -->
|
|
83
|
+
## Common first-time issues — surface these proactively
|
|
84
|
+
|
|
85
|
+
If a local `attach_browser` call errors, times out, or `waitForCdpReady` reports the port isn't answering, walk through these in order before retrying:
|
|
86
|
+
|
|
87
|
+
- **Browser binary not found.** If the error lists candidate paths it tried (Program Files Chrome, LOCALAPPDATA Chrome, Edge), every one missed. Either the dev has Chrome installed somewhere unusual, or doesn't have Chrome/Edge at all. Walk them through setting `RECLAIM_AGENT_CHROME_PATH` to the actual binary path:
|
|
88
|
+
```powershell
|
|
89
|
+
setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"
|
|
90
|
+
```
|
|
91
|
+
`setx` (not `$env:`) writes to the user-level Windows env, so future shells and future Claude Code launches inherit it. **Then fully restart Claude Code** — MCP servers snapshot env vars at launch, so the running reclaim-agent has the old empty value. After restart, the new MCP process picks up the env. On macOS/Linux: `export RECLAIM_AGENT_CHROME_PATH=/path/to/chrome` in the shell, then restart Claude Code from that same shell so the var propagates.
|
|
92
|
+
- **Pre-existing `chrome.exe` processes hijack the new launch (Windows).** If any Chrome is already running, a new launch with `--remote-debugging-port` gets delegated to the existing process group and the debug port silently doesn't open. Tell the dev to kill all `chrome.exe` first (`taskkill /F /IM chrome.exe` in cmd/PowerShell, or via Task Manager), then retry. Affects both `attach` and `dedicated` modes when the existing instance was launched with the same profile dir.
|
|
93
|
+
- **Chrome 136+ silently disables remote debugging on the default user profile.** Already noted in step 1, but worth raising again here: the only fix is a non-default `--user-data-dir`. `dedicated` mode is always safe; `attach` mode needs the dev to have launched with both flags.
|
|
94
|
+
|
|
95
|
+
<!-- section: credential-intro -->
|
|
96
|
+
**Credential for signing the proof.** `run_proof` requires an `ownerAddress` — the eth proof-owner key whose private half signs the claim. It does **no** resolution itself; get the address from `resolve_owner_key` first. The private key is resolved locally (`RECLAIM_PRIVATE_KEY` env → a `RECLAIM_PRIVATE_KEY=0x…` line in the project `.env` → a file at `RECLAIM_PRIVATE_KEY_FILE`) and never enters the conversation.
|
|
97
|
+
|
|
98
|
+
<!-- section: credential-step2 -->
|
|
99
|
+
2. All fully local — no network, no server registration. The key is read live from `.env` on each `run_proof`, so no restart is needed after issuing/importing.
|
|
100
|
+
|
|
101
|
+
<!-- section: mcp-config-bullet -->
|
|
102
|
+
- **Multiple MCP config files exist on Windows.** `~/.claude.json` takes precedence over `~/.claude/mcp.json`. When advising config edits, point them at the right file or tell them to use `claude mcp list` / `claude mcp add --scope user` rather than editing JSON by hand.
|
package/skill/SKILL.old.md
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> **Terminology note:** A **provider** is the recipe you author here — the
|
|
4
|
-
> instructions for extracting a data point from a website (the
|
|
5
|
-
> `responseMatches` / `responseRedactions` spec, identified by a
|
|
6
|
-
> `providerId`). Always call it a **provider**; never call a provider a
|
|
7
|
-
> "verification." A **verification** (or **verification session**) is the
|
|
8
|
-
> separate runtime act in which an end-user proves a data point *using* a
|
|
9
|
-
> provider — reserve that word for the session/result, never for the recipe.
|
|
10
|
-
> If a developer calls the recipe a "verification," gently use "provider"
|
|
11
|
-
> yourself rather than correcting them.
|
|
12
|
-
|
|
13
|
-
You drive the browser through the capture → draft → replay → prove loop using the `reclaim-agent` MCP server. **Your default stance is driver, not orchestrator.** Only pause to ask the dev for things that genuinely require a human: typing credentials, completing 2FA, solving a captcha, confirming an ambiguous value. Navigation, reloads, DOM inspection, request capture — all yours.
|
|
1
|
+
<!-- common: header -->
|
|
14
2
|
|
|
15
3
|
## Getting authenticated
|
|
16
4
|
|
|
@@ -22,11 +10,7 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
22
10
|
|
|
23
11
|
## The loop
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
- `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest path; works out of the box.
|
|
27
|
-
- `attach` — connect to a Chrome the dev is already running. **Important Chrome 136+ gotcha:** the dev's Chrome must be launched with BOTH `--remote-debugging-port=9222` AND `--user-data-dir=<some-non-default-path>`. Since Chrome 136 (May 2025), the debug-port flag is silently ignored when the default user profile is in use. If `attach` fails, tell them this verbatim and offer to switch to `dedicated`.
|
|
28
|
-
|
|
29
|
-
Re-call with the chosen `attachMode`.
|
|
13
|
+
<!-- common: step-attach -->
|
|
30
14
|
|
|
31
15
|
2. **Pick a tab and open the site yourself.** Call `list_tabs`. If there's already a tab on the right origin, use it. Otherwise pick any tab (the first one is usually fine) and call `navigate(tabId, url)` to send it to the site you've inferred from the dev's request (e.g. "GitHub username" → `https://github.com`). Don't ask the dev to navigate.
|
|
32
16
|
|
|
@@ -49,18 +33,9 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
49
33
|
|
|
50
34
|
5. **Start capture, then trigger the request.** Call `start_capture(tabId)` first. Then issue a reload so the relevant API call fires while capture is live: `eval_in_page(tabId, "location.reload()")`. For some pages you may need to click a specific element or call a SPA route — use `eval_in_page` to do that programmatically rather than asking the dev to click.
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
7. **Find the request.** Call `find_requests_containing(captureId, value)`. For each top candidate (limit ≤ 5), call `analyze_request_constraints` and surface its `concerns` to the dev verbatim. Skip candidates flagged `signedRequest` or `botChallenge` unless none others are available. Concerns flagged `csrfDependency`, `shortLivedToken`, or `graphql` are informational — surface them and proceed.
|
|
36
|
+
<!-- common: step-identify-value -->
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
When ranking candidates from `find_requests_containing`, prefer ones that:
|
|
59
|
-
- Have `Cookie`, `Authorization`, or other secret headers (analyze_request_constraints will surface these via `splitSecrets`).
|
|
60
|
-
- Have URL paths suggesting authenticated data: `/me`, `/user`, `/settings`, `/api/...`, `/graphql` with viewer queries.
|
|
61
|
-
- Return JSON keyed to the dev's identity (login/username/id fields next to the target value).
|
|
62
|
-
|
|
63
|
-
If the only candidates are public-page HTML responses, tell the dev: "The value appears on a public page, so a proof against this endpoint won't bind to your identity — anyone could prove the same fact. I can still build it if you just want to attest the value, but for an identity-bound proof we need to capture an authenticated request. Want to keep digging, or proceed anyway?" Then wait for their call.
|
|
38
|
+
<!-- common: step-find-request -->
|
|
64
39
|
|
|
65
40
|
8. **Decide whether to OPRF the value** (privacy from the attestor). Before calling `propose_provider`, decide if the captured value should be hashed end-to-end so the attestor never sees it in cleartext. Four options:
|
|
66
41
|
- **No hash** (default). Value travels via `paramValues` + `{{name}}` template; attestor sees the cleartext substring. Simplest, fewest moving parts. Right call for non-sensitive values (public IPs from `ifconfig.me`, public usernames the dev is happy to expose, public repo names).
|
|
@@ -79,6 +54,8 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
79
54
|
|
|
80
55
|
**Hard constraint:** OPRF-hashed values are capped at **62 bytes**. If the value is longer and the dev picks any hash, surface that and propose either a tighter `target` (e.g. just the email local-part) or no hash. `propose_provider` will return a clean error if you try anyway.
|
|
81
56
|
|
|
57
|
+
**Hard constraint: OPRF and the request URL are mutually exclusive for the same value.** The entire point of `hash` is that the attestor never sees the value in cleartext — but any value templated into the request URL (`{{name}}`, `{{context_<name>}}`, or left literal) is always signed into the attestor's context in the clear, regardless of hashing. So if the target's value ALSO appears anywhere in the request URL, hashing it is not just pointless, it doesn't verify. Check this before requesting `hash` for a target: if that value shows up in the captured URL too, either drop `hash` for that target or capture a different request whose URL doesn't carry the value. `propose_provider` throws a clean error if you try anyway — same as the byte cap above.
|
|
58
|
+
|
|
82
59
|
9. **Draft.** Call `propose_provider(captureId, requestId, target, name, hash?)`. Pass `hash` only if the dev opted in. Inspect the returned provider:
|
|
83
60
|
- Is the right header marked secret? (cookies, Authorization, X-\* tokens)
|
|
84
61
|
- Is `responseMatches[0].value` a `contains` with a `{{name}}` template (matching the captured value's structural context)?
|
|
@@ -115,14 +92,7 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
115
92
|
}
|
|
116
93
|
```
|
|
117
94
|
|
|
118
|
-
|
|
119
|
-
- Do not replace the `contains` template with a hand-crafted regex.
|
|
120
|
-
- Do not hand-write a named-capture regex unless `hash` was requested AND the synthesizer's output doesn't already have one. (It will, for any digit/slug/email-shaped target.)
|
|
121
|
-
- Do not remove `paramValues` — that's how the template resolves at proof time.
|
|
122
|
-
- The redaction's xPath/jsonPath is what the prover reveals; the match's template is what the attestor verifies post-substitution. They serve different purposes — don't conflate them.
|
|
123
|
-
- Do not prefix the match-template's param with `context.`/`context_` — that namespace is reserved for consumer-supplied URL/body params (step 10). Match templates are extraction params and stay bare.
|
|
124
|
-
|
|
125
|
-
Permitted edits: narrowing the `contains` template, picking a more distinctive xPath class/id if the synthesizer's pick is too generic, fixing the `name` field, adding/removing secret headers. If you're tempted to rewrite the provider from scratch, call `propose_provider` again with different inputs instead.
|
|
95
|
+
<!-- common: rewrite-shape-block -->
|
|
126
96
|
|
|
127
97
|
10. **Parameterize user-specific URL segments** so the provider isn't user-specific at the URL level. The matcher already populates `paramValues` with the _captured value_ (e.g. `{username: "Scratch-net"}` for a username target). If the URL ALSO contains user-identifying segments, those are **consumer-supplied context params** — at verification time their values come from the Consumer's session `context`, not from extraction. They use a namespaced placeholder, and the namespace differs by mode:
|
|
128
98
|
|
|
@@ -131,7 +101,11 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
131
101
|
- Generic segments (`/api/v3`, `/users`, `/repos`) stay literal.
|
|
132
102
|
- If the URL has no user-identifying segments (e.g. `/settings/profile`), leave it untouched — the matcher already handled the value via paramValues.
|
|
133
103
|
|
|
134
|
-
**
|
|
104
|
+
**Marking a value secret:** name ANY param with `SECRET` anywhere in the key (case-insensitive) — a bare `{{SECRET_x}}` (e.g. a value extracted earlier in a multi-request flow) just as much as a consumer-supplied `{{context_SECRET_apiKey}}` — and it's routed to `secretParams.paramValues` (`classifyParamName`/`splitSecretParamValues`), same naming convention `reclaim-inapp-sdk`/`reclaim-portal` use for their `paramValues`. The `context_` prefix is a namespace convention (consumer-supplied vs extracted-from-traffic); the attestor itself just looks up `{{whatever}}` in `paramValues`/`secretParams.paramValues` and doesn't care about the prefix. Headers are secret by the OPPOSITE rule — an ALLOWLIST, not a name check: any header not in a small fixed public list (`user-agent`, `accept`, `accept-language`, `accept-encoding`, `sec-fetch-*`, `origin`, `x-requested-with`, `sec-ch-ua*`) is secret by default, including `Cookie`/`Authorization` and anything custom (`classifyHeader`/`splitSecrets`).
|
|
105
|
+
|
|
106
|
+
A secret value CAN be used in the URL, geoLocation, request body, response match, or response redactions — write-redaction hides it there. The value of any parameter marked secret is omitted from the final proof entirely. There's still a limitation specific to the URL: only up to a small TOTAL character budget summed across every secret value used in that URL (`secretUrlCharBudget`), not a per-value allowance — 2 characters under `key-update` (the default), 12 under `zk`. Using a longer-than-budget secret in the URL causes the attestor's claim creation to fail outright.
|
|
107
|
+
|
|
108
|
+
<!-- common: no-rename-param -->
|
|
135
109
|
|
|
136
110
|
`propose_provider` returns `verificationWarnings` for the calls only an author can make (currently: a captured value baked literally into the URL, needing the `{{context_<name>}}`-vs-bare-vs-re-capture decision). They are advisory, never blocking — **surface each warning to the dev verbatim** and resolve it before publishing.
|
|
137
111
|
|
|
@@ -150,20 +124,7 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
150
124
|
|
|
151
125
|
**Missing signing credential (`errorKind: "missing-owner-key"`)** — the dev hasn't set up an eth proof-owner key yet. Tell them what's happening, then drive the bootstrap yourself: call `resolve_owner_key`; if it returns no key, ask once _"shall I mint a fresh eth key via `issue_credentials` (or `import_credentials` if you already hold one)? It's written to the project `.env` (mode 0600, gitignored) for this and future sessions."_; on yes, call `issue_credentials` (or `import_credentials`) and retry the same `run_proof` call. **No restart needed** — the key is read live from `.env` on each `run_proof`, and the existing `secretParamsRef` from `propose_provider` is still valid.
|
|
152
126
|
|
|
153
|
-
|
|
154
|
-
- `error` — the SDK's thrown error message verbatim
|
|
155
|
-
- `sentParams` — the exact `params` object the SDK saw, post-template substitution (lets you spot mutation / substitution surprises)
|
|
156
|
-
|
|
157
|
-
**Deeper tracing (opt-in).** If the dev launches Claude Code with `RECLAIM_AGENT_TRACE=1` in the MCP server's env, `run_proof` ALSO captures the SDK's full pino log stream into `result.attestorLogs` and persists a per-call dump to `~/.reclaim/proof-runs/<timestamp>.json` (surfaced as `runLogPath`). Off by default because the trace stream is verbose. Suggest turning it on when investigating a recurring proof failure.
|
|
158
|
-
|
|
159
|
-
**The attestor is reliable. If it errors, the cause is in the provider, the network, or the prover-side code — not "the attestor can't handle this site".** Do not invent failure modes like "HTML is too large", "TLS records are chunked", "the attestor matches against gzipped bytes" (it doesn't if the provider has `Accept-Encoding: identity`, which `propose_provider` always sets), or "xPath doesn't work on this DOM". These are wrong and they waste the dev's time. Instead:
|
|
160
|
-
1. Show the `error` field to the dev verbatim.
|
|
161
|
-
2. If `sentParams` differs from what you expected — e.g. `{{name}}` substituted to the wrong value, headers got mutated, redactions look different from `propose_provider`'s output — surface that.
|
|
162
|
-
3. Only then propose a hypothesis, and tie it to that evidence. If the diagnosis is unclear, suggest the dev set `RECLAIM_AGENT_TRACE=1` and rerun for full attestor logs.
|
|
163
|
-
|
|
164
|
-
**Do not auto-retry with a different `attestorUrl`.** There is one live public attestor right now; backup hostnames (`attestor-2.*`, etc.) **do not exist**. `attestorUrl` is only for when the dev explicitly supplies a private/staging URL.
|
|
165
|
-
|
|
166
|
-
**Missing ZK circuit files:** if the error says "ZK circuit resources are missing" and points at a `circuit.wasm` path, the npm postinstall didn't run (commonly because the dev used `npm install --ignore-scripts` or a corp policy disabled it). Tell the dev to run `npm run download:zk-files` from the project root and then retry `run_proof` — no need to redo capture or propose. The MCP server doesn't need a restart either; the next `run_proof` call will see the files.
|
|
127
|
+
<!-- common: prove-tail -->
|
|
167
128
|
|
|
168
129
|
13. **Publish the proved provider.** `provider` is the exact JSON from step 9 with the step-10 URL parameterisation applied, and `initialUrl` is the page where the user logs in / the value is displayed (e.g. `https://github.com/Syed`) — **not** the API endpoint URL inside the provider.
|
|
169
130
|
|
|
@@ -180,33 +141,20 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
180
141
|
|
|
181
142
|
When done, tell the developer the version is live (PRIVATE) or submitted for review (PUBLIC), and that they can integrate using the `providerId`.
|
|
182
143
|
|
|
183
|
-
|
|
144
|
+
- **If a browser is still attached**, `create_provider_version_from_capture`'s response carries a `_notes` reminder about it. Surface that to the dev and ask whether they want to keep the session open for further edits — only call `dispose_browser` once they confirm they're done. Never close it unprompted; a `builder`-mode session is quota-accounted, but the dev may still be mid-workflow.
|
|
184
145
|
|
|
185
|
-
|
|
146
|
+
<!-- common: concerns-section -->
|
|
186
147
|
|
|
187
148
|
## Things to never do
|
|
188
149
|
|
|
189
|
-
|
|
190
|
-
- Never call `run_proof` before `replay_request` succeeds. Attestor calls are expensive.
|
|
191
|
-
- Never pretend a proof succeeded when `verified: false` came back. Show the error and iterate.
|
|
150
|
+
<!-- common: never-do-common -->
|
|
192
151
|
- **Never hand-craft `responseMatches` or `responseRedactions`.** If you find yourself typing a regex by hand, OR writing `(?<name>...)`-style patterns by hand, OR copying the captured value literally into the match — stop. The synthesizer in `propose_provider` already produces the canonical shape: `contains` template + paramValues for the match, xPath/jsonPath (optionally + regex+hash when `hash` is passed) for the redaction. The named-capture regex in the redaction is the synthesizer's job, not yours.
|
|
193
|
-
- Never write a literal value into `responseMatches.value` — the synthesizer always emits `{{name}}` templates with paramValues. A literal value bakes the original user into the provider.
|
|
194
|
-
- Never pass `hash` to `propose_provider` for a free-form (unicode / spaces / punctuation) target — the synthesizer will error, and the right move is either to use a tighter `target` or accept cleartext (no `hash`).
|
|
195
152
|
- Never accept "the synthesizer's xPath looks too narrow, let me write a regex" — the xPath is fine, the attestor will reveal the whole matched element which is naturally ≥16 bytes.
|
|
153
|
+
- Mark ANY param secret (not just consumer-supplied `context_*` ones) by naming it with `SECRET` (e.g. `{{SECRET_x}}` or `{{context_SECRET_apiKey}}`). It CAN be used in the URL, geoLocation, request body, response match, response redactions — write-redaction hides it there. Any value of a parameter marked secret is not included in the proof. There's also a limitation for secret-marked values used in a URL: only up to a small TOTAL character budget across every secret value in that URL (2 chars under `key-update`, the default; 12 under `zk`). That's checked at verification time against the real value, not at publish time (see step 10). Headers are secret by allowlist, not by name — anything not on a small fixed public list is secret by default.
|
|
196
154
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
If `attach_chrome` errors, times out, or `waitForCdpReady` reports the port isn't answering, walk through these in order before retrying:
|
|
200
|
-
|
|
201
|
-
- **Browser binary not found.** If the error lists candidate paths it tried (Program Files Chrome, LOCALAPPDATA Chrome, Edge), every one missed. Either the dev has Chrome installed somewhere unusual, or doesn't have Chrome/Edge at all. Walk them through setting `RECLAIM_AGENT_CHROME_PATH` to the actual binary path:
|
|
202
|
-
```powershell
|
|
203
|
-
setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"
|
|
204
|
-
```
|
|
205
|
-
`setx` (not `$env:`) writes to the user-level Windows env, so future shells and future Claude Code launches inherit it. **Then fully restart Claude Code** — MCP servers snapshot env vars at launch, so the running reclaim-agent has the old empty value. After restart, the new MCP process picks up the env. On macOS/Linux: `export RECLAIM_AGENT_CHROME_PATH=/path/to/chrome` in the shell, then restart Claude Code from that same shell so the var propagates.
|
|
206
|
-
- **Pre-existing `chrome.exe` processes hijack the new launch (Windows).** If any Chrome is already running, a new launch with `--remote-debugging-port` gets delegated to the existing process group and the debug port silently doesn't open. Tell the dev to kill all `chrome.exe` first (`taskkill /F /IM chrome.exe` in cmd/PowerShell, or via Task Manager), then retry. Affects both `attach` and `dedicated` modes when the existing instance was launched with the same profile dir.
|
|
207
|
-
- **Chrome 136+ silently disables remote debugging on the default user profile.** Already noted in step 1, but worth raising again here: the only fix is a non-default `--user-data-dir`. `dedicated` mode is always safe; `attach` mode needs the dev to have launched with both flags.
|
|
155
|
+
<!-- common: first-time-issues-intro -->
|
|
208
156
|
|
|
209
|
-
|
|
157
|
+
<!-- common: credential-intro -->
|
|
210
158
|
|
|
211
159
|
1. **Call `resolve_owner_key`.** It checks `RECLAIM_PRIVATE_KEY` (MCP env) → the project `.env` → `RECLAIM_PRIVATE_KEY_FILE`, and returns only the derived address.
|
|
212
160
|
- `{ ready: true, address }` → call `run_proof({ provider, ownerAddress: address, … })`. No prompts.
|
|
@@ -215,9 +163,9 @@ If `attach_chrome` errors, times out, or `waitForCdpReady` reports the port isn'
|
|
|
215
163
|
- **Create a new one** → `issue_credentials({})`. (Fine default for test proofs — owner identity doesn't matter.)
|
|
216
164
|
- Either way the key is written to the project `.env` (mode 0600; `.env` is added to `.gitignore`) — never to `~/.reclaim`.
|
|
217
165
|
- Then call `resolve_owner_key` again and `run_proof` with the returned address.
|
|
218
|
-
|
|
166
|
+
<!-- common: credential-step2 -->
|
|
219
167
|
- **Advanced:** setting `RECLAIM_PRIVATE_KEY` directly in the MCP server's env also works, but env vars are snapshotted at Claude Code startup, so that route needs a full restart (which invalidates every `secretParamsRef`). The `.env` flow above avoids that — prefer it.
|
|
220
|
-
|
|
168
|
+
<!-- common: mcp-config-bullet -->
|
|
221
169
|
|
|
222
170
|
If `replay_request` or `run_proof` errors with `unknown secretParamsRef`:
|
|
223
171
|
|
package/skill/builder-mode.md
CHANGED
|
@@ -2,13 +2,13 @@ This server targets the Reclaim BUILDER backend. It exposes three tool layers: g
|
|
|
2
2
|
|
|
3
3
|
**Authentication:** try `get_me` first. On 401, `authenticate({})` opens a device-pairing session — give the user the `attachUrl`, then poll `authenticate({ code })` until LINKED. Tokens are cached ~2 days.
|
|
4
4
|
|
|
5
|
-
**Provider-authoring flow:** `
|
|
5
|
+
**Provider-authoring flow:** `attach_browser` (local, Builder remote, or custom CDP) → `start_capture(tabId)` ⇒ a **`captureId`** (the session handle every browser/inspect tool takes as its first arg) → drive the page (`navigate`, `wait_for_page`, `eval_in_page`, `get_cookies`) → inspect (`list_requests`, `get_request`, `find_requests_containing`) → `propose_provider` ⇒ a **`draftId`** (the recipe + captured secrets held server-side; secrets never enter the transcript) → `replay_request(captureId, draftId)` → `run_proof(captureId, draftId, ownerAddress)` → resolve a `providerId` (**Reuse before create**, below) → `create_provider_version_from_capture` (lands on the draft branch) → `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review` (PUBLIC).
|
|
6
6
|
|
|
7
7
|
**Reuse before create (avoid duplicates):** `create_provider_version_from_capture` requires a `providerId`. Before minting a new one with `create_provider`, search your existing providers with `providers` (by domain / data point) — a site you've already built will be there. If one matches, pass its `providerId` so the capture lands as a NEW VERSION on that provider. Only `create_provider` (then use its returned id) when none exists. Re-running the flow against `create_provider` each time is what stacks duplicate providers in "My providers".
|
|
8
8
|
|
|
9
9
|
**Multiple requests & login-gated data:** one version can carry many requests — `propose_provider` + `run_proof` each target request individually, then publish them together via `create_provider_version_from_capture({ providers: [draftA, draftB, …] })` (every entry is required at verification; the single-`provider` form still works for the one-request case). For data gated behind login/navigation, supply `jsUserScripts` — plain page JavaScript if needed that drives login/waits for user to login/waits for user to do multifactor auth/navigation with standard browser APIs (`window.location`, clicking elements, submitting forms) so the target request fires (page requests are intercepted automatically; `window.Reclaim` is only for rare edge cases) — and/or `allowedJsRequests` (extra requests the script may fire, e.g. pagination). NOTE: the builder verification client installs `jsUserScripts` to run before every page load (before navigating to `initialUrl`), matching the old-devtool `customInjection`; `window.Reclaim` itself is not yet exposed there, so prefer plain browser JS.
|
|
10
10
|
|
|
11
|
-
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response by the attestor). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) are templated `{{context.<name>}}` and declared on the version as `requiredContext` — a JSON Schema set via `update_provider_version` while the version is on draft. The Builder validates every session-create's `context` against it (400 on mismatch; extra context fields are always allowed). `requiredContext` is optional — skip it when the recipe has no `{{context.*}}` placeholders. At verification time, requests are matched by exact origin+path: `{{context.<name>}}` segments match the consumer-sent value only, bare `{{name}}` values are extracted from the live traffic (plain-text alignment — works for JSON, XML, HTML, form-encoded, any text). Extract selectors apply client-side in spec order xPath → jsonPath → regex, each to the prior selection, so only the first selector constrains the full response; responses that cannot satisfy it (or miss a required match) are skipped. `propose_provider` returns advisory `verificationWarnings` for author-only decisions — surface them to the dev before publishing.
|
|
11
|
+
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response by the attestor). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) are templated `{{context.<name>}}` and declared on the version as `requiredContext` — a JSON Schema set via `update_provider_version` while the version is on draft. The Builder validates every session-create's `context` against it (400 on mismatch; extra context fields are always allowed). `requiredContext` is optional — skip it when the recipe has no `{{context.*}}` placeholders. At verification time, requests are matched by exact origin+path: `{{context.<name>}}` segments match the consumer-sent value only, bare `{{name}}` values are extracted from the live traffic (plain-text alignment — works for JSON, XML, HTML, form-encoded, any text). Extract selectors apply client-side in spec order xPath → jsonPath → regex, each to the prior selection, so only the first selector constrains the full response; responses that cannot satisfy it (or miss a required match) are skipped. `propose_provider` returns advisory `verificationWarnings` for author-only decisions — surface them to the dev before publishing. **Two hard limitations:** (1) never `hash`/OPRF a target whose value also appears in the request URL — URL param values are always signed into context in the clear, so `propose_provider` throws rather than shipping a draft that can't verify; (2) ANY param — bare `{{SECRET_x}}` or consumer-supplied `{{context.SECRET_apiKey}}` alike — named with `SECRET` routes to `secretParams.paramValues` and CAN be used in the URL/geoLocation, but only up to a small TOTAL character budget summed across every secret value in that URL — 2 chars under `key-update` (default), 12 under `zk` — checked at verification time against the real value, since it doesn't exist yet at publish time. See `how_it_works` for the full reasoning.
|
|
12
12
|
|
|
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
|
|