@reclaimprotocol/agent 0.1.23 → 0.1.24
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 +55 -20
- package/lib/authoring/backend.d.ts +5 -4
- package/lib/authoring/tools.js +30 -30
- package/lib/cdp/browser-proxy.d.ts +78 -0
- package/lib/cdp/browser-proxy.js +186 -0
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/client.d.ts +10 -0
- package/lib/cdp/geo.d.ts +10 -0
- package/lib/cdp/geo.js +36 -0
- package/lib/cdp/index.d.ts +1 -0
- package/lib/cdp/index.js +1 -0
- package/lib/live/cloudflared.d.ts +15 -0
- package/lib/live/cloudflared.js +268 -0
- package/lib/live/page.d.ts +20 -0
- package/lib/live/page.js +134 -0
- package/lib/live/tunnel.d.ts +23 -0
- package/lib/live/tunnel.js +120 -0
- package/lib/live/viewer.d.ts +28 -0
- package/lib/live/viewer.js +232 -0
- package/lib/mcp/api/operations.gen.js +34 -26
- package/lib/mcp/api/to-zod.d.ts +1 -1
- package/lib/mcp/api/to-zod.js +1 -1
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/instructions.d.ts +15 -2
- package/lib/mcp/instructions.js +101 -38
- package/lib/mcp/start.js +4 -3
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +8 -1
- package/lib/mcp/tools/agent/attach.js +166 -22
- package/lib/mcp/tools/agent/build.d.ts +13 -8
- package/lib/mcp/tools/agent/build.js +22 -11
- package/lib/mcp/tools/agent/capture.js +5 -1
- package/lib/mcp/tools/agent/eval.js +3 -1
- package/lib/mcp/tools/agent/find.js +6 -5
- package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
- package/lib/mcp/tools/agent/how-it-works.js +57 -17
- package/lib/mcp/tools/agent/live-view.d.ts +21 -0
- package/lib/mcp/tools/agent/live-view.js +153 -0
- package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
- package/lib/mcp/tools/agent/pod-proof.js +64 -0
- package/lib/mcp/tools/agent/propose.js +4 -2
- package/lib/mcp/tools/agent/proxy.d.ts +3 -0
- package/lib/mcp/tools/agent/proxy.js +199 -0
- package/lib/mcp/tools/agent/publish.js +47 -77
- package/lib/mcp/tools/agent/session.d.ts +2 -1
- package/lib/mcp/tools/agent/session.js +26 -7
- package/lib/mcp/tools/authenticate/authenticate.js +26 -13
- package/lib/mcp/tools/mode/mode.d.ts +1 -1
- package/lib/mcp/tools/mode/mode.js +10 -22
- package/lib/old/client.d.ts +1 -1
- package/lib/old/client.js +1 -1
- package/lib/old/instructions.d.ts +9 -1
- package/lib/old/instructions.js +4 -6
- package/lib/old/login-cdp.d.ts +2 -2
- package/lib/old/login-cdp.js +46 -6
- package/lib/old/to-register.d.ts +3 -2
- package/lib/old/to-register.js +1 -1
- package/lib/old/tools/authenticate.js +9 -1
- package/lib/old/tools/get-provider-info.js +6 -5
- package/lib/old/tools/issue-app-credentials.js +3 -2
- package/lib/old/tools/publish.d.ts +1 -1
- package/lib/old/tools/publish.js +90 -131
- package/lib/old/tools/update-provider-metadata.d.ts +1 -1
- package/lib/old/tools/update-provider-metadata.js +1 -1
- package/lib/old/tools/verify-sdk-setup.js +1 -1
- package/lib/proof/attestor.d.ts +3 -3
- package/lib/proof/debug-response-dump.js +4 -4
- package/lib/proof/index.d.ts +1 -0
- package/lib/proof/index.js +1 -0
- package/lib/proof/page-replay.d.ts +1 -1
- package/lib/proof/page-replay.js +1 -1
- package/lib/proof/pod-prover.d.ts +47 -0
- package/lib/proof/pod-prover.js +155 -0
- package/lib/provider/dom-xpath.js +11 -9
- package/lib/provider/draft.d.ts +10 -7
- package/lib/provider/draft.js +11 -9
- package/lib/provider/injection-doc.d.ts +23 -1
- package/lib/provider/injection-doc.js +34 -21
- package/lib/provider/match-redaction-warning.d.ts +2 -2
- package/lib/provider/match-redaction-warning.js +3 -3
- package/lib/provider/matcher.js +13 -12
- package/lib/provider/secrets.d.ts +3 -3
- package/lib/provider/secrets.js +3 -3
- package/lib/provider/to-version.d.ts +7 -6
- package/lib/provider/to-version.js +3 -3
- package/lib/tools/registry.d.ts +5 -5
- package/lib/tools/registry.js +5 -5
- package/package.json +3 -3
- package/samples/demo-server/README.md +7 -6
- package/skill/SKILL.builder.md +356 -189
- package/skill/SKILL.common.md +323 -97
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +58 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +235 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +60 -21
- package/skill/user-script.md +61 -0
- package/src/authoring/backend.ts +5 -4
- package/src/authoring/tools.ts +31 -30
- package/src/cdp/capture-engine.ts +8 -7
- package/src/cdp/client.ts +8 -0
- package/src/cdp/geo.ts +52 -0
- package/src/cdp/index.ts +1 -0
- package/src/consts.ts +1 -0
- package/src/live/cloudflared.ts +348 -0
- package/src/live/page.ts +145 -0
- package/src/live/tunnel.ts +157 -0
- package/src/live/viewer.ts +293 -0
- package/src/mcp/api/operations.gen.ts +34 -26
- package/src/mcp/api/to-zod.ts +1 -1
- package/src/mcp/api/tools.ts +1 -1
- package/src/mcp/instructions.ts +121 -48
- package/src/mcp/start.ts +4 -3
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +194 -22
- package/src/mcp/tools/agent/build.ts +26 -15
- package/src/mcp/tools/agent/capture.ts +5 -1
- package/src/mcp/tools/agent/eval.ts +4 -1
- package/src/mcp/tools/agent/find.ts +6 -5
- package/src/mcp/tools/agent/how-it-works.ts +62 -21
- package/src/mcp/tools/agent/live-view.ts +198 -0
- package/src/mcp/tools/agent/pod-proof.ts +90 -0
- package/src/mcp/tools/agent/propose.ts +4 -2
- package/src/mcp/tools/agent/publish.ts +52 -77
- package/src/mcp/tools/agent/session.ts +26 -5
- package/src/mcp/tools/authenticate/authenticate.ts +28 -13
- package/src/mcp/tools/mode/mode.ts +10 -22
- package/src/old/client.ts +2 -2
- package/src/old/instructions.ts +4 -10
- package/src/old/login-cdp.ts +50 -6
- package/src/old/to-register.ts +3 -2
- package/src/old/tools/authenticate.ts +9 -1
- package/src/old/tools/get-provider-info.ts +6 -5
- package/src/old/tools/issue-app-credentials.ts +3 -2
- package/src/old/tools/publish.ts +95 -131
- package/src/old/tools/update-provider-metadata.ts +1 -1
- package/src/old/tools/verify-sdk-setup.ts +1 -1
- package/src/proof/attestor.ts +3 -3
- package/src/proof/debug-response-dump.ts +4 -4
- package/src/proof/index.ts +8 -0
- package/src/proof/page-replay.ts +1 -1
- package/src/proof/pod-prover.ts +231 -0
- package/src/provider/dom-xpath.ts +11 -9
- package/src/provider/draft.ts +13 -10
- package/src/provider/injection-doc.ts +37 -24
- package/src/provider/match-redaction-warning.ts +3 -3
- package/src/provider/matcher.ts +13 -12
- package/src/provider/secrets.ts +3 -3
- package/src/provider/to-version.ts +11 -9
- package/src/tools/registry.ts +5 -5
- package/skill/injection-api.md +0 -364
package/skill/SKILL.builder.md
CHANGED
|
@@ -1,211 +1,378 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
|
|
1
|
+
<!-- topic: overview -->
|
|
2
|
+
# Authoring a Reclaim provider (Builder backend)
|
|
3
|
+
|
|
4
|
+
<!-- common: terminology -->
|
|
5
|
+
|
|
6
|
+
## The loop, at a glance
|
|
7
|
+
|
|
8
|
+
Copy this checklist and check off each step as you finish it. Read the linked
|
|
9
|
+
topic before a step you aren't already fluent in.
|
|
10
|
+
|
|
11
|
+
- [ ] 1. `attach_browser` — pick a browser source with the developer.
|
|
12
|
+
→ topic `browser`
|
|
13
|
+
- [ ] 2. `list_tabs` → `start_capture(tabId)` ⇒ **`captureId`**, your handle for
|
|
14
|
+
everything below. → topic `capture`
|
|
15
|
+
- [ ] 3. Drive the page: `navigate`, `wait_for_page`, `eval_in_page`,
|
|
16
|
+
`get_cookies`. Detect login, land on the page showing the value, trigger
|
|
17
|
+
the request. → topic `capture`
|
|
18
|
+
- [ ] 4. `find_requests_containing` → `analyze_request_constraints` → pick an
|
|
19
|
+
**authenticated** request. → topic `capture`
|
|
20
|
+
- [ ] 5. `propose_provider(captureId, requestId, …)` ⇒ **`draftId`** (recipe +
|
|
21
|
+
captured secrets, held server-side — secrets never enter the transcript).
|
|
22
|
+
→ topic `draft`
|
|
23
|
+
- [ ] 6. Parameterize user-specific URL segments; mark secrets. → topic `params`
|
|
24
|
+
- [ ] 7. `replay_request(captureId, draftId)`, then again with
|
|
25
|
+
`withoutSecrets: true`. → topic `prove`
|
|
26
|
+
- [ ] 8. `run_proof(captureId, draftId, ownerAddress)`.
|
|
27
|
+
→ topics `prove`, `credentials`
|
|
28
|
+
- [ ] 9. Resolve a `providerId` (**reuse before create**) →
|
|
29
|
+
`create_provider_version_from_capture` → `publish_provider_version` or
|
|
30
|
+
`submit_provider_version_for_review`. → topic `publish`
|
|
31
|
+
|
|
32
|
+
Multi-request provider? Run steps 5–8 once per request, then publish every
|
|
33
|
+
proven draft in one call. See topic `publish`.
|
|
34
|
+
|
|
35
|
+
<!-- topic: auth -->
|
|
3
36
|
## Getting authenticated
|
|
4
37
|
|
|
5
|
-
**Check first:**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
38
|
+
**Check first:** call `get_me`. A profile back means you're authenticated —
|
|
39
|
+
proceed.
|
|
40
|
+
|
|
41
|
+
**On 401:** `authenticate({})` starts a device-pairing session. It returns
|
|
42
|
+
immediately with a `code` and `attachUrl` — it does NOT poll.
|
|
43
|
+
|
|
44
|
+
1. Tell the user: _"Open `<attachUrl>`, sign in, and click 'Authorize this
|
|
45
|
+
device'."_
|
|
46
|
+
2. After they confirm, call `authenticate({ code: "<code>" })` —
|
|
47
|
+
`LINKED` → proceed; `PENDING` → wait and call again; `EXPIRED` → start over.
|
|
48
|
+
|
|
49
|
+
The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs
|
|
50
|
+
(`RECLAIM_API_TOKEN` in the server env overrides it).
|
|
51
|
+
|
|
52
|
+
<!-- topic: browser -->
|
|
53
|
+
<!-- common: browser -->
|
|
54
|
+
|
|
55
|
+
<!-- topic: capture -->
|
|
56
|
+
## Capture
|
|
57
|
+
|
|
58
|
+
1. **Attach** — topic `browser`.
|
|
59
|
+
|
|
60
|
+
2. **Pick a tab and start the capture.** `list_tabs`, then
|
|
61
|
+
`start_capture(tabId)` ⇒ a **`captureId`**: the session handle every browser
|
|
62
|
+
and inspect tool takes as its first argument (`navigate`, `eval_in_page`,
|
|
63
|
+
`wait_for_page`, `get_cookies`, `list_requests`, `find_requests_containing`,
|
|
64
|
+
`propose_provider`). If a tab is already on the right origin use it,
|
|
65
|
+
otherwise the first is usually fine. Capturing from the start is harmless —
|
|
66
|
+
it also records the login navigation. Then `navigate(captureId, url)` to
|
|
67
|
+
the site you inferred from the developer's request ("GitHub username" →
|
|
68
|
+
`https://github.com`). Don't ask the developer to navigate.
|
|
69
|
+
|
|
70
|
+
3. <!-- common: login-detect -->
|
|
71
|
+
|
|
72
|
+
4. **Navigate to the page that displays the value.** Construct the URL from what
|
|
73
|
+
you know about the site; eval for anything you need (for example, GitHub's
|
|
74
|
+
`meta[name="user-login"]`'s `content`), then `navigate(captureId, url)`.
|
|
75
|
+
|
|
76
|
+
5. **Trigger the request.** Capture has been live since step 2, so make the
|
|
77
|
+
call fire: `eval_in_page(captureId, "location.reload()")`, or click an
|
|
78
|
+
element / hit a SPA route programmatically. Don't ask the developer to click.
|
|
79
|
+
|
|
80
|
+
6. <!-- common: identify-value -->
|
|
81
|
+
|
|
82
|
+
7. <!-- common: find-request -->
|
|
83
|
+
|
|
84
|
+
<!-- topic: draft -->
|
|
85
|
+
## Draft the provider
|
|
86
|
+
|
|
87
|
+
<!-- common: oprf -->
|
|
88
|
+
|
|
89
|
+
**Call `propose_provider`.** It returns a **`draftId`** — the handle for
|
|
90
|
+
`replay_request` and `run_proof` — plus the secret-free `provider` and
|
|
91
|
+
`secretRefs`. The recipe **and the captured secret values** live server-side
|
|
92
|
+
under that `draftId`; the secrets never enter the transcript, so you carry the
|
|
93
|
+
`draftId`, not the provider JSON, into replay/prove.
|
|
94
|
+
|
|
95
|
+
**Extracting MORE THAN ONE value?** Prefer a **single request whose response
|
|
96
|
+
contains all of them** and draft them together — one `responseMatches` +
|
|
97
|
+
`responseRedactions` pair per value. Find that request by running
|
|
98
|
+
`find_requests_containing` for each value and picking a `requestId` common to
|
|
99
|
+
all. Then pass a `targets` array, each entry with its `value` (exactly as it
|
|
100
|
+
appears) and a distinct `name`:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
propose_provider(captureId, requestId, name, targets: [
|
|
104
|
+
{ value: "Scratch-net", name: "username" },
|
|
105
|
+
{ value: "Alex", name: "name" },
|
|
106
|
+
])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The shorthand `propose_provider(captureId, requestId, target, name, hash?)`
|
|
110
|
+
still works for one target. Per-target `hash` works inside `targets` too.
|
|
111
|
+
|
|
112
|
+
**Inspect what comes back:**
|
|
113
|
+
|
|
114
|
+
- Is the right header marked secret? (cookies, `Authorization`, `X-*` tokens)
|
|
115
|
+
- Is `responseMatches[0].value` a `contains` with a `{{name}}` template matching
|
|
116
|
+
the captured value's structural context?
|
|
117
|
+
- Is `paramValues[name]` the original captured value?
|
|
118
|
+
- Does each `responseRedactions` entry carry an `xPath`/`jsonPath` anchor
|
|
119
|
+
**plus** a named-capture `regex` isolating the value? The synthesizer adds the
|
|
120
|
+
regex in both cases — an HTML reveal is the element markup and a jsonPath
|
|
121
|
+
reveal is the surrounding `"key":"value"`, so neither is the bare value
|
|
122
|
+
without it. (With `hash`, the regex also carries `hash: oprf-…`.)
|
|
123
|
+
|
|
124
|
+
**Provenance — match the structure, never the bare value.** The `contains`
|
|
125
|
+
template and the redaction regex are always anchored on the value's surrounding
|
|
126
|
+
context — the JSON key (`"login":"{{username}}"`) or the enclosing element
|
|
127
|
+
(`<div class="handle">{{username}}</div>`) — never `{{username}}` alone. That
|
|
128
|
+
ties the proof to *where the value legitimately appears*; a bare-value match
|
|
129
|
+
would let a user surface the same string from any field they control. The
|
|
130
|
+
synthesizer does this for you.
|
|
131
|
+
|
|
132
|
+
**Canonical drafted shape** (no hash):
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"responseMatches": [
|
|
137
|
+
{ "type": "contains", "value": "\"login\":\"{{username}}\"" }
|
|
138
|
+
],
|
|
139
|
+
"responseRedactions": [
|
|
140
|
+
{
|
|
141
|
+
"xPath": "//script[@id=\"client-env\"]",
|
|
142
|
+
"regex": "\"login\":\"(?<username>[A-Za-z0-9_-]+)\""
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"paramValues": { "username": "Scratch-net" }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
With `hash: 'oprf-raw'`, the redaction gains `"hash": "oprf-raw"`. Nothing else
|
|
150
|
+
changes.
|
|
79
151
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
**Canonical drafted shape** (no hash):
|
|
83
|
-
|
|
84
|
-
```json
|
|
85
|
-
{
|
|
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" }
|
|
96
|
-
}
|
|
97
|
-
```
|
|
152
|
+
<!-- common: rewrite-shape-block -->
|
|
98
153
|
|
|
99
|
-
|
|
154
|
+
<!-- topic: params -->
|
|
155
|
+
## Parameters: extracted, consumer-supplied, and secret
|
|
100
156
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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" }
|
|
114
|
-
}
|
|
115
|
-
```
|
|
157
|
+
**Parameterize user-specific URL segments** so the provider isn't pinned to one
|
|
158
|
+
user. The matcher already puts the *captured* value in `paramValues`. If the URL
|
|
159
|
+
*also* carries user-identifying segments, those are **consumer-supplied context
|
|
160
|
+
params**: at verification time their values come from the consumer's session
|
|
161
|
+
`context`, not from extraction.
|
|
116
162
|
|
|
117
|
-
|
|
163
|
+
Replace them with `{{context.<name>}}`, for example
|
|
164
|
+
`https://github.com/Scratch-net/repos` →
|
|
165
|
+
`https://github.com/{{context.username}}/repos`, with `paramValues: { …,
|
|
166
|
+
"context.username": "Scratch-net" }` for replay/proof (the key must match the
|
|
167
|
+
placeholder exactly, dot included).
|
|
118
168
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
replace with `{{context.<name>}}`. E.g. `https://github.com/Scratch-net/repos` → `https://github.com/{{context.username}}/repos`, with `paramValues: { ..., "context.username": "Scratch-net" }` for replay/proof (the paramValues key must match the placeholder name exactly, dot included). Declare these in the version's `requiredContext` at publish time (step 13).
|
|
122
|
-
- Query parameters with user-specific values → same treatment.
|
|
169
|
+
- Query params with user-specific values → same treatment.
|
|
123
170
|
- Generic segments (`/api/v3`, `/users`, `/repos`) stay literal.
|
|
124
|
-
-
|
|
171
|
+
- No user-identifying segments (for example, `/settings/profile`) → leave it
|
|
172
|
+
alone.
|
|
125
173
|
|
|
126
|
-
|
|
174
|
+
Declare them in the version's `requiredContext` at publish time (topic
|
|
175
|
+
`publish`).
|
|
127
176
|
|
|
128
|
-
|
|
177
|
+
<!-- common: secrets -->
|
|
129
178
|
|
|
130
|
-
The
|
|
179
|
+
The `context.` prefix is a **Builder-only namespace** (consumer-supplied vs
|
|
180
|
+
extracted-from-traffic). The attestor itself only looks up `{{whatever}}` in
|
|
181
|
+
`paramValues`/`secretParams.paramValues` and doesn't care about the prefix.
|
|
131
182
|
|
|
132
183
|
<!-- common: no-rename-param -->
|
|
133
184
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
- Requests
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
### How the verification client resolves these at runtime
|
|
186
|
+
|
|
187
|
+
- Requests match by **exact origin+path** (query ignored). A
|
|
188
|
+
`{{context.<name>}}` segment matches **only the value the consumer sent**
|
|
189
|
+
(validated against `requiredContext`); a bare `{{name}}` segment matches any
|
|
190
|
+
one path segment and its value is **auto-extracted from live traffic**.
|
|
191
|
+
- Interpolation and `contains` matching are **plain text** — JSON, XML, HTML,
|
|
192
|
+
form-encoded, anything.
|
|
193
|
+
- Extract selectors apply **client-side in spec order xPath → jsonPath →
|
|
194
|
+
regex**, each to the prior selection. Only the FIRST selector constrains the
|
|
195
|
+
full response (an `xPath`→`jsonPath` chain reads JSON embedded in HTML; a
|
|
196
|
+
jsonPath-first extract needs the whole response to be JSON). Only the selected
|
|
197
|
+
portions reach the attestor.
|
|
198
|
+
- A matched response that can't satisfy the recipe is **skipped, not failed**
|
|
199
|
+
(the first selector can't operate on it, or a required `contains` is missing).
|
|
200
|
+
The client keeps polling until timeout.
|
|
201
|
+
- So: `{{context.<name>}}` when the consumer knows the value up front and the
|
|
202
|
+
proof should be **pinned** to it; bare when the value should come from
|
|
203
|
+
whatever the logged-in user's traffic shows.
|
|
204
|
+
|
|
205
|
+
`propose_provider` returns `verificationWarnings` for calls only an author can
|
|
206
|
+
make (currently: a captured value baked literally into the URL, needing the
|
|
207
|
+
`{{context.<name>}}`-vs-bare-vs-recapture decision). Advisory, never blocking —
|
|
208
|
+
**surface each one verbatim** and resolve it before publishing.
|
|
209
|
+
|
|
210
|
+
<!-- topic: prove -->
|
|
211
|
+
## Replay, then prove
|
|
212
|
+
|
|
213
|
+
**1. Replay.** `replay_request(captureId, draftId)`:
|
|
145
214
|
|
|
146
215
|
- `matchesOriginal: true` → proceed.
|
|
147
|
-
- `
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
216
|
+
- `false` with `hints` mentioning auth/csrf → re-trigger the action yourself
|
|
217
|
+
(navigate or reload with the tools) and redraft. Don't ask the developer to
|
|
218
|
+
redo it.
|
|
219
|
+
- `false` with a stable diff (timestamps, and so on) → tighten the matcher
|
|
220
|
+
and retry.
|
|
221
|
+
|
|
222
|
+
**2. The authentication-bound check — mandatory.**
|
|
223
|
+
`replay_request(captureId, draftId, withoutSecrets: true)` re-issues the request
|
|
224
|
+
with no secrets/cookies, as an anonymous visitor.
|
|
225
|
+
|
|
226
|
+
- FAILS to extract (401/403/redirect, or the value is gone) → the endpoint is
|
|
227
|
+
auth-bound. Good: the proof genuinely means "the holder of these secrets saw
|
|
228
|
+
this value."
|
|
229
|
+
- STILL extracts the value → the endpoint is **PUBLIC**; the proof binds to
|
|
230
|
+
nobody. **Do NOT prove it.** Find a request whose response depends on the
|
|
231
|
+
session (an API keyed to the logged-in user — `/settings/*`, a
|
|
232
|
+
`viewer`/`currentUser` API) and re-check. If the value only ever appears on
|
|
233
|
+
public responses, tell the developer it can't be proven as an identity claim.
|
|
234
|
+
|
|
235
|
+
**3. Prove.** `run_proof(captureId, draftId, ownerAddress)`. On
|
|
236
|
+
`verified: true` the full claim is written to
|
|
237
|
+
`~/.reclaim/claims/<timestamp>.json` and you get
|
|
238
|
+
`{verified, extractedValue, extractedParameters, identifier, owner, claimPath, claim}`.
|
|
239
|
+
`claim` is display-ready (huge `request` payload stripped, byte arrays base64'd)
|
|
240
|
+
— **show it to the developer as a fenced JSON block at the end**, with the
|
|
241
|
+
provider JSON. That's the final result.
|
|
242
|
+
|
|
243
|
+
**`errorKind: "missing-owner-key"`** → no eth proof-owner key yet. Tell the
|
|
244
|
+
developer what's happening and drive the bootstrap yourself — see topic
|
|
245
|
+
`credentials`. No restart needed, and the existing `draftId` stays valid.
|
|
158
246
|
|
|
159
247
|
<!-- common: prove-tail -->
|
|
160
248
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
249
|
+
<!-- topic: publish -->
|
|
250
|
+
## Publish
|
|
251
|
+
|
|
252
|
+
`provider` is the JSON from the draft step with the `params` URL
|
|
253
|
+
parameterization applied. `initialUrl` is the page where the user logs in / the
|
|
254
|
+
value is displayed (for example, `https://github.com/Syed`) — **not** the API
|
|
255
|
+
endpoint URL inside the provider.
|
|
256
|
+
|
|
257
|
+
**1. Reuse before create — this is how duplicates happen.**
|
|
258
|
+
`create_provider_version_from_capture` requires a `providerId`. Before minting a
|
|
259
|
+
new one, search your existing providers with `providers` (by domain / data
|
|
260
|
+
point) — a site you've already built is there. Match → pass its
|
|
261
|
+
`providerId` and the capture lands as a NEW VERSION on it. Only call
|
|
262
|
+
`create_provider(orgId, domain, slug, title, description)` when none exists
|
|
263
|
+
(`orgId` from `list_orgs`; `domain` such as `github.com`, `slug` such as
|
|
264
|
+
`github-username`, `title` such as "GitHub Username"). It starts PRIVATE with an
|
|
265
|
+
empty seed version. Routing every run through `create_provider` is what stacks
|
|
266
|
+
duplicates in "My providers".
|
|
267
|
+
|
|
268
|
+
**2. Persist the version.**
|
|
269
|
+
`create_provider_version_from_capture(providerId, provider, initialUrl, notes)`
|
|
270
|
+
— `notes` such as `"Initial draft from browser capture"`. It lands on the
|
|
271
|
+
`draft` branch.
|
|
272
|
+
|
|
273
|
+
- **Multi-request provider** (landing call + details call, a
|
|
274
|
+
CSRF-preflight/refresh-token chain, …): run capture→draft→replay→prove ONCE
|
|
275
|
+
PER REQUEST, then pass `providers: [draftA, draftB, …]` instead of the single
|
|
276
|
+
`provider`. Each becomes its own request on the version and **all** are
|
|
277
|
+
required at verification time.
|
|
278
|
+
- **Login/navigation-gated data:** supply `jsUserScripts` (plain page JS that
|
|
279
|
+
drives login/waits/navigation so the target request fires), or
|
|
280
|
+
`allowedJsRequests` (extra requests the script may fire, for example,
|
|
281
|
+
pagination). → topic `user-script`. Before populating `allowedJsRequests` for
|
|
282
|
+
a `verifyProof({ providerId })` use case, read topic `hash-validation` — a
|
|
283
|
+
session-unique URL there makes verifyProof reject every real proof.
|
|
284
|
+
|
|
285
|
+
**3. `requiredContext` is derived for you.** The publish tool scans the recipe's
|
|
286
|
+
url/body for `{{context.<name>}}` placeholders and emits a JSON Schema requiring
|
|
287
|
+
each — the Builder then validates every session-create's `context` against it (a
|
|
288
|
+
clear 400 instead of a late attestor failure). Extra context fields are always
|
|
289
|
+
allowed; it's optional when the recipe has no `{{context.*}}` placeholders.
|
|
290
|
+
Derived properties are plain strings; refine types or add descriptions with
|
|
291
|
+
`update_provider_version` while the version is still on `draft`:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{ "requiredContext": { "type": "object", "required": ["username"], "properties": { "username": { "type": "string", "description": "GitHub username to verify" } } } }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**4. Go live.** PRIVATE → `publish_provider_version`. PUBLIC →
|
|
298
|
+
`submit_provider_version_for_review(providerId, version)`, then tell the
|
|
299
|
+
developer it's awaiting admin review.
|
|
300
|
+
|
|
301
|
+
**5. Tell the developer how consumers call it.** Sessions must include the
|
|
302
|
+
context params — for example, `context: { "username": "…" }` in
|
|
303
|
+
`create_verification_session`. Quote the `requiredContext` from the created
|
|
304
|
+
version verbatim — a session missing those fields gets a 400, and verification
|
|
305
|
+
matches the supplied values exactly.
|
|
306
|
+
|
|
307
|
+
**6. The browser is still open.** The response's `_notes` says so. Surface it
|
|
308
|
+
and ask whether the developer wants to keep the session for further edits — call
|
|
309
|
+
`dispose_browser` only once they confirm. Never close it unprompted; a
|
|
310
|
+
`builder`-mode session is quota-accounted, so don't leave it idle either.
|
|
311
|
+
|
|
312
|
+
<!-- topic: discover -->
|
|
313
|
+
## Discover and resolve providers for the developer's app
|
|
314
|
+
|
|
315
|
+
When a developer wants to add verification but doesn't know which providers to
|
|
316
|
+
use, run the same flow the Discover UI does:
|
|
317
|
+
|
|
318
|
+
1. Ask which website their users prove data from.
|
|
319
|
+
2. `discover_data_points({ domain })` → what's verifiable there. Confirm the
|
|
320
|
+
data points with the developer.
|
|
321
|
+
3. `discover_resolve({ domain, dataPoints })` → the smallest provider set
|
|
322
|
+
(`providers`, greedy combine), the `requiredContext` inputs to collect, and
|
|
323
|
+
any `uncovered` points (author those with the flow above, then re-resolve).
|
|
324
|
+
4. `create_verification_session({ providers: [{ providerId }, …], context,
|
|
325
|
+
verificationClientUrl })` with EVERY resolved provider id and the collected
|
|
326
|
+
context.
|
|
327
|
+
|
|
328
|
+
`merge.mergeable` only signals the set *could* collapse into one provider — you
|
|
329
|
+
still pass them all; the Builder runs them in one session.
|
|
330
|
+
|
|
331
|
+
**Verification sessions.** Consumers authenticate with a per-org token
|
|
332
|
+
(`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create
|
|
333
|
+
sessions via `POST /verifications/sessions`. Results are ECIES-encrypted
|
|
334
|
+
(secp256k1 + AES-256-GCM) to the org's eth public key when `canEncryptResult` is
|
|
335
|
+
enabled (set via `PUT /orgs/{orgId}/keypair`) and delivered to the org's
|
|
336
|
+
callback subscriptions; plaintext otherwise. Troubleshoot with
|
|
337
|
+
`get_verification_session`, `list_verification_events`,
|
|
338
|
+
`list_org_verification_sessions`.
|
|
339
|
+
|
|
340
|
+
<!-- topic: never-do -->
|
|
182
341
|
## Things to never do
|
|
183
342
|
|
|
184
343
|
<!-- common: never-do-common -->
|
|
185
|
-
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<!--
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
344
|
+
- Never request `hash` for a target whose value also appears in the request URL
|
|
345
|
+
(topic `draft`), and never `hash` a free-form target — `propose_provider`
|
|
346
|
+
throws either way; retrying won't help.
|
|
347
|
+
|
|
348
|
+
<!-- topic: credentials -->
|
|
349
|
+
<!-- common: credentials -->
|
|
350
|
+
|
|
351
|
+
<!-- topic: troubleshooting -->
|
|
352
|
+
## Troubleshooting
|
|
353
|
+
|
|
354
|
+
**`unknown draftId` / `no capture`.** The draft is bound to its capture, held in
|
|
355
|
+
the MCP server process. Anything that restarts the server (Claude Code restart,
|
|
356
|
+
`reset_session`, reboot) drops every capture and its drafts. The fix is always
|
|
357
|
+
`start_capture` → interact → `propose_provider` for a fresh `draftId`. Don't ask
|
|
358
|
+
the developer to "re-enter the value" — walk them back through capture.
|
|
359
|
+
|
|
360
|
+
**Replaying as an anonymous visitor.** Pass `withoutSecrets: true` to
|
|
361
|
+
`replay_request`; the draft still holds the captured secrets, so you never
|
|
362
|
+
re-supply them.
|
|
363
|
+
|
|
364
|
+
**Site won't load, won't let the user sign in, or breaks right after
|
|
365
|
+
sign-in.** Check WHICH client the session targets first: on `builder` (the
|
|
366
|
+
default) and `portals` interception happens over CDP, so it is never the
|
|
367
|
+
cause — look for a captcha instead. On `inapp-sdk`, `verifier-app`, or the
|
|
368
|
+
browser extension, suspect interception before the recipe, and bisect: FIRST
|
|
369
|
+
turn
|
|
370
|
+
document replay off and keep the interceptor (`disableRequestReplay: true`, or
|
|
371
|
+
`isDocumentRequestReplayEnabled: false` on builder) — often that alone is what
|
|
372
|
+
breaks the page, and it's the smallest change. Only if that doesn't help, turn
|
|
373
|
+
interception off entirely (`injectionType: NONE`, or omit
|
|
374
|
+
`interceptorOptions`). Full checklist and remedies: topic `interception`.
|
|
375
|
+
|
|
376
|
+
**Proof failures / attestor errors** → topic `prove`.
|
|
377
|
+
**Browser won't launch or attach** → topic `browser`.
|
|
378
|
+
**Missing signing key** → topic `credentials`.
|