@reclaimprotocol/agent 0.1.0 → 0.1.1
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 +1 -1
- package/lib/cdp/capture-engine.d.ts +66 -0
- package/lib/cdp/capture-engine.js +153 -0
- package/lib/cdp/capture.d.ts +9 -20
- package/lib/cdp/capture.js +14 -138
- package/lib/cdp/index.d.ts +3 -0
- package/lib/cdp/index.js +6 -0
- package/lib/config/store.d.ts +7 -6
- package/lib/config/store.js +2 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/mcp/api/operations.gen.d.ts +101 -45
- package/lib/mcp/api/operations.gen.js +996 -126
- package/lib/mcp/api/to-zod.d.ts +24 -0
- package/lib/mcp/api/to-zod.js +94 -0
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/credentials/authenticate.d.ts +3 -0
- package/lib/mcp/{tools/credentials → credentials}/authenticate.js +3 -3
- package/lib/mcp/credentials/build.d.ts +5 -0
- package/lib/mcp/credentials/build.js +18 -0
- package/lib/mcp/{tools/credentials → credentials}/cache.d.ts +9 -9
- package/lib/mcp/{tools/credentials → credentials}/cache.js +36 -19
- package/lib/mcp/credentials/import.d.ts +2 -0
- package/lib/mcp/{tools/credentials → credentials}/import.js +2 -2
- package/lib/mcp/credentials/issue.d.ts +2 -0
- package/lib/mcp/{tools/credentials → credentials}/issue.js +4 -4
- package/lib/mcp/credentials/keypair.d.ts +17 -0
- package/lib/mcp/credentials/keypair.js +48 -0
- package/lib/mcp/credentials/link.d.ts +3 -0
- package/lib/mcp/credentials/link.js +107 -0
- package/lib/mcp/{tools/credentials → credentials}/list-cached.d.ts +1 -1
- package/lib/mcp/{tools/credentials → credentials}/list-cached.js +5 -5
- package/lib/mcp/credentials/owner-key.d.ts +23 -0
- package/lib/mcp/credentials/owner-key.js +58 -0
- package/lib/mcp/credentials/paths.d.ts +12 -0
- package/lib/mcp/credentials/paths.js +35 -0
- package/lib/mcp/credentials/poll.d.ts +27 -0
- package/lib/mcp/credentials/poll.js +61 -0
- package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.d.ts +1 -1
- package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.js +2 -2
- package/lib/mcp/credentials/session-cache.d.ts +14 -0
- package/lib/mcp/credentials/session-cache.js +42 -0
- package/lib/mcp/{tools/credentials → credentials}/status.d.ts +1 -1
- package/lib/mcp/credentials/status.js +76 -0
- package/lib/mcp/instructions.d.ts +1 -0
- package/lib/mcp/instructions.js +11 -0
- package/lib/mcp/register.d.ts +38 -0
- package/lib/mcp/register.js +80 -0
- package/lib/mcp/server.d.ts +2 -2
- package/lib/mcp/server.js +3 -3
- package/lib/mcp/start.js +10 -4
- package/lib/mcp/tools/agent/build.js +2 -15
- package/lib/mcp/tools/agent/index.d.ts +2 -0
- package/lib/mcp/tools/agent/index.js +3 -0
- package/lib/mcp/tools/agent/proof.js +1 -6
- package/lib/mcp/tools/agent/propose.js +13 -2
- package/lib/mcp/tools/agent/publish.js +10 -2
- package/lib/mcp/tools/agent/replay.js +0 -5
- package/lib/mcp/tools/analyze.d.ts +3 -0
- package/lib/mcp/tools/analyze.js +26 -0
- package/lib/mcp/tools/attach.d.ts +11 -0
- package/lib/mcp/tools/attach.js +72 -0
- package/lib/mcp/tools/authenticate/authenticate.js +107 -0
- package/lib/mcp/tools/authenticate/build.d.ts +15 -0
- package/lib/mcp/tools/authenticate/build.js +28 -0
- package/lib/mcp/tools/authenticate/decrypt-key.d.ts +32 -0
- package/lib/mcp/tools/authenticate/decrypt-key.js +49 -0
- package/lib/mcp/tools/authenticate/eth-key.d.ts +38 -0
- package/lib/mcp/tools/authenticate/eth-key.js +130 -0
- package/lib/mcp/tools/authenticate/import.js +57 -0
- package/lib/mcp/tools/authenticate/index.d.ts +2 -0
- package/lib/mcp/tools/authenticate/index.js +4 -0
- package/lib/mcp/tools/authenticate/issue.js +42 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.d.ts +13 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.js +53 -0
- package/lib/mcp/tools/capture.d.ts +10 -0
- package/lib/mcp/tools/capture.js +142 -0
- package/lib/mcp/tools/eval.d.ts +5 -0
- package/lib/mcp/tools/eval.js +127 -0
- package/lib/mcp/tools/find.d.ts +3 -0
- package/lib/mcp/tools/find.js +60 -0
- package/lib/mcp/tools/index.d.ts +3 -0
- package/lib/mcp/tools/index.js +6 -0
- package/lib/mcp/tools/owner-key.d.ts +2 -0
- package/lib/mcp/tools/owner-key.js +63 -0
- package/lib/mcp/tools/proof.d.ts +3 -0
- package/lib/mcp/tools/proof.js +214 -0
- package/lib/mcp/tools/propose.d.ts +9 -0
- package/lib/mcp/tools/propose.js +75 -0
- package/lib/mcp/tools/publish.d.ts +3 -0
- package/lib/mcp/tools/publish.js +59 -0
- package/lib/mcp/tools/replay.d.ts +3 -0
- package/lib/mcp/tools/replay.js +46 -0
- package/lib/mcp/tools/session.d.ts +10 -0
- package/lib/mcp/tools/session.js +29 -0
- package/lib/mcp/tools/verification/build.d.ts +12 -0
- package/lib/mcp/tools/verification/build.js +14 -0
- package/lib/mcp/tools/verification/diagnose.d.ts +11 -0
- package/lib/mcp/tools/verification/diagnose.js +132 -0
- package/lib/mcp/tools/verification/index.d.ts +1 -0
- package/lib/mcp/tools/verification/index.js +2 -0
- package/lib/mcp/tools/verification/result.d.ts +12 -0
- package/lib/mcp/tools/verification/result.js +126 -0
- package/lib/mcp/verifications/build.d.ts +5 -0
- package/lib/mcp/verifications/build.js +14 -0
- package/lib/mcp/verifications/create.d.ts +3 -0
- package/lib/mcp/verifications/create.js +57 -0
- package/lib/mcp/verifications/find.d.ts +3 -0
- package/lib/mcp/verifications/find.js +37 -0
- package/lib/mcp/verifications/get.d.ts +3 -0
- package/lib/mcp/verifications/get.js +26 -0
- package/lib/mcp/verifications/update.d.ts +3 -0
- package/lib/mcp/verifications/update.js +78 -0
- package/lib/old/client.d.ts +3 -0
- package/lib/old/instructions.d.ts +0 -6
- package/lib/old/instructions.js +6 -24
- package/lib/old/to-register.d.ts +3 -0
- package/lib/old/to-register.js +35 -7
- package/lib/old/tools/publish.js +13 -1
- package/lib/proof/attestor.d.ts +33 -1
- package/lib/proof/attestor.js +67 -61
- package/lib/proof/index.d.ts +5 -0
- package/lib/proof/index.js +6 -0
- package/lib/proof/proof-core.d.ts +35 -0
- package/lib/proof/proof-core.js +73 -0
- package/lib/proof/zk-files.d.ts +14 -0
- package/lib/proof/zk-files.js +73 -0
- package/lib/provider/index.d.ts +5 -0
- package/lib/provider/index.js +9 -0
- package/lib/provider/to-version.d.ts +9 -12
- package/lib/provider/to-version.js +38 -4
- package/lib/provider/verification-checks.d.ts +12 -0
- package/lib/provider/verification-checks.js +30 -0
- package/lib/routes.gen.d.ts +271 -0
- package/lib/routes.gen.js +73 -0
- package/lib/types/openapi.gen.d.ts +3967 -0
- package/lib/types/openapi.gen.js +5 -0
- package/package.json +18 -10
- package/skill/SKILL.md +35 -14
- package/skill/builder-mode.md +9 -0
- package/skill/old-mode.md +13 -0
- package/src/cdp/capture-engine.ts +256 -0
- package/src/cdp/capture.ts +25 -185
- package/src/cdp/index.ts +6 -0
- package/src/config/store.ts +7 -6
- package/src/index.ts +2 -4
- package/src/mcp/api/operations.gen.ts +1023 -121
- package/src/mcp/api/to-zod.ts +115 -0
- package/src/mcp/api/tools.ts +9 -3
- package/src/mcp/instructions.ts +15 -0
- package/src/mcp/server.ts +5 -4
- package/src/mcp/start.ts +14 -4
- package/src/mcp/tools/agent/build.ts +2 -18
- package/src/mcp/tools/agent/index.ts +4 -0
- package/src/mcp/tools/agent/proof.ts +1 -7
- package/src/mcp/tools/agent/propose.ts +13 -2
- package/src/mcp/tools/agent/publish.ts +10 -2
- package/src/mcp/tools/agent/replay.ts +0 -5
- package/src/mcp/tools/{credentials → authenticate}/authenticate.ts +9 -8
- package/src/mcp/tools/authenticate/build.ts +32 -0
- package/src/mcp/tools/authenticate/decrypt-key.ts +64 -0
- package/src/mcp/tools/authenticate/eth-key.ts +182 -0
- package/src/mcp/tools/authenticate/import.ts +73 -0
- package/src/mcp/tools/authenticate/index.ts +4 -0
- package/src/mcp/tools/authenticate/issue.ts +50 -0
- package/src/mcp/tools/authenticate/resolve-owner-key.ts +60 -0
- package/src/mcp/tools/index.ts +6 -0
- package/src/mcp/tools/verification/build.ts +16 -0
- package/src/mcp/tools/verification/index.ts +2 -0
- package/src/mcp/tools/verification/result.ts +155 -0
- package/src/old/client.ts +3 -0
- package/src/old/instructions.ts +10 -25
- package/src/old/to-register.ts +62 -27
- package/src/old/tools/publish.ts +17 -1
- package/src/proof/attestor.ts +115 -69
- package/src/proof/index.ts +21 -0
- package/src/proof/proof-core.ts +109 -0
- package/src/proof/zk-files.ts +83 -0
- package/src/provider/index.ts +20 -0
- package/src/provider/to-version.ts +52 -11
- package/src/provider/verification-checks.ts +36 -0
- package/lib/mcp/tools/credentials/build.d.ts +0 -16
- package/lib/mcp/tools/credentials/build.js +0 -33
- package/lib/mcp/tools/credentials/status.js +0 -62
- package/src/mcp/tools/credentials/build.ts +0 -37
- package/src/mcp/tools/credentials/cache.ts +0 -161
- package/src/mcp/tools/credentials/env-file.ts +0 -101
- package/src/mcp/tools/credentials/import.ts +0 -72
- package/src/mcp/tools/credentials/issue.ts +0 -107
- package/src/mcp/tools/credentials/list-cached.ts +0 -66
- package/src/mcp/tools/credentials/register.ts +0 -49
- package/src/mcp/tools/credentials/resolve-owner-key.ts +0 -78
- package/src/mcp/tools/credentials/status.ts +0 -82
- package/src/types/openapi.ts +0 -7
- /package/lib/mcp/{tools/credentials → credentials}/env-file.d.ts +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/env-file.js +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/register.d.ts +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/register.js +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/authenticate.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/import.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/issue.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/session-cache.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/session-cache.js +0 -0
- /package/src/mcp/tools/{credentials → authenticate}/session-cache.ts +0 -0
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.1",
|
|
5
5
|
"description": "Reclaim Protocol agent — SDK, CLI, and MCP server in one package. SDK + CLI talk to the builder backend; MCP server adds Chrome CDP capture, provider synthesis, replay, and zkTLS proof.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
"types": "./lib/index.d.ts",
|
|
12
12
|
"import": "./lib/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./provider": {
|
|
15
|
+
"types": "./lib/provider/index.d.ts",
|
|
16
|
+
"import": "./lib/provider/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./capture": {
|
|
19
|
+
"types": "./lib/cdp/index.d.ts",
|
|
20
|
+
"import": "./lib/cdp/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./proof": {
|
|
23
|
+
"types": "./lib/proof/index.d.ts",
|
|
24
|
+
"import": "./lib/proof/index.js"
|
|
25
|
+
},
|
|
14
26
|
"./mcp/register": {
|
|
15
27
|
"types": "./lib/mcp/register.d.ts",
|
|
16
28
|
"import": "./lib/mcp/register.js"
|
|
@@ -38,15 +50,14 @@
|
|
|
38
50
|
"websiteUrl": "https://docs.reclaimprotocol.org",
|
|
39
51
|
"scripts": {
|
|
40
52
|
"build": "tsc && node scripts/prepend-shebang.js && chmod 755 lib/cli.js lib/mcp-bin.js",
|
|
41
|
-
"openapi:typegen": "node ../app/scripts/mcp-tools-gen.ts",
|
|
42
53
|
"prepublishOnly": "npm run build",
|
|
43
54
|
"dev": "tsc --watch",
|
|
44
|
-
"test": "node --test tests/*.test.ts",
|
|
55
|
+
"test": "node --env-file=.test.env --test tests/*.test.ts",
|
|
56
|
+
"check": "tsc -p tsconfig.test.json",
|
|
45
57
|
"start:cli": "node lib/cli.js",
|
|
46
58
|
"start:mcp": "node lib/mcp-bin.js",
|
|
47
59
|
"inspect": "npx @modelcontextprotocol/inspector node lib/mcp-bin.js",
|
|
48
|
-
"download:zk-files": "node scripts/download-zk-files.js"
|
|
49
|
-
"postinstall": "node scripts/download-zk-files.js"
|
|
60
|
+
"download:zk-files": "node scripts/download-zk-files.js"
|
|
50
61
|
},
|
|
51
62
|
"keywords": [
|
|
52
63
|
"mcp",
|
|
@@ -69,15 +80,12 @@
|
|
|
69
80
|
"dependencies": {
|
|
70
81
|
"@hapi/boom": "^9.1.4",
|
|
71
82
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
72
|
-
"@noble/hashes": "^2.2.0",
|
|
73
|
-
"@noble/secp256k1": "^3.1.0",
|
|
74
83
|
"@reclaimprotocol/attestor-core": "^5.0.7",
|
|
75
84
|
"@reclaimprotocol/client": "*",
|
|
76
85
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
77
86
|
"chrome-remote-interface": "^0.33.0",
|
|
78
87
|
"pino": "^9.14.0",
|
|
79
|
-
"zod": "^
|
|
80
|
-
"zod-from-json-schema": "^0.1.0"
|
|
88
|
+
"zod": "^4.4.3"
|
|
81
89
|
},
|
|
82
90
|
"devDependencies": {
|
|
83
91
|
"@types/chrome-remote-interface": "^0.31.0",
|
|
@@ -86,4 +94,4 @@
|
|
|
86
94
|
"typescript": "^5.9.0",
|
|
87
95
|
"yaml": "^2.6.0"
|
|
88
96
|
}
|
|
89
|
-
}
|
|
97
|
+
}
|
package/skill/SKILL.md
CHANGED
|
@@ -20,7 +20,7 @@ This MCP server targets one of two backends. Detect which from the tools availab
|
|
|
20
20
|
- **Old devtools (the default).** `get_me_providers` is present; the generated API tools (`get_me`, `create_verification`, `list_orgs`, …) are absent.
|
|
21
21
|
- **Builder.** The generated API tools are present; `get_me_providers` is absent.
|
|
22
22
|
|
|
23
|
-
Only **authentication** (below)
|
|
23
|
+
Only **authentication** (below), **publishing** (the final step), and the **consumer-supplied param namespace** differ between modes: values the Consumer supplies at verification time are templated `{{context.<name>}}` on builder vs `{{context_<name>}}` on old devtools (no dotted interpolation there) — see step 10. The capture → draft → replay → prove loop in between is identical, and OPRF is optional and supported in both.
|
|
24
24
|
|
|
25
25
|
## Getting authenticated
|
|
26
26
|
|
|
@@ -42,7 +42,7 @@ Call `reclaim_authenticate({})` with no arguments. It opens the Reclaim dashboar
|
|
|
42
42
|
2. Tell the user: _"Open `<attachUrl>`, sign in, and click 'Authorize this device'."_
|
|
43
43
|
3. After they confirm, call `authenticate({ code: "<code>" })`: `LINKED` → proceed; `PENDING` → wait and call again; `EXPIRED` → start a new session.
|
|
44
44
|
|
|
45
|
-
The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (`RECLAIM_API_TOKEN` in the server env overrides it).
|
|
45
|
+
The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (`RECLAIM_API_TOKEN` in the server env overrides it).
|
|
46
46
|
|
|
47
47
|
## The loop
|
|
48
48
|
|
|
@@ -144,16 +144,30 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
144
144
|
- 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.)
|
|
145
145
|
- Do not remove `paramValues` — that's how the template resolves at proof time.
|
|
146
146
|
- 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.
|
|
147
|
+
- 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.
|
|
147
148
|
|
|
148
149
|
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.
|
|
149
150
|
|
|
150
|
-
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,
|
|
151
|
+
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:
|
|
151
152
|
|
|
152
|
-
-
|
|
153
|
+
- **Builder mode:** 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).
|
|
154
|
+
- **Old devtools mode:** same idea, but the legacy backend doesn't support dotted interpolation — use `{{context_<name>}}` and `paramValues: { ..., "context_username": "Scratch-net" }`.
|
|
153
155
|
- Query parameters with user-specific values → same treatment.
|
|
154
156
|
- Generic segments (`/api/v3`, `/users`, `/repos`) stay literal.
|
|
155
157
|
- If the URL has no user-identifying segments (e.g. `/settings/profile`), leave it untouched — the matcher already handled the value via paramValues.
|
|
156
158
|
|
|
159
|
+
**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.
|
|
160
|
+
|
|
161
|
+
**How the verification client resolves these at runtime** (builder mode — use this to make the context-vs-bare call):
|
|
162
|
+
|
|
163
|
+
- Requests are matched by **exact origin+path** (query ignored). A `{{context.<name>}}` segment matches **only the value the consumer sent** in the session context (validated by `requiredContext`); a bare `{{name}}` segment matches any one path segment and its value is **auto-extracted from the live traffic**.
|
|
164
|
+
- Interpolation and `contains` matching are **plain text** — they work for any captured content (JSON, XML, HTML, form-encoded bodies, …).
|
|
165
|
+
- 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 (an `xPath`→`jsonPath` chain reads JSON embedded in an HTML page; a jsonPath-first extract needs the whole response to be JSON). Only the selected portions reach the attestor.
|
|
166
|
+
- A matched response that can't satisfy the recipe is **skipped**, not failed: the first selector can't operate on it (e.g. jsonPath-first against the HTML navigation of a URL whose JSON XHR was captured), or a required `contains` match is missing. The VC keeps polling for a satisfying request until timeout.
|
|
167
|
+
- So: choose `{{context.<name>}}` when the consumer knows the value up front and the proof should be **pinned to it**; keep it bare when the value should come from whatever the logged-in user's traffic shows.
|
|
168
|
+
|
|
169
|
+
`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.
|
|
170
|
+
|
|
157
171
|
11. **Replay.** Call `replay_request(provider, secretParamsRef)`. Three outcomes:
|
|
158
172
|
|
|
159
173
|
- `matchesOriginal: true` → proceed.
|
|
@@ -167,7 +181,7 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
167
181
|
|
|
168
182
|
12. **Prove.** Call `run_proof(provider, secretParamsRef)`. On `verified: true`, the full claim has been written to `~/.reclaim/claims/<timestamp>.json` and the tool returns `{verified, extractedValue, extractedParameters, identifier, owner, claimPath, claim}`. The `claim` field is a display-ready version of the proof — its huge `request` payload has been stripped and all byte arrays converted to base64, so it renders cleanly. **Show it to the dev as a fenced JSON code block at the end** along with the provider JSON. That's the final result. On `verified: false`, diagnose using the returned error and iterate.
|
|
169
183
|
|
|
170
|
-
**Missing signing credential (`errorKind: "missing-owner-key"`)** — the dev hasn't set up
|
|
184
|
+
**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.
|
|
171
185
|
|
|
172
186
|
**On any `verified: false` response, the result includes (inline):**
|
|
173
187
|
- `error` — the SDK's thrown error message verbatim
|
|
@@ -191,11 +205,18 @@ The 2-day token is cached in `~/.reclaim/config.json` and reused on later runs (
|
|
|
191
205
|
- **New version of an existing verification:** pass its `providerId` — the patch is auto-bumped above the highest existing version. Pass `notes` (≥10 chars) as the change note.
|
|
192
206
|
- `get_me_providers({ searchQuery? })` lists the verifications you own — use it to find a `providerId` or to confirm the result. It's the only provider-listing tool in this mode.
|
|
193
207
|
- There's no separate "create-then-version" step and no org / public-review workflow here.
|
|
208
|
+
- Consumer-supplied params (step 10) keep the `{{context_<name>}}` form in this mode; there is no `requiredContext` declaration on the legacy backend.
|
|
194
209
|
|
|
195
210
|
**Builder mode:**
|
|
196
211
|
1. **Create the verification if it doesn't exist:** `create_verification(orgId, domain, slug, title, description)` — find `orgId` via `list_orgs`. `domain` e.g. `github.com`; `slug` e.g. `github-username`; `title` e.g. "GitHub Username". Starts PRIVATE with an empty seed version. Skip if the dev already has a `providerId`.
|
|
197
|
-
2. **Persist the version:** `create_provider_version_from_capture(providerId, provider, initialUrl, notes)` — `notes` e.g. `"Initial draft from browser capture"`.
|
|
198
|
-
3.
|
|
212
|
+
2. **Persist the version:** `create_provider_version_from_capture(providerId, provider, initialUrl, notes)` — `notes` e.g. `"Initial draft from browser capture"`. The version lands on the `draft` branch.
|
|
213
|
+
3. **`requiredContext` is derived for you.** The publish tool scans the recipe's url/body for `{{context.<name>}}` placeholders (step 10) and emits a JSON Schema requiring each one — the Builder then validates every session-create's `context` against it (a clear 400 instead of a late attestor failure). Extra context fields are always allowed. The derived properties are plain strings; to refine types or add human-readable descriptions, call `update_provider_version` with a `requiredContext` override while the version is on `draft`, e.g.:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{ "requiredContext": { "type": "object", "required": ["username"], "properties": { "username": { "type": "string", "description": "GitHub username to verify" } } } }
|
|
217
|
+
```
|
|
218
|
+
4. **Go live:** PRIVATE → `publish_provider_version`; PUBLIC → `submit_provider_version_for_review(providerId, version)` and tell the dev it's awaiting admin review.
|
|
219
|
+
5. **Tell the dev how consumers call it:** sessions for this verification must include the context params, e.g. `context: { "username": "..." }` in `POST /verifications/sessions`. Quote the `requiredContext` returned on the created version — a session missing those fields gets a 400, and verification matches the supplied values exactly.
|
|
199
220
|
|
|
200
221
|
When done, tell the developer the version is live (PRIVATE) or submitted for review (PUBLIC), and that they can integrate using the `providerId`.
|
|
201
222
|
|
|
@@ -225,17 +246,17 @@ If `attach_chrome` errors, times out, or `waitForCdpReady` reports the port isn'
|
|
|
225
246
|
- **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.
|
|
226
247
|
- **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.
|
|
227
248
|
|
|
228
|
-
**Credential for signing the proof.** `run_proof` requires an `ownerAddress` — the
|
|
249
|
+
**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.
|
|
229
250
|
|
|
230
|
-
1. **Call `resolve_owner_key`.** It checks `RECLAIM_PRIVATE_KEY` (MCP env) → the project `.env` →
|
|
251
|
+
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.
|
|
231
252
|
- `{ ready: true, address }` → call `run_proof({ provider, ownerAddress: address, … })`. No prompts.
|
|
232
|
-
- `{ ready: false }` → no
|
|
233
|
-
- **Provide their own key** → `import_credentials({ privateKey: "0x…" })
|
|
253
|
+
- `{ ready: false }` → no key exists yet. Ask the dev once how to set one up:
|
|
254
|
+
- **Provide their own key** → `import_credentials({ privateKey: "0x…" })` (also accepts a `keyFile` path).
|
|
234
255
|
- **Create a new one** → `issue_credentials({})`. (Fine default for test proofs — owner identity doesn't matter.)
|
|
235
|
-
-
|
|
256
|
+
- Either way the key is written to the project `.env` (mode 0600; `.env` is added to `.gitignore`) — never to `~/.reclaim`.
|
|
236
257
|
- Then call `resolve_owner_key` again and `run_proof` with the returned address.
|
|
237
|
-
2. All fully local — no network, no server registration.
|
|
238
|
-
- **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
|
|
258
|
+
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.
|
|
259
|
+
- **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.
|
|
239
260
|
- **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.
|
|
240
261
|
|
|
241
262
|
If `replay_request` or `run_proof` errors with `unknown secretParamsRef`:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
This server targets the Reclaim BUILDER backend. It exposes three tool layers: generated API tools (one per builder operation, snake_case), credential/verification helpers, and browser agent tools (CDP capture, synthesis, replay, proof).
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
**Provider-authoring flow:** `attach_chrome` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`) → `propose_provider` → `replay_request` → `run_proof` → `create_provider_version_from_capture` (lands on the draft branch) → `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review` (PUBLIC).
|
|
6
|
+
|
|
7
|
+
**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.
|
|
8
|
+
|
|
9
|
+
**Verification sessions:** consumers authenticate with a per-org token (`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create sessions via `POST /verifications/sessions`. Results are JWE-encrypted (ECDH-ES + A256GCM over X25519) to the org's single encryption credential — set via `PUT /orgs/{orgId}/credential` with an X25519 public JWK — and delivered to the org's callback subscriptions (plaintext when no credential is set). Use the org/session tools (`get_verification_session`, `list_verification_events`, `list_org_verification_sessions`) to troubleshoot.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
This server is running against the LEGACY Reclaim devtools backend (temporary, while the new builder backend is in development), so its capabilities are intentionally limited.
|
|
2
|
+
|
|
3
|
+
**Provider-authoring flow:**
|
|
4
|
+
|
|
5
|
+
1. `reclaim_authenticate` — by default opens the dashboard in Chrome, waits for you to sign in, captures the token automatically, and closes the tab (or paste a `token` / pass an eth `ethAddress` to skip the browser). Do this once before publishing or listing.
|
|
6
|
+
2. `attach_chrome` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`).
|
|
7
|
+
3. `propose_provider` to draft a provider, `replay_request` to pre-flight, `run_proof` to verify with the attestor. OPRF/`hash` redactions are optional and supported.
|
|
8
|
+
4. `create_provider_version_from_capture` to publish. With NO `providerId` it REGISTERS A NEW provider (re-running creates a duplicate); pass a `providerId` from `get_me_providers` to add a new version to an existing provider instead.
|
|
9
|
+
5. `get_me_providers` lists the providers you own (the only provider-listing tool available in this mode).
|
|
10
|
+
|
|
11
|
+
Only GET/POST endpoints can be captured into providers here. There is no organization, credential-linking, or public-review workflow in this mode.
|
|
12
|
+
|
|
13
|
+
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) use the `{{context_<name>}}` form — this backend does not support the dotted `{{context.<name>}}` interpolation the new builder uses.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic CDP Network → CapturedRequest state machine. The agent
|
|
3
|
+
* (local CDP, page-level `Network.*` bindings) and the app (remote Popcorn,
|
|
4
|
+
* events read off a session-filtered `event` stream) share this entire engine;
|
|
5
|
+
* only how events are delivered and how a response body is fetched differ, so
|
|
6
|
+
* those two are injected. See `cdp/capture.ts` and the app's
|
|
7
|
+
* `capture/capture.ts` for the two thin transports that wire into it.
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from 'node:crypto'
|
|
10
|
+
import { extractGraphqlOp } from '../provider/graphql.ts'
|
|
11
|
+
import type { CapturedRequest } from '../provider/schema.ts'
|
|
12
|
+
import { BodyStore } from './eviction.ts'
|
|
13
|
+
|
|
14
|
+
/** The slice of CDP `Network.requestWillBeSent` the engine reads. Kept
|
|
15
|
+
* structural — not devtools-protocol's full event type — so a transport
|
|
16
|
+
* pinned to a different devtools-protocol version still satisfies it. */
|
|
17
|
+
export interface RequestWillBeSentLike {
|
|
18
|
+
requestId: string
|
|
19
|
+
request: {
|
|
20
|
+
method: string
|
|
21
|
+
url: string
|
|
22
|
+
headers: Record<string, string>
|
|
23
|
+
postData?: string
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The slice of CDP `Network.responseReceived` the engine reads. */
|
|
28
|
+
export interface ResponseReceivedLike {
|
|
29
|
+
requestId: string
|
|
30
|
+
response: {
|
|
31
|
+
status: number
|
|
32
|
+
mimeType: string
|
|
33
|
+
encodedDataLength: number
|
|
34
|
+
headers: Record<string, string>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Drop HTTP/2 pseudo-headers (`:authority`, `:method`, `:path`, `:scheme`,
|
|
39
|
+
* `:status`) from a CDP header map. Chrome emits them on HTTP/2 requests,
|
|
40
|
+
* but they're a transport-layer abstraction with no place in our domain:
|
|
41
|
+
* the URL already carries authority/path/scheme, the request object carries
|
|
42
|
+
* method, and Node's `fetch` (undici) throws on any header starting with
|
|
43
|
+
* `:`. Stripping at capture time keeps every downstream consumer
|
|
44
|
+
* (`get_request`, draft, replay, proof) clean by construction. */
|
|
45
|
+
export function stripPseudoHeaders(
|
|
46
|
+
headers: Record<string, string>,
|
|
47
|
+
): Record<string, string> {
|
|
48
|
+
const out: Record<string, string> = {}
|
|
49
|
+
for(const [k, v] of Object.entries(headers)) {
|
|
50
|
+
if(!k.startsWith(':')) {
|
|
51
|
+
out[k] = v
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CaptureSession {
|
|
59
|
+
captureId: string
|
|
60
|
+
requests: Map<string, CapturedRequest>
|
|
61
|
+
bodies: BodyStore
|
|
62
|
+
stop: () => Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Public knobs a transport accepts; mapped into `CaptureEngineOptions`. */
|
|
66
|
+
export interface StartCaptureOptions {
|
|
67
|
+
maxBodyBytes?: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CaptureEngineOptions {
|
|
71
|
+
maxBodyBytes?: number
|
|
72
|
+
/** Fetch a finished response body. Transport-specific: the agent calls
|
|
73
|
+
* `Network.getResponseBody` on the page binding, the app routes the
|
|
74
|
+
* command through its Popcorn session. */
|
|
75
|
+
fetchBody: (
|
|
76
|
+
requestId: string,
|
|
77
|
+
) => Promise<{ body: string, base64Encoded: boolean }>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface InFlight {
|
|
81
|
+
requestId: string
|
|
82
|
+
method: string
|
|
83
|
+
url: string
|
|
84
|
+
requestHeaders: Record<string, string>
|
|
85
|
+
requestBody?: string
|
|
86
|
+
startedAt: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The engine state plus the handlers a transport drives. A transport feeds
|
|
90
|
+
* raw CDP events into these and exposes `captureId`/`requests`/`bodies` to
|
|
91
|
+
* its caller. */
|
|
92
|
+
export interface CaptureEngine {
|
|
93
|
+
captureId: string
|
|
94
|
+
requests: Map<string, CapturedRequest>
|
|
95
|
+
bodies: BodyStore
|
|
96
|
+
onRequestWillBeSent: (p: RequestWillBeSentLike) => void
|
|
97
|
+
mergeRequestHeaders: (
|
|
98
|
+
requestId: string,
|
|
99
|
+
extra: Record<string, string>,
|
|
100
|
+
) => void
|
|
101
|
+
onResponseReceived: (p: ResponseReceivedLike) => void
|
|
102
|
+
mergeResponseHeaders: (
|
|
103
|
+
requestId: string,
|
|
104
|
+
extra: Record<string, string>,
|
|
105
|
+
) => void
|
|
106
|
+
onLoadingFinished: (requestId: string) => Promise<void>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createCaptureEngine(opts: CaptureEngineOptions): CaptureEngine {
|
|
110
|
+
const captureId = randomUUID()
|
|
111
|
+
const requests = new Map<string, CapturedRequest>()
|
|
112
|
+
const inFlight = new Map<string, InFlight>()
|
|
113
|
+
const bodies = new BodyStore({
|
|
114
|
+
maxBytes: opts.maxBodyBytes ?? 200 * 1024 * 1024,
|
|
115
|
+
})
|
|
116
|
+
// CDP doesn't guarantee `requestWillBeSent` arrives before
|
|
117
|
+
// `requestWillBeSentExtraInfo` (same for the response pair). When extra
|
|
118
|
+
// info lands first, buffer it here and drain on the matching primary
|
|
119
|
+
// event. Without this, headers (Cookie, Authorization, …) get silently
|
|
120
|
+
// dropped for any request whose events race in the unlucky order.
|
|
121
|
+
const pendingRequestExtra = new Map<string, Record<string, string>>()
|
|
122
|
+
const pendingResponseExtra = new Map<string, Record<string, string>>()
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
captureId,
|
|
126
|
+
requests,
|
|
127
|
+
bodies,
|
|
128
|
+
onRequestWillBeSent,
|
|
129
|
+
mergeRequestHeaders,
|
|
130
|
+
onResponseReceived,
|
|
131
|
+
mergeResponseHeaders,
|
|
132
|
+
onLoadingFinished,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Merge extra headers from the ...ExtraInfo events into either the
|
|
136
|
+
* in-flight entry or the finalized captured request, whichever has
|
|
137
|
+
* arrived. If neither exists yet (extra-info-first race), buffer the
|
|
138
|
+
* headers and drain on the matching primary event. */
|
|
139
|
+
function mergeRequestHeaders(
|
|
140
|
+
requestId: string,
|
|
141
|
+
extra: Record<string, string>,
|
|
142
|
+
) {
|
|
143
|
+
const clean = stripPseudoHeaders(extra)
|
|
144
|
+
const f = inFlight.get(requestId)
|
|
145
|
+
if(f) {
|
|
146
|
+
f.requestHeaders = { ...f.requestHeaders, ...clean }
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const r = requests.get(requestId)
|
|
151
|
+
if(r) {
|
|
152
|
+
r.requestHeaders = { ...r.requestHeaders, ...clean }
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const buffered = pendingRequestExtra.get(requestId) ?? {}
|
|
157
|
+
pendingRequestExtra.set(requestId, { ...buffered, ...clean })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function mergeResponseHeaders(
|
|
161
|
+
requestId: string,
|
|
162
|
+
extra: Record<string, string>,
|
|
163
|
+
) {
|
|
164
|
+
const clean = stripPseudoHeaders(extra)
|
|
165
|
+
const r = requests.get(requestId)
|
|
166
|
+
if(r) {
|
|
167
|
+
r.responseHeaders = { ...r.responseHeaders, ...clean }
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const buffered = pendingResponseExtra.get(requestId) ?? {}
|
|
172
|
+
pendingResponseExtra.set(requestId, { ...buffered, ...clean })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onRequestWillBeSent(p: RequestWillBeSentLike) {
|
|
176
|
+
const f: InFlight = {
|
|
177
|
+
requestId: p.requestId,
|
|
178
|
+
method: p.request.method,
|
|
179
|
+
url: p.request.url,
|
|
180
|
+
requestHeaders: stripPseudoHeaders(p.request.headers),
|
|
181
|
+
startedAt: Date.now(),
|
|
182
|
+
}
|
|
183
|
+
if(p.request.postData !== undefined) {
|
|
184
|
+
f.requestBody = p.request.postData
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Drain any extra-info that arrived before us.
|
|
188
|
+
const buffered = pendingRequestExtra.get(p.requestId)
|
|
189
|
+
if(buffered) {
|
|
190
|
+
f.requestHeaders = { ...f.requestHeaders, ...buffered }
|
|
191
|
+
pendingRequestExtra.delete(p.requestId)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
inFlight.set(p.requestId, f)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function onResponseReceived(p: ResponseReceivedLike) {
|
|
198
|
+
const f = inFlight.get(p.requestId)
|
|
199
|
+
if(!f) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const captured: CapturedRequest = {
|
|
204
|
+
requestId: p.requestId,
|
|
205
|
+
method: f.method,
|
|
206
|
+
url: f.url,
|
|
207
|
+
status: p.response.status,
|
|
208
|
+
contentType: p.response.mimeType,
|
|
209
|
+
size: p.response.encodedDataLength,
|
|
210
|
+
requestHeaders: f.requestHeaders,
|
|
211
|
+
responseHeaders: stripPseudoHeaders(p.response.headers),
|
|
212
|
+
finishedAt: 0,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Drain any response extra-info that arrived before us.
|
|
216
|
+
const bufferedResp = pendingResponseExtra.get(p.requestId)
|
|
217
|
+
if(bufferedResp) {
|
|
218
|
+
captured.responseHeaders = {
|
|
219
|
+
...captured.responseHeaders,
|
|
220
|
+
...bufferedResp,
|
|
221
|
+
}
|
|
222
|
+
pendingResponseExtra.delete(p.requestId)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if(f.requestBody !== undefined) {
|
|
226
|
+
captured.requestBody = f.requestBody
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const op = extractGraphqlOp(f.requestBody)
|
|
230
|
+
if(op !== undefined) {
|
|
231
|
+
captured.graphqlOp = op
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
requests.set(p.requestId, captured)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function onLoadingFinished(requestId: string) {
|
|
238
|
+
const r = requests.get(requestId)
|
|
239
|
+
if(!r) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const { body, base64Encoded } = await opts.fetchBody(requestId)
|
|
245
|
+
const text = base64Encoded
|
|
246
|
+
? Buffer.from(body, 'base64').toString('utf8')
|
|
247
|
+
: body
|
|
248
|
+
bodies.set(requestId, text)
|
|
249
|
+
r.responseBody = text
|
|
250
|
+
r.finishedAt = Date.now()
|
|
251
|
+
} catch{
|
|
252
|
+
// Body might be unavailable for
|
|
253
|
+
// some content-types (e.g. websocket). Skip.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|