@reclaimprotocol/agent 0.1.9 → 0.1.11

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @reclaimprotocol/agent
2
2
 
3
- Chrome-CDP + zkTLS provider-authoring tools. The agent captures network traffic from a browser, synthesizes Reclaim provider definitions from it (`contains` matchers + `jsonPath` / `xPath` / `regex` redactions), runs a local replay diagnostic, and produces a verified attestor proof via `@reclaimprotocol/attestor-core`.
3
+ 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.
4
+
5
+ Under the hood it's Chrome-CDP + zkTLS provider-authoring tooling: it captures network traffic from a browser, synthesizes provider definitions from it (`contains` matchers + `jsonPath` / `xPath` / `regex` redactions), runs a local replay diagnostic, and produces a verified attestor proof via `@reclaimprotocol/attestor-core`.
4
6
 
5
7
  This package **is** the Reclaim MCP server (bin: `reclaim-mcp-server`). It depends on `@reclaimprotocol/client` for the typed HTTP client and registers the provider-authoring tools below alongside the backend-facing tools (credentials, publishing) in a single MCP surface. A standalone entry (`src/index.ts`) re-exports the client SDK plus `buildAgentTools` for embedding.
6
8
 
@@ -18,122 +20,18 @@ npx -y --package=@reclaimprotocol/agent reclaim-mcp-server
18
20
 
19
21
  > The `--package=` (short form `-p`) flag selects the `reclaim-mcp-server` binary from the package. We use the long `--package=` form throughout because the short `-p` collides with the `-p`/`--print` flag of some agent CLIs (notably `claude mcp add`), which silently swallows the command. Prefer a global install? Run `npm i -g @reclaimprotocol/agent` and use `reclaim-mcp-server` directly as the `command` (drop the `npx` wrapper and its `args`).
20
22
 
21
- ### Claude Code
22
-
23
- **Recommended — install the plugin from the marketplace** (bundles this MCP server; no `claude mcp add` and no `-p` collision to worry about — the server's `how_it_works` tool serves the authoring guide on demand):
24
-
25
- ```
26
- /plugin marketplace add reclaimprotocol/builder
27
- /plugin install reclaim@reclaim
28
- ```
29
-
30
- This is the preferred path — prefer it over the manual `claude mcp add` below unless you have a specific reason not to (e.g. you're pinning a local build).
31
-
32
- **Advanced / fallback — add the MCP server directly** (skip if you used the plugin above):
33
-
34
- ```bash
35
- # project scope → writes .mcp.json; add `-s user` for a global install
36
- claude mcp add reclaim -- npx -y --package=@reclaimprotocol/agent reclaim-mcp-server
37
- ```
38
-
39
- …or add it to `.mcp.json` yourself:
40
-
41
- ```json
42
- {
43
- "mcpServers": {
44
- "reclaim": {
45
- "command": "npx",
46
- "args": ["-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"]
47
- }
48
- }
49
- }
50
- ```
51
-
52
- ### Claude Desktop (macOS / Windows app)
53
-
54
- Claude Desktop is the standalone consumer app — **separate from Claude Code**, and it does **not** support the plugin marketplace (`/plugin …` is Claude Code only). Configure the MCP server manually via its config file:
55
-
56
- - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
57
- - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
58
-
59
- Edit it (via **Settings → Developer → Edit Config**, or open the file directly) and add:
60
-
61
- ```json
62
- {
63
- "mcpServers": {
64
- "reclaim": {
65
- "command": "npx",
66
- "args": ["-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"]
67
- }
68
- }
69
- }
70
- ```
71
-
72
- Then **fully quit and reopen** Claude Desktop (a window close is not enough) so it re-reads the config and starts the server.
73
-
74
- > **Node must be on the app's PATH.** Claude Desktop launches the server as a subprocess using the environment it inherits at startup — it does **not** bundle Node. If the server shows as failed, either install Node ≥ 20 and relaunch, or set `"command"` to an **absolute path** to `npx`/`node` (find it with `which npx`), since GUI-launched apps sometimes get a minimal PATH. Chrome capture works the same as anywhere else — see [Chrome / CDP](#platform-notes).
75
-
76
- ### Cursor
23
+ ### Installing into your coding agent
77
24
 
78
- Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
25
+ Copy-paste setup for **Claude Code** (plugin marketplace), **Claude Desktop**, **Cursor**, **opencode**, **Codex**, **Windsurf**, **VS Code**, **Cline** and other MCP clients lives in the public **[reclaimprotocol/developer-tools](https://github.com/reclaimprotocol/developer-tools)** repo — that's where the Claude Code plugin/marketplace is published.
79
26
 
80
- ```json
81
- {
82
- "mcpServers": {
83
- "reclaim": {
84
- "command": "npx",
85
- "args": ["-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"]
86
- }
87
- }
88
- }
89
- ```
90
-
91
- ### opencode
92
-
93
- Add to `opencode.json` (project) or `~/.config/opencode/opencode.json` (global):
27
+ The quickest path, in Claude Code:
94
28
 
95
- ```json
96
- {
97
- "$schema": "https://opencode.ai/config.json",
98
- "mcp": {
99
- "reclaim": {
100
- "type": "local",
101
- "command": ["npx", "-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"],
102
- "enabled": true
103
- }
104
- }
105
- }
106
- ```
107
-
108
- ### Codex
109
-
110
- ```bash
111
- # writes ~/.codex/config.toml
112
- codex mcp add reclaim -- npx -y --package=@reclaimprotocol/agent reclaim-mcp-server
113
29
  ```
114
-
115
- …or add it to `~/.codex/config.toml` yourself (Codex uses TOML, not JSON):
116
-
117
- ```toml
118
- [mcp_servers.reclaim]
119
- command = "npx"
120
- args = ["-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"]
30
+ /plugin marketplace add reclaimprotocol/developer-tools
31
+ /plugin install reclaim@reclaim
121
32
  ```
122
33
 
123
- ### Antigravity, Windsurf, VS Code, Cline & other MCP clients
124
-
125
- These all use the standard `mcpServers` shape — add a server through the app's MCP settings (or its `mcp`/`mcpServers` config file) with:
126
-
127
- ```json
128
- {
129
- "mcpServers": {
130
- "reclaim": {
131
- "command": "npx",
132
- "args": ["-y", "--package=@reclaimprotocol/agent", "reclaim-mcp-server"]
133
- }
134
- }
135
- }
136
- ```
34
+ For any other client, point it at the stdio command above (`npx -y --package=@reclaimprotocol/agent reclaim-mcp-server`) — see the developer-tools README for per-client config and the Claude Desktop / GUI-app notes.
137
35
 
138
36
  ### Optional environment
139
37
 
@@ -135,6 +135,73 @@ export function authoringTools(resolveBackend) {
135
135
  ...(c.httpOnly ? { httpOnly: true } : {}),
136
136
  }));
137
137
  }),
138
+ defineTool({
139
+ name: 'wait_for_login',
140
+ description: 'Wait for the user to finish logging in. Polls the browser\'s '
141
+ + 'REAL cookies over CDP (httpOnly INCLUDED) until a NEW session '
142
+ + 'cookie appears, ignoring analytics/bot cookies. The ONLY '
143
+ + 'correct login wait — NEVER use wait_for_page for it: page JS '
144
+ + 'can\'t see the httpOnly session cookie (hangs the whole '
145
+ + 'timeout), yet DOES see pre-login tracking cookies (a '
146
+ + 'length/substring check fires instantly, before the user '
147
+ + 'types). Snapshots the baseline itself — call right after '
148
+ + 'navigating to the login page. Does NOT navigate (never yanks '
149
+ + 'the user off login/2FA). Returns { loggedIn, newCookies }.',
150
+ inputSchema: objectSchema({ timeoutMs: { type: 'number' } }),
151
+ }, async (args) => {
152
+ const backend = resolveBackend(args);
153
+ const deadline = Date.now() + (Number(args.timeoutMs) || 180000);
154
+ const cookies = async () => {
155
+ const res = (await backend.sendCdp('Network.getCookies'));
156
+ return res.cookies ?? [];
157
+ };
158
+ // Analytics / bot-protection / pre-auth infra cookies appear (and
159
+ // rotate) without any login — never the signal, even when new.
160
+ const JUNK = new RegExp('^(' + [
161
+ '_ga', '_gid', '_gat', '_ym', '__ddg', '__gads', '__gpi', '__eoi',
162
+ '__utm', '_fbp', 'fbp', 'fr', 'FCCDCF', 'pinLogger',
163
+ 'userFirstVisit', 'cf_', '_dd_', 'bcookie', 'bscookie', 'lidc',
164
+ ].join('|') + ')', 'i');
165
+ // A real session/auth cookie name. Deliberately NOT bare `token` — a
166
+ // challenge/CSRF token (e.g. LinkedIn `chp_token`) contains it and is
167
+ // set BEFORE auth completes. Any new httpOnly cookie is also a
168
+ // candidate (server-set session cookies are usually httpOnly).
169
+ const SESSION = /sess|sid|jwt|_session|auth|logged.?in|remember|li_at/i;
170
+ // Auth/challenge URL paths — while the page is still on one, login is
171
+ // NOT done (a mid-flow cookie must not count). Cross-site-stable, far
172
+ // more so than cookie names.
173
+ const AUTH_URL = new RegExp([
174
+ 'login', 'signin', 'sign-in', 'challenge', 'checkpoint', '\\buas\\b',
175
+ 'authorize', '\\bsso\\b', 'mfa', '2fa', 'two-factor', 'otp', 'verify',
176
+ 'password',
177
+ ].join('|'), 'i');
178
+ const offAuthUrl = async () => {
179
+ const { value } = await evalInPage(backend, 'location.href');
180
+ const href = typeof value === 'string' ? value : '';
181
+ try {
182
+ const u = new URL(href);
183
+ return !AUTH_URL.test(u.pathname + u.search);
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ };
189
+ const initial = await cookies();
190
+ const baseline = new Set(initial.map((c) => c.name));
191
+ while (Date.now() < deadline) {
192
+ const current = await cookies();
193
+ const fresh = current
194
+ .filter((c) => !baseline.has(c.name) && !JUNK.test(c.name));
195
+ const hit = fresh.filter((c) => c.httpOnly || SESSION.test(c.name));
196
+ // BOTH required: a new session cookie AND the page has left the auth
197
+ // flow. Either alone false-positives (mid-flow token; stray nav).
198
+ if (hit.length && await offAuthUrl()) {
199
+ return { loggedIn: true, newCookies: hit.map((c) => c.name) };
200
+ }
201
+ await sleep(1000);
202
+ }
203
+ return { loggedIn: false, newCookies: [] };
204
+ }),
138
205
  defineTool({
139
206
  name: 'list_requests',
140
207
  description: 'List captured network requests (method/url/status/size), newest '
@@ -14,38 +14,63 @@ export function findTool(resolveBackend) {
14
14
  description: 'Search captured responses for any encoding of `value` (raw, '
15
15
  + 'URL/JSON/HTML-escaped, …). Returns a ranked candidate list with '
16
16
  + 'the matched variant, score, and a short snippet — used to locate '
17
- + 'the request that returns a target. Server-side; bodies stay here.',
17
+ + 'the request that returns a target. Also finds SPLIT matches: when '
18
+ + '`value` is multi-word (e.g. a full name), a response that carries '
19
+ + 'every word SEPARATELY (a JSON profile API with first/last name in '
20
+ + 'distinct fields) is returned with `splitMatch:true` — prove those '
21
+ + 'per-field (a redaction per part) rather than as one string. '
22
+ + 'Server-side; bodies stay here.',
18
23
  inputSchema: objectSchema({ value: { type: 'string' }, limit: { type: 'number' } }, ['value']),
19
24
  }, async (args) => {
20
25
  const backend = resolveBackend(args);
21
- const variants = normalizeCandidates(String(args.value));
26
+ const value = String(args.value);
27
+ const variants = normalizeCandidates(value);
28
+ // Name-part tokens (≥3 chars) for split-field detection — a JSON API
29
+ // often carries a full name as separate first/last fields, so the whole
30
+ // string never matches even though the value IS there.
31
+ const tokens = value.split(/\s+/).filter((t) => t.length >= 3);
32
+ const snip = (body, idx, len) => {
33
+ const s = Math.max(0, idx - 40);
34
+ return body.slice(s, Math.min(body.length, idx + len + 40));
35
+ };
22
36
  const ranked = [];
23
37
  for (const r of backend.capture.requests.values()) {
24
38
  if (!r.responseBody) {
25
39
  continue;
26
40
  }
41
+ const body = r.responseBody;
42
+ // Prefer JSON responses + smaller bodies (likely API endpoints).
43
+ const base = (r.contentType.includes('json') ? 10 : 0)
44
+ + Math.max(0, 5 - Math.log10(Math.max(r.size, 1)));
27
45
  let hit;
28
46
  for (const v of variants) {
29
- const idx = r.responseBody.indexOf(v);
47
+ const idx = body.indexOf(v);
30
48
  if (idx >= 0) {
31
- const start = Math.max(0, idx - 40);
32
- const end = Math.min(r.responseBody.length, idx + v.length + 40);
33
- hit = { snippet: r.responseBody.slice(start, end), variant: v };
49
+ hit = { snippet: snip(body, idx, v.length), variant: v };
34
50
  break;
35
51
  }
36
52
  }
37
- if (!hit) {
53
+ if (hit) {
54
+ // Whole-value match — ranked above split matches (+5).
55
+ ranked.push({
56
+ ...publicRequest(r),
57
+ matchedVariant: hit.variant,
58
+ snippet: hit.snippet,
59
+ score: base + 5,
60
+ });
38
61
  continue;
39
62
  }
40
- // Prefer JSON responses + smaller bodies (likely API endpoints).
41
- const score = (r.contentType.includes('json') ? 10 : 0)
42
- + Math.max(0, 5 - Math.log10(Math.max(r.size, 1)));
43
- ranked.push({
44
- ...publicRequest(r),
45
- matchedVariant: hit.variant,
46
- snippet: hit.snippet,
47
- score,
48
- });
63
+ // Split match: every word present separately (e.g. first/last name
64
+ // in distinct JSON fields). Requires ≥2 tokens so it's specific.
65
+ if (tokens.length >= 2 && tokens.every((t) => body.includes(t))) {
66
+ ranked.push({
67
+ ...publicRequest(r),
68
+ matchedVariant: `split:${tokens.join('+')}`,
69
+ splitMatch: true,
70
+ snippet: snip(body, body.indexOf(tokens[0]), tokens[0].length),
71
+ score: base,
72
+ });
73
+ }
49
74
  }
50
75
  ranked.sort((a, b) => b.score - a.score);
51
76
  const limit = Number(args.limit) || 10;
@@ -71,11 +71,11 @@ export function proposeTool(resolveBackend) {
71
71
  + '`targets` array');
72
72
  }
73
73
  for (const t of targets) {
74
- if ((t.hash ?? defaultHash) !== undefined
75
- && t.value.length > OPRF_MAX_BYTES) {
74
+ const bytes = Buffer.byteLength(t.value, 'utf8');
75
+ if ((t.hash ?? defaultHash) !== undefined && bytes > OPRF_MAX_BYTES) {
76
76
  throw new Error(`OPRF caps hashed values at ${OPRF_MAX_BYTES} bytes; got `
77
- + `${t.value.length}-byte "${t.value}". Use a tighter `
78
- + 'target or drop `hash`.');
77
+ + `${bytes}-byte "${t.value}". Use a tighter target or drop `
78
+ + '`hash`.');
79
79
  }
80
80
  }
81
81
  const captured = backend.capture.requests.get(requestId);
@@ -99,11 +99,6 @@ export declare class ReclaimOldClient {
99
99
  registerProvider(body: RegisterProviderBody): Promise<unknown>;
100
100
  updateProviderConfig(providerId: string, body: UpdateProviderConfigBody): Promise<unknown>;
101
101
  listVersions(providerId: string): Promise<unknown>;
102
- /** Full `providerConfig` (loginUrl, customInjection, requestData, ...) for
103
- * one version — the latest when `version` is omitted. Readable for ANY
104
- * non-archived provider (not owner-restricted), same as the dashboard's
105
- * edit-form prefill fetch. */
106
- getProviderConfig(providerId: string, version?: string): Promise<unknown>;
107
102
  /** Partial update on the PROVIDER document itself — name/description/
108
103
  * providerType/tags/isActive, after creation. The backend 403s if you
109
104
  * don't own the provider (and aren't admin); other providerConfig-style
package/lib/old/client.js CHANGED
@@ -60,16 +60,6 @@ export class ReclaimOldClient {
60
60
  listVersions(providerId) {
61
61
  return __classPrivateFieldGet(this, _ReclaimOldClient_instances, "m", _ReclaimOldClient_request).call(this, 'GET', `/providers/${encodeURIComponent(providerId)}/versions`, { auth: true });
62
62
  }
63
- /** Full `providerConfig` (loginUrl, customInjection, requestData, ...) for
64
- * one version — the latest when `version` is omitted. Readable for ANY
65
- * non-archived provider (not owner-restricted), same as the dashboard's
66
- * edit-form prefill fetch. */
67
- getProviderConfig(providerId, version) {
68
- return __classPrivateFieldGet(this, _ReclaimOldClient_instances, "m", _ReclaimOldClient_request).call(this, 'GET', `/providers/${encodeURIComponent(providerId)}/config`, {
69
- auth: true,
70
- query: version ? { versionNumber: version } : undefined,
71
- });
72
- }
73
63
  /** Partial update on the PROVIDER document itself — name/description/
74
64
  * providerType/tags/isActive, after creation. The backend 403s if you
75
65
  * don't own the provider (and aren't admin); other providerConfig-style
@@ -83,6 +83,8 @@ interface OldRequestData {
83
83
  export interface OldInjectedRequestData extends OldRequestData {
84
84
  required?: boolean;
85
85
  multiple?: boolean;
86
+ templateParams?: string[];
87
+ templateParamsMode?: 'separate' | 'merge';
86
88
  }
87
89
  /** Translate fresh `allowedInjectedRequestData` drafts (the author's input
88
90
  * shape, `AllowedJsRequest[]` — same shape the builder translator uses for
@@ -89,11 +89,15 @@ function requestDataFrom(providers) {
89
89
  * this shape and should be passed straight through instead — don't run it
90
90
  * through this a second time. */
91
91
  export function injectedRequestDataFrom(requests) {
92
- return requests.map((req) => ({
93
- ...requestDataFromProvider(req),
94
- required: req.required ?? true,
95
- multiple: req.multiple ?? true,
96
- }));
92
+ return requests.map((req) => {
93
+ return {
94
+ ...requestDataFromProvider(req),
95
+ required: req.required ?? true,
96
+ multiple: req.multiple ?? true,
97
+ templateParams: req.templateParams,
98
+ templateParamsMode: req.templateParamsMode,
99
+ };
100
+ });
97
101
  }
98
102
  /** Body for `POST /api/providers/register` — new provider + v1.0.0. Every
99
103
  * drafted request becomes a `requestData` entry; provider-level fields (name)
@@ -1,6 +1,5 @@
1
1
  import { authenticateTool } from "./authenticate.js";
2
2
  import { getProviderInfoTool } from "./get-provider-info.js";
3
- import { getProviderVersionConfigTool } from "./get-version-config.js";
4
3
  import { getMeProvidersTool } from "./me-providers.js";
5
4
  import { createVersionFromCaptureTool } from "./publish.js";
6
5
  import { sessionAnalyticsLogsTool } from "./session-analytics-logs.js";
@@ -20,7 +19,6 @@ export function buildOldBackendTools(client, sdkClient) {
20
19
  authenticateTool(client),
21
20
  createVersionFromCaptureTool(client),
22
21
  getMeProvidersTool(client),
23
- getProviderVersionConfigTool(client),
24
22
  getProviderInfoTool(sdkClient),
25
23
  updateProviderMetadataTool(client),
26
24
  sessionAnalyticsLogsTool(client),
@@ -1,14 +1,17 @@
1
1
  import { type RegisteredTool } from '../../mcp/server.ts';
2
2
  import type { ReclaimSdkClient } from '../sdk-client.ts';
3
3
  /**
4
- * Read provider info from the PUBLIC production SDK backend
5
- * (`api.reclaimprotocol.org`) — the service the verification SDK/attestor
6
- * itself calls at runtime, NOT the old-devtools dashboard backend
7
- * (`get_provider_version_config`, `devapi.reclaimprotocol.org`, requires
8
- * auth). Both underlying reads are unauthenticated.
4
+ * The single provider-read tool for old-devtools mode. Reads from the PUBLIC
5
+ * production SDK backend (`api.reclaimprotocol.org`) — the service the
6
+ * verification SDK/attestor itself calls at runtime. No auth required, works
7
+ * for ANY provider.
9
8
  *
10
- * Fetches BOTH shapes in one call, since they're complementary:
11
- * - `provider`: full provider + providerConfig, `customInjection` included.
9
+ * Fetches BOTH shapes in one call, since they're complementary and together
10
+ * cover everything a caller needs before re-publishing a new version:
11
+ * - `provider`: the full provider + providerConfig — `customInjection`,
12
+ * `userAgent`, `allowedInjectedRequestData`, `requestData`, everything.
13
+ * This is what you read before create_provider_version_from_capture so
14
+ * you can pass the whole config back (that tool carries nothing forward).
12
15
  * - `configs`: the trimmed runtime recipe the attestor actually consumes —
13
16
  * `customInjection` deliberately omitted, `isScriptRequestingClaim`
14
17
  * computed instead.
@@ -13,14 +13,17 @@ async function attempt(label, call) {
13
13
  }
14
14
  }
15
15
  /**
16
- * Read provider info from the PUBLIC production SDK backend
17
- * (`api.reclaimprotocol.org`) — the service the verification SDK/attestor
18
- * itself calls at runtime, NOT the old-devtools dashboard backend
19
- * (`get_provider_version_config`, `devapi.reclaimprotocol.org`, requires
20
- * auth). Both underlying reads are unauthenticated.
16
+ * The single provider-read tool for old-devtools mode. Reads from the PUBLIC
17
+ * production SDK backend (`api.reclaimprotocol.org`) — the service the
18
+ * verification SDK/attestor itself calls at runtime. No auth required, works
19
+ * for ANY provider.
21
20
  *
22
- * Fetches BOTH shapes in one call, since they're complementary:
23
- * - `provider`: full provider + providerConfig, `customInjection` included.
21
+ * Fetches BOTH shapes in one call, since they're complementary and together
22
+ * cover everything a caller needs before re-publishing a new version:
23
+ * - `provider`: the full provider + providerConfig — `customInjection`,
24
+ * `userAgent`, `allowedInjectedRequestData`, `requestData`, everything.
25
+ * This is what you read before create_provider_version_from_capture so
26
+ * you can pass the whole config back (that tool carries nothing forward).
24
27
  * - `configs`: the trimmed runtime recipe the attestor actually consumes —
25
28
  * `customInjection` deliberately omitted, `isScriptRequestingClaim`
26
29
  * computed instead.
@@ -31,15 +34,18 @@ export function getProviderInfoTool(client) {
31
34
  description: 'Read a PUBLISHED provider from the public production SDK backend ' +
32
35
  '(api.reclaimprotocol.org — what the verification SDK/attestor ' +
33
36
  'itself fetches at runtime), for ANY provider, no auth required. ' +
34
- 'Different from get_provider_version_config, which reads the old ' +
35
- 'devtools DASHBOARD backend and requires auth. \n\n' +
37
+ 'This is the provider-read tool for old-devtools mode. \n\n' +
36
38
  'Returns two complementary reads for the given version (latest ' +
37
- 'when `versionNumber` is omitted): `provider` (the full provider ' +
38
- '+ providerConfig, including `customInjection`) and `configs` (the ' +
39
- 'trimmed recipe the attestor actually consumes — `customInjection` ' +
40
- 'is deliberately OMITTED there, replaced by a computed ' +
41
- '`isScriptRequestingClaim` flag). One endpoint failing (e.g. an ' +
42
- 'invalid `versionNumber`) is reported under `errors` without ' +
39
+ 'when `versionNumber` is omitted): `provider` (the FULL provider ' +
40
+ '+ providerConfig — `customInjection`, `userAgent`, ' +
41
+ '`allowedInjectedRequestData`, `requestData`, everything) and ' +
42
+ '`configs` (the trimmed recipe the attestor actually consumes — ' +
43
+ '`customInjection` is deliberately OMITTED there, replaced by a ' +
44
+ 'computed `isScriptRequestingClaim` flag). Read `provider` here ' +
45
+ 'BEFORE create_provider_version_from_capture so you can pass the ' +
46
+ 'whole existing config back — that tool always creates a new ' +
47
+ 'version and carries nothing forward. One endpoint failing (e.g. ' +
48
+ 'an invalid `versionNumber`) is reported under `errors` without ' +
43
49
  'hiding a result the other endpoint did return.',
44
50
  inputSchema: {
45
51
  type: 'object',