@reclaimprotocol/agent 0.1.14 → 0.1.17

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.
Files changed (53) hide show
  1. package/lib/authoring/index.d.ts +1 -1
  2. package/lib/authoring/index.js +1 -1
  3. package/lib/authoring/util.js +3 -0
  4. package/lib/config/store.d.ts +7 -0
  5. package/lib/consts.d.ts +8 -0
  6. package/lib/consts.js +20 -0
  7. package/lib/index.js +1 -1
  8. package/lib/mcp/api/operations.gen.d.ts +8 -16
  9. package/lib/mcp/api/operations.gen.js +428 -479
  10. package/lib/mcp/remap_errors.js +7 -14
  11. package/lib/mcp/start.js +10 -3
  12. package/lib/mcp/tools/agent/proof.js +3 -3
  13. package/lib/mcp/tools/authenticate/authenticate.js +178 -93
  14. package/lib/mcp/tools/authenticate/eth-key.js +2 -3
  15. package/lib/mcp/tools/index.d.ts +1 -0
  16. package/lib/mcp/tools/index.js +1 -0
  17. package/lib/mcp/tools/mode/index.d.ts +1 -0
  18. package/lib/mcp/tools/mode/index.js +3 -0
  19. package/lib/mcp/tools/mode/mode.d.ts +16 -0
  20. package/lib/mcp/tools/mode/mode.js +49 -0
  21. package/lib/old/tools/publish.js +75 -4
  22. package/lib/provider/to-version.js +4 -0
  23. package/lib/tools/registry.d.ts +6 -13
  24. package/lib/tools/registry.js +29 -0
  25. package/package.json +5 -8
  26. package/scripts/prepend-shebang.js +1 -1
  27. package/skill/injection-api.md +98 -7
  28. package/src/authoring/index.ts +1 -0
  29. package/src/authoring/util.ts +3 -0
  30. package/src/cdp/launcher.ts +1 -1
  31. package/src/config/store.ts +8 -0
  32. package/src/consts.ts +24 -0
  33. package/src/index.ts +1 -1
  34. package/src/mcp/api/operations.gen.ts +430 -487
  35. package/src/mcp/remap_errors.ts +8 -15
  36. package/src/mcp/start.ts +11 -2
  37. package/src/mcp/tools/agent/proof.ts +3 -3
  38. package/src/mcp/tools/authenticate/authenticate.ts +238 -108
  39. package/src/mcp/tools/authenticate/eth-key.ts +7 -5
  40. package/src/mcp/tools/index.ts +1 -0
  41. package/src/mcp/tools/mode/index.ts +3 -0
  42. package/src/mcp/tools/mode/mode.ts +58 -0
  43. package/src/old/tools/publish.ts +78 -4
  44. package/src/proof/debug-response-dump.ts +1 -1
  45. package/src/proof/proof-core.ts +1 -1
  46. package/src/proof/zk-files.ts +2 -2
  47. package/src/provider/dom-xpath.ts +1 -1
  48. package/src/provider/draft.ts +3 -3
  49. package/src/provider/to-version.ts +4 -0
  50. package/src/tools/registry.ts +19 -0
  51. package/src/cli/args.ts +0 -20
  52. package/src/cli/commands.ts +0 -27
  53. package/src/cli.ts +0 -7
@@ -1,3 +1,17 @@
1
+ /**
2
+ * The ONE tool primitive for the agent. A tool is authored once with
3
+ * {@link defineTool} — `{ definition, handler }`, where the handler receives
4
+ * args already parsed + validated against the definition's JSON Schema (via
5
+ * zod), so the schema shown to the model and the validation applied to inbound
6
+ * args cannot drift.
7
+ *
8
+ * This lives outside `mcp/` on purpose: it depends only on zod (NOT the MCP
9
+ * SDK), so non-MCP consumers — the cloud authoring loop — can author and run
10
+ * these tools without pulling in `@modelcontextprotocol/sdk`. The MCP-specific
11
+ * glue (`registerToolList` onto an `McpServer`) stays in `mcp/server.ts`, which
12
+ * re-exports this primitive for the MCP-side tool files.
13
+ */
14
+ import { isBoom } from '@hapi/boom';
1
15
  import { toZod } from "../mcp/api/to-zod.js";
2
16
  /**
3
17
  * Define a tool with a single inputSchema (JSON Schema) as the source of truth.
@@ -15,6 +29,18 @@ export function defineTool(definition, handler) {
15
29
  },
16
30
  };
17
31
  }
32
+ /**
33
+ * Render a tool-handler error as the text every host shows the LLM.
34
+ * Boom errors carrying an RFC 9457 problem include its `detail`; anything
35
+ * else falls back to the plain message.
36
+ */
37
+ export function errorAsToolText(err) {
38
+ if (isBoom(err) && err.output?.statusCode) {
39
+ const detail = err.data?.detail;
40
+ return detail ? `${err.message}: ${detail}` : err.message;
41
+ }
42
+ return err instanceof Error ? err.message : String(err);
43
+ }
18
44
  /**
19
45
  * Adapt a tool to a host by WIDENING its input schema — add properties, mark
20
46
  * some required, optionally override the description — without touching the
@@ -30,6 +56,9 @@ export function extendTool(tool, extra) {
30
56
  ...(base.inputSchema.required ?? []),
31
57
  ...(extra.required ?? []),
32
58
  ];
59
+ // The merged `properties` spread carries the base's untyped property values,
60
+ // which don't structurally match ObjectSchema's strict per-property schema
61
+ // type — assert the merged shape.
33
62
  const inputSchema = {
34
63
  ...base.inputSchema,
35
64
  properties: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.14",
4
+ "version": "0.1.17",
5
5
  "description": "Reclaim Protocol's tools for agents — Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
@@ -33,7 +33,6 @@
33
33
  }
34
34
  },
35
35
  "bin": {
36
- "reclaim": "./lib/cli.js",
37
36
  "reclaim-mcp-server": "./lib/mcp-bin.js"
38
37
  },
39
38
  "files": [
@@ -45,7 +44,7 @@
45
44
  "README.md"
46
45
  ],
47
46
  "engines": {
48
- "node": ">=20"
47
+ "node": ">=22.0.0"
49
48
  },
50
49
  "repository": {
51
50
  "url": "https://github.com/reclaimprotocol/builder",
@@ -53,12 +52,11 @@
53
52
  },
54
53
  "websiteUrl": "https://docs.reclaimprotocol.org",
55
54
  "scripts": {
56
- "build": "tsc && node scripts/prepend-shebang.js && chmod 755 lib/cli.js lib/mcp-bin.js",
55
+ "build": "tsc && node scripts/prepend-shebang.js && chmod 755 lib/mcp-bin.js",
57
56
  "prepublishOnly": "npm run build",
58
57
  "dev": "tsc --watch",
59
- "test": "node --env-file=.test.env --test tests/*.test.ts",
58
+ "test": "node --env-file=.test.env --test-timeout=120000 --test tests/*.test.ts",
60
59
  "check": "tsc -p tsconfig.test.json",
61
- "start:cli": "node lib/cli.js",
62
60
  "start:mcp": "node lib/mcp-bin.js",
63
61
  "inspect": "npx @modelcontextprotocol/inspector node lib/mcp-bin.js",
64
62
  "download:zk-files": "node scripts/download-zk-files.js"
@@ -67,7 +65,6 @@
67
65
  "mcp",
68
66
  "model-context-protocol",
69
67
  "sdk",
70
- "cli",
71
68
  "ai",
72
69
  "agent",
73
70
  "chrome-devtools-protocol",
@@ -86,7 +83,7 @@
86
83
  "@modelcontextprotocol/sdk": "^1.29.0",
87
84
  "@noble/hashes": "^2.2.0",
88
85
  "@reclaimprotocol/attestor-core": "^5.0.8",
89
- "@reclaimprotocol/client": "0.1.14",
86
+ "@reclaimprotocol/client": "0.1.17",
90
87
  "@reclaimprotocol/tls": "^0.1.3",
91
88
  "chrome-remote-interface": "^0.33.0",
92
89
  "domhandler": "^6.0.0",
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
4
4
 
5
5
  const SHEBANG = '#!/usr/bin/env node\n'
6
6
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
7
- const BIN_FILES = ['cli.js', 'mcp-bin.js']
7
+ const BIN_FILES = ['mcp-bin.js']
8
8
 
9
9
  for(const file of BIN_FILES) {
10
10
  const path = join(ROOT, 'lib', file)
@@ -133,13 +133,104 @@ field isn't touched by `create_provider_version_from_capture` at all).
133
133
  ## `bodySniff` — unrelated to the above
134
134
 
135
135
  A per-request field (sits next to `url`/`method` on each `requestData` entry,
136
- not a version-level setting): `{ enabled: boolean, template: string }`. Per
137
- the InApp SDK's own type comment, it's "the template used for matching and
138
- extracting params from `requestBody`" — the REQUEST-body counterpart of
139
- `responseMatches`/`responseRedactions`, which only ever look at the response.
140
- It applies regardless of `injectionType`/`interceptorType`. Both publish
141
- tools in this package already set it for you from `provider.body` — you
142
- should not need to touch it directly.
136
+ not a version-level setting): `{ enabled: boolean, template: string }`.
137
+
138
+ **`bodySniff.enabled` should be `true` whenever the request HAS a body at
139
+ all** — this isn't a special case reserved for dynamic bodies. Only skip it
140
+ (leave `enabled: false` / template empty) when the request genuinely sends
141
+ no body (typically a GET). Both publish tools in this package already set
142
+ `enabled`/`template` for you from `provider.body` for a normal
143
+ capture-and-replay provider — you don't need to touch it directly there.
144
+ Read the rest of this section if you're authoring a claim via live
145
+ interception (`window.Reclaim.requestClaim`) whose body isn't simply the
146
+ one literal string you captured once — most commonly because some SMALL
147
+ part of it is dynamic per user/run (e.g. a user id, not necessarily the
148
+ whole payload).
149
+
150
+ **What actually gets hashed.** Confirmed directly from attestor-core
151
+ (`hashProviderParams` in `@reclaimprotocol/attestor-core`): the claim/spec
152
+ hash is `keccak256(canonicalStringify({ url, method, body, responseMatches,
153
+ responseRedactions }))`, where `body` is whatever plain string sits in
154
+ `params.body` — attestor-core does **no** template resolution, substitution,
155
+ or `{{}}`-stripping on it; it hashes exactly the string it's given, verbatim.
156
+ `bodySniff` as a concept (the `{enabled, template}` wrapper) doesn't exist
157
+ inside attestor-core at all — it's translated to a plain `body: string`
158
+ one layer up (old-devtools' `providerUtils.ts`: `body: bodySniff.enabled ?
159
+ bodySniff.template : ''`), and THAT literal string — including any
160
+ unresolved `{{paramName}}` text still in it — is what reaches
161
+ `hashProviderParams`.
162
+
163
+ **The consequence — and why this differs from `templateParams`.** A live
164
+ claim's own `body` field (submitted via `requestClaim`) can ALSO be set to
165
+ a literal template string containing `{{paramName}}`, rather than the real
166
+ substituted content, with the real value supplied separately as a witness
167
+ param. Since attestor-core hashes `body` as an opaque string with no
168
+ resolution, a claim whose `body` is the literal template `T` and a spec
169
+ whose `body`/`bodySniff.template` is that SAME literal template `T` hash
170
+ identically — regardless of what real value the `{{paramName}}` hole stood
171
+ for. This is the OPPOSITE of `templateParams`/`${var}` (used in
172
+ `responseMatches`/`responseRedactions`), which — per the JS SDK's
173
+ `generateSpecsFromRequestSpecTemplate` — genuinely gets substituted with a
174
+ real value at verify time before hashing. Don't conflate the two: `${var}`
175
+ in a match/redaction template IS resolved before hashing; `{{var}}` in a
176
+ body template is NEVER resolved before hashing, on either side.
177
+
178
+ **Practical use**: check exactly which part of the body actually changes
179
+ across users/runs — usually it's one small field (an id, a token, a short
180
+ list), not the whole payload. Template ONLY that minimal changing part;
181
+ leave everything else as fixed literal text. For a body like
182
+ `{"request":1,"type":"user_info","id":"12345"}` where only `id` varies, the
183
+ template is `{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}`
184
+ — NOT e.g. `{{whole_body}}` wrapping the entire object, which would throw
185
+ away the ability to structurally pin down everything else about the
186
+ request. Then:
187
+ 1. In the claim you build for `requestClaim`, set `bodySniff: { enabled:
188
+ true, template: '{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}' }`
189
+ and add a matching witness/extracted param
190
+ `REQ_BODY_USER_ID: theRealUserId` so the real value is still
191
+ independently verifiable, just not part of the hash.
192
+ 2. In the corresponding `allowedInjectedRequestData` entry, set `body` to
193
+ that EXACT SAME literal template string (byte-for-byte, same
194
+ `{{REQ_BODY_USER_ID}}` placeholder, same everything else) — NOT a
195
+ `${var}` substitution, since `body` doesn't go through that substitution
196
+ step.
197
+ 3. Before publishing, sanity-check by hand that
198
+ `template.replace('{{REQ_BODY_USER_ID}}', 'a real sample id')`
199
+ reproduces the real captured/expected request body byte-for-byte — if it
200
+ doesn't (extra whitespace, different key order, a field that turned out
201
+ to ALSO vary but wasn't templated, etc.), the hashes won't match either,
202
+ since the SPEC side uses the literal template as-is and the real claim's
203
+ body must independently be built to match that same shape when the
204
+ placeholder(s) are filled in. If MULTIPLE fields vary independently,
205
+ template each one with its own `{{placeholder}}` and its own witness
206
+ param — you're not limited to one hole per template.
207
+
208
+ **Fallback — whole-body placeholder.** If precise per-field templating
209
+ keeps causing hash mismatches (e.g. more fields turn out to vary than
210
+ expected, key order isn't stable, whitespace differs run-to-run) and you
211
+ can't pin down every variable field individually, fall back to templating
212
+ the ENTIRE body as one placeholder instead: `{{REQ_BODY_GRD}}`. The `GRD`
213
+ suffix matters — per the response-matching template engine
214
+ (`convertTemplateToRegex`), a placeholder name ending in `GRD` is treated as
215
+ a GREEDY match (`(.*)`, matching everything including embedded delimiters)
216
+ rather than the default non-greedy `(.*?)` (which stops at the first
217
+ occurrence of whatever comes after it in the template) — for a placeholder
218
+ covering the entire remaining body with nothing fixed after it, greedy is
219
+ what you want. This is strictly a fallback: it means NOTHING about the
220
+ body's structure is pinned down by the hash anymore (any body at all would
221
+ hash the same), so prefer the minimal-placeholder approach above whenever
222
+ you can actually identify which fields vary.
223
+
224
+ Caveat: I confirmed `hashProviderParams` treats `body` as an opaque string
225
+ with no `{{}}` resolution directly from the attestor-core bundle shipped in
226
+ this repo's `node_modules`. I did NOT find, in this same bundle, the code
227
+ path that actually invokes `hashProviderParams` against a live submitted
228
+ claim to gate proof acceptance — that caller lives elsewhere (the mobile/
229
+ InApp SDK client and/or a server-side verifier not checked into this repo).
230
+ So this is strong but not 100% end-to-end-traced confirmation; treat it as
231
+ "very likely correct, empirically consistent with real proof/verifyProof
232
+ behavior observed in practice" rather than "certain from first-hand
233
+ tracing of the whole call chain."
143
234
 
144
235
  ## Recognizing this on a provider you didn't author
145
236
 
@@ -14,6 +14,7 @@ export { authoringTools } from './tools.ts'
14
14
  // subsystem.
15
15
  export {
16
16
  defineTool,
17
+ errorAsToolText,
17
18
  extendTool,
18
19
  type RegisteredTool,
19
20
  type ToolDefinition,
@@ -21,6 +21,9 @@ export function objectSchema(
21
21
  properties: Record<string, unknown>,
22
22
  required?: string[],
23
23
  ): JSONSchema.ObjectSchema {
24
+ // `properties` is an untyped Record here (this is a literal shortener, not a
25
+ // schema validator), so it doesn't structurally match ObjectSchema's strict
26
+ // per-property schema type — assert the shape.
24
27
  return {
25
28
  type: 'object',
26
29
  properties,
@@ -113,7 +113,7 @@ export function launchChrome(opts: LaunchOptions): ChildProcess {
113
113
  export async function waitForCdpReady(
114
114
  port: number,
115
115
  timeoutMs = 10_000,
116
- ): Promise<void> {
116
+ ) {
117
117
  const deadline = Date.now() + timeoutMs
118
118
  while(Date.now() < deadline) {
119
119
  try {
@@ -18,6 +18,10 @@ export interface ReclaimAgentConfig {
18
18
  /**
19
19
  * The single on-disk document at `~/.reclaim/config.json`. Each top-level
20
20
  * key is an independent section owned by a different subsystem:
21
+ * - `mode` — persisted backend preference (old-devtools vs builder),
22
+ * set via the `set_devtools_mode` tool. Applied at server
23
+ * start; an explicit `USE_OLD_DEVTOOLS` env var overrides
24
+ * it (see `resolveOldMode` in consts.ts).
21
25
  * - `browser_agent` — Chrome attach mode/port (the agent tools)
22
26
  * - `session` — the auth bearer token (authenticate)
23
27
  * - `old_devtools` — externally-supplied identity for OLD-devtools mode
@@ -32,11 +36,15 @@ export interface ReclaimAgentConfig {
32
36
  */
33
37
  export interface ReclaimConfig {
34
38
  version: 1
39
+ mode?: DevtoolsMode
35
40
  browser_agent?: ReclaimAgentConfig
36
41
  session?: CachedSession
37
42
  old_devtools?: OldIdentity
38
43
  }
39
44
 
45
+ /** Which backend the MCP server targets. See `resolveOldMode` in consts.ts. */
46
+ export type DevtoolsMode = 'old' | 'builder'
47
+
40
48
  /**
41
49
  * Read/write the unified Reclaim config document. The file holds the
42
50
  * (short-lived) session bearer token, so it is always locked to `0600` and
package/src/consts.ts CHANGED
@@ -72,6 +72,30 @@ export function useOldDevtools(): boolean {
72
72
  return (process.env.USE_OLD_DEVTOOLS ?? '').trim().toLowerCase() !== 'false'
73
73
  }
74
74
 
75
+ /** True when `USE_OLD_DEVTOOLS` is set to a non-empty value (explicit). */
76
+ function oldDevtoolsEnvSet(): boolean {
77
+ return (process.env.USE_OLD_DEVTOOLS ?? '').trim() !== ''
78
+ }
79
+
80
+ /**
81
+ * Effective mode, layering the persisted preference under the env var:
82
+ * explicit `USE_OLD_DEVTOOLS` wins → persisted config `mode` → default (old).
83
+ * The persisted value comes from `~/.reclaim/config.json` (set via the
84
+ * `set_devtools_mode` tool); the caller reads it so this module needn't import
85
+ * `ConfigStore` (which would create an import cycle).
86
+ */
87
+ export function resolveOldMode(persisted?: 'old' | 'builder'): boolean {
88
+ if(oldDevtoolsEnvSet()) {
89
+ return useOldDevtools()
90
+ }
91
+
92
+ if(persisted) {
93
+ return persisted === 'old'
94
+ }
95
+
96
+ return true
97
+ }
98
+
75
99
  // ---------------------------------------------------------------------------
76
100
  // Chrome / CDP launcher.
77
101
  // ---------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Package entry — SDK re-exports + agent public API.
2
- // The CLI lives at ./cli.ts, the MCP server bin at ./mcp-bin.ts.
2
+ // The MCP server bin lives at ./mcp-bin.ts.
3
3
 
4
4
  // SDK — re-export everything from @reclaimprotocol/client so consumers
5
5
  // only need one package for both the typed HTTP client and the agent tools.