@reclaimprotocol/agent 0.1.34 → 0.1.35

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 (49) hide show
  1. package/README.md +3 -3
  2. package/lib/mcp/api/operations.gen.js +340 -150
  3. package/lib/mcp/instructions.js +3 -3
  4. package/lib/mcp/start.js +8 -7
  5. package/lib/mcp/tools/agent/build.js +2 -0
  6. package/lib/mcp/tools/agent/check-attestor.d.ts +5 -0
  7. package/lib/mcp/tools/agent/check-attestor.js +37 -0
  8. package/lib/mcp/tools/agent/live-view.js +42 -18
  9. package/lib/mcp/tools/agent/proof.d.ts +12 -0
  10. package/lib/mcp/tools/agent/proof.js +32 -0
  11. package/lib/mcp/tools/agent/shared.d.ts +2 -1
  12. package/lib/mcp/tools/agent/shared.js +28 -24
  13. package/lib/mcp/tools/agent/start-integration.js +5 -4
  14. package/lib/mcp/tools/verification/build.d.ts +3 -1
  15. package/lib/mcp/tools/verification/build.js +10 -2
  16. package/lib/mcp/tools/verification/callback-listener.d.ts +51 -0
  17. package/lib/mcp/tools/verification/callback-listener.js +163 -0
  18. package/lib/mcp/tools/verification/index.d.ts +2 -0
  19. package/lib/mcp/tools/verification/index.js +2 -0
  20. package/lib/mcp/tools/verification/proxy-callback-events.d.ts +59 -0
  21. package/lib/mcp/tools/verification/proxy-callback-events.js +300 -0
  22. package/lib/mcp/tools/verification/read-callback-events.d.ts +3 -0
  23. package/lib/mcp/tools/verification/read-callback-events.js +135 -0
  24. package/lib/mcp/tools/verification/result.d.ts +6 -0
  25. package/lib/mcp/tools/verification/result.js +1 -1
  26. package/lib/proof/check-attestor.d.ts +67 -0
  27. package/lib/proof/check-attestor.js +156 -0
  28. package/package.json +2 -2
  29. package/skill/SKILL.builder.md +7 -1
  30. package/skill/SKILL.common.md +21 -9
  31. package/skill/builder-mode.md +17 -12
  32. package/skill/start-integration.md +36 -11
  33. package/skill/verification-client-integration.md +15 -9
  34. package/src/mcp/api/operations.gen.ts +344 -150
  35. package/src/mcp/instructions.ts +3 -3
  36. package/src/mcp/start.ts +9 -7
  37. package/src/mcp/tools/agent/build.ts +2 -0
  38. package/src/mcp/tools/agent/check-attestor.ts +49 -0
  39. package/src/mcp/tools/agent/live-view.ts +42 -18
  40. package/src/mcp/tools/agent/proof.ts +55 -0
  41. package/src/mcp/tools/agent/shared.ts +33 -23
  42. package/src/mcp/tools/agent/start-integration.ts +5 -4
  43. package/src/mcp/tools/verification/build.ts +10 -2
  44. package/src/mcp/tools/verification/callback-listener.ts +203 -0
  45. package/src/mcp/tools/verification/index.ts +2 -0
  46. package/src/mcp/tools/verification/proxy-callback-events.ts +374 -0
  47. package/src/mcp/tools/verification/read-callback-events.ts +160 -0
  48. package/src/mcp/tools/verification/result.ts +1 -1
  49. package/src/proof/check-attestor.ts +209 -0
@@ -1,4 +1,10 @@
1
1
  import { type RegisteredTool } from '../../server.ts';
2
+ /**
3
+ * Resolve the org secret used to authenticate verification reads. Auth is a
4
+ * single per-org bearer secret (`rorg_…`), not an eth signature: the call's
5
+ * `orgToken`, else `RECLAIM_ORG_SECRET`.
6
+ */
7
+ export declare function resolveOrgSecret(orgToken?: string): string;
2
8
  /**
3
9
  * `get_verification_result` — check a session's status and, once a result has
4
10
  * been submitted, pull the stored payload(s) and verify locally. Auth is the
@@ -25,7 +25,7 @@ const RESULT_EVENTS = new Set([
25
25
  * single per-org bearer secret (`rorg_…`), not an eth signature: the call's
26
26
  * `orgToken`, else `RECLAIM_ORG_SECRET`.
27
27
  */
28
- function resolveOrgSecret(orgToken) {
28
+ export function resolveOrgSecret(orgToken) {
29
29
  const secret = orgToken?.trim() || orgSecret()?.trim() || savedOrgKey();
30
30
  assert(secret, new Error('No organization API key. Verification reads authenticate with the org '
31
31
  + 'secret (rorg_…): run issue_org_token to save a key, pass orgToken, '
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Attestor reachability check. Proves a public request with a throwaway owner
3
+ * key to show whether the attestor works from this machine, independent of any
4
+ * developer provider or site.
5
+ */
6
+ import { createClaimOnAttestor } from '@reclaimprotocol/attestor-core';
7
+ import './patch-domhandler.ts';
8
+ /**
9
+ * Default time budget for one check. A live run takes about 8 s; a longer
10
+ * budget risks the MCP client's own tool-call timeout.
11
+ */
12
+ export declare const CHECK_ATTESTOR_TIMEOUT_MS = 45000;
13
+ /**
14
+ * Request params for the check: the public CoinGecko price request from the
15
+ * attestor-core examples. Source:
16
+ * https://raw.githubusercontent.com/reclaimprotocol/attestor-core/refs/heads/main/example/coin-gecko.json
17
+ */
18
+ export declare const COIN_GECKO_PARAMS: {
19
+ url: string;
20
+ method: "GET";
21
+ body: string;
22
+ headers: {
23
+ 'User-Agent': string;
24
+ 'Sec-Fetch-Mode': string;
25
+ 'Sec-Fetch-Site': string;
26
+ };
27
+ responseMatches: {
28
+ invert: boolean;
29
+ type: "regex";
30
+ value: string;
31
+ }[];
32
+ responseRedactions: {
33
+ jsonPath: string;
34
+ regex: string;
35
+ xPath: string;
36
+ }[];
37
+ additionalClientOptions: {};
38
+ };
39
+ export type AttestorCheckErrorKind =
40
+ /** Socket, connection, DNS, TLS, or timeout failure. */
41
+ 'attestor-unreachable'
42
+ /** The attestor answered and rejected or failed the claim. */
43
+ | 'attestor-error'
44
+ /** Local setup (ZK files) failed before the attestor was contacted. */
45
+ | 'setup-failed';
46
+ export interface AttestorCheckResult {
47
+ ok: boolean;
48
+ attestorUrl: string;
49
+ durationMs: number;
50
+ extractedParameters?: Record<string, string>;
51
+ error?: string;
52
+ errorKind?: AttestorCheckErrorKind;
53
+ }
54
+ /** Creates the claim on the attestor (`createClaimOnAttestor` by default). */
55
+ export type CreateClaim = (args: Parameters<typeof createClaimOnAttestor>[0]) => Promise<unknown>;
56
+ export interface CheckAttestorOptions {
57
+ attestorUrl?: string;
58
+ timeoutMs?: number;
59
+ /** Replaces the network call in tests. */
60
+ createClaim?: CreateClaim;
61
+ /** Replaces the ZK file download in tests. */
62
+ prepare?: () => Promise<void>;
63
+ }
64
+ /** True when `message` looks like a network failure rather than a rejection. */
65
+ export declare function isNetworkError(message: string | undefined): boolean;
66
+ /** Prove the public CoinGecko request once and classify the outcome. */
67
+ export declare function checkAttestor(opts?: CheckAttestorOptions): Promise<AttestorCheckResult>;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Attestor reachability check. Proves a public request with a throwaway owner
3
+ * key to show whether the attestor works from this machine, independent of any
4
+ * developer provider or site.
5
+ */
6
+ import { createClaimOnAttestor } from '@reclaimprotocol/attestor-core';
7
+ import { generateKeypair } from '@reclaimprotocol/client';
8
+ import "./patch-domhandler.js";
9
+ import { claimRejection, ensureAttestorCrypto, parseClaim, resolveAttestorUrl, } from "./proof-core.js";
10
+ import { ensureZkFiles } from "./zk-files.js";
11
+ /**
12
+ * Default time budget for one check. A live run takes about 8 s; a longer
13
+ * budget risks the MCP client's own tool-call timeout.
14
+ */
15
+ export const CHECK_ATTESTOR_TIMEOUT_MS = 45000;
16
+ /* eslint-disable @stylistic/max-len -- the source URL cannot wrap. */
17
+ /**
18
+ * Request params for the check: the public CoinGecko price request from the
19
+ * attestor-core examples. Source:
20
+ * https://raw.githubusercontent.com/reclaimprotocol/attestor-core/refs/heads/main/example/coin-gecko.json
21
+ */
22
+ /* eslint-enable @stylistic/max-len */
23
+ export const COIN_GECKO_PARAMS = {
24
+ url: 'https://api.coingecko.com/api/v3/simple/price'
25
+ + '?ids=cardano,solana&vs_currencies=usd',
26
+ method: 'GET',
27
+ body: '',
28
+ headers: {
29
+ 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) '
30
+ + 'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1',
31
+ 'Sec-Fetch-Mode': 'same-origin',
32
+ 'Sec-Fetch-Site': 'same-origin',
33
+ },
34
+ responseMatches: [
35
+ {
36
+ invert: false,
37
+ type: 'regex',
38
+ value: 'cardano":{"usd":(?<cardanoPrice>\\d*\\.*\\d*)}',
39
+ },
40
+ {
41
+ invert: false,
42
+ type: 'regex',
43
+ value: 'solana":{"usd":(?<solanaPrice>\\d*\\.*\\d*)}',
44
+ },
45
+ ],
46
+ responseRedactions: [
47
+ { jsonPath: '$.cardano', regex: 'cardano":{"usd":(.*?)}', xPath: '' },
48
+ { jsonPath: '$.solana', regex: 'solana":{"usd":(.*?)}', xPath: '' },
49
+ ],
50
+ additionalClientOptions: {},
51
+ };
52
+ /** The only secret in the example request. */
53
+ const COIN_GECKO_SECRET_HEADERS = {
54
+ Referer: 'https://www.coingecko.com/en/nft',
55
+ };
56
+ /** Prefix of the error raised when the check exceeds its time budget. */
57
+ const CHECK_TIMEOUT_MESSAGE = 'attestor check timed out';
58
+ /**
59
+ * Signals that the attestor connection failed. Bare "timeout", "timed out",
60
+ * and "websocket" are left out: a target site's errors contain them too.
61
+ */
62
+ const NETWORK_ERROR_PATTERN = new RegExp([
63
+ 'connection closed',
64
+ 'ERROR_NETWORK_ERROR',
65
+ 'ECONNRESET',
66
+ 'ECONNREFUSED',
67
+ 'ENOTFOUND',
68
+ 'EAI_AGAIN',
69
+ 'ETIMEDOUT',
70
+ 'EHOSTUNREACH',
71
+ 'ENETUNREACH',
72
+ 'socket hang up',
73
+ 'websocket (connection|error|closed|failed)',
74
+ '(failed|unable|could not) to connect',
75
+ CHECK_TIMEOUT_MESSAGE,
76
+ ].join('|'), 'i');
77
+ /** True when `message` looks like a network failure rather than a rejection. */
78
+ export function isNetworkError(message) {
79
+ return message !== undefined && NETWORK_ERROR_PATTERN.test(message);
80
+ }
81
+ /** Prove the public CoinGecko request once and classify the outcome. */
82
+ export async function checkAttestor(opts = {}) {
83
+ const attestorUrl = resolveAttestorUrl(opts.attestorUrl);
84
+ const timeoutMs = opts.timeoutMs ?? CHECK_ATTESTOR_TIMEOUT_MS;
85
+ const createClaim = opts.createClaim ?? createClaimOnAttestor;
86
+ const prepare = opts.prepare ?? (async () => {
87
+ ensureAttestorCrypto();
88
+ await ensureZkFiles();
89
+ });
90
+ const startedAt = Date.now();
91
+ const finish = (outcome) => {
92
+ return { ...outcome, attestorUrl, durationMs: Date.now() - startedAt };
93
+ };
94
+ let timer;
95
+ const timeout = new Promise((_, reject) => {
96
+ timer = setTimeout(() => {
97
+ reject(new Error(`${CHECK_TIMEOUT_MESSAGE} after ${timeoutMs} ms`));
98
+ }, timeoutMs);
99
+ });
100
+ try {
101
+ try {
102
+ await Promise.race([prepare(), timeout]);
103
+ }
104
+ catch (err) {
105
+ const error = messageOf(err);
106
+ return finish({
107
+ ok: false,
108
+ error,
109
+ errorKind: error.startsWith(CHECK_TIMEOUT_MESSAGE)
110
+ ? 'attestor-unreachable'
111
+ : 'setup-failed',
112
+ });
113
+ }
114
+ const claim = await Promise.race([
115
+ createClaim({
116
+ name: 'http',
117
+ params: COIN_GECKO_PARAMS,
118
+ secretParams: { headers: COIN_GECKO_SECRET_HEADERS },
119
+ ownerPrivateKey: generateKeypair().privateKey,
120
+ client: { url: attestorUrl },
121
+ zkEngine: 'stwo',
122
+ }),
123
+ timeout,
124
+ ]);
125
+ const rejection = claimRejection(claim);
126
+ if (rejection !== undefined) {
127
+ return finish({
128
+ ok: false,
129
+ error: rejection,
130
+ errorKind: isNetworkError(rejection)
131
+ ? 'attestor-unreachable'
132
+ : 'attestor-error',
133
+ });
134
+ }
135
+ return finish({
136
+ ok: true,
137
+ extractedParameters: parseClaim(claim).extractedParameters,
138
+ });
139
+ }
140
+ catch (err) {
141
+ const error = messageOf(err);
142
+ return finish({
143
+ ok: false,
144
+ error,
145
+ errorKind: isNetworkError(error)
146
+ ? 'attestor-unreachable'
147
+ : 'attestor-error',
148
+ });
149
+ }
150
+ finally {
151
+ clearTimeout(timer);
152
+ }
153
+ }
154
+ function messageOf(err) {
155
+ return err instanceof Error ? err.message : String(err);
156
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.34",
4
+ "version": "0.1.35",
5
5
  "description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
@@ -83,7 +83,7 @@
83
83
  "@modelcontextprotocol/sdk": "^1.29.0",
84
84
  "@noble/hashes": "^2.2.0",
85
85
  "@reclaimprotocol/attestor-core": "^5.1.5",
86
- "@reclaimprotocol/client": "0.1.34",
86
+ "@reclaimprotocol/client": "0.1.35",
87
87
  "@reclaimprotocol/tls": "^0.1.3",
88
88
  "chrome-remote-interface": "^0.33.0",
89
89
  "domhandler": "^6.0.0",
@@ -315,6 +315,11 @@ provider JSON. That's the final result.
315
315
  developer what's happening and drive the bootstrap yourself — see topic
316
316
  `credentials`. No restart needed, and the existing `draftId` stays valid.
317
317
 
318
+ **Network error from `run_proof`.** Read its `diagnosis`. Attestor reachable
319
+ → the target site likely blocks the attestor's network; call
320
+ `run_proof_via_pod`. Attestor unreachable → `check_attestor` confirms it; the
321
+ fix is on the developer's network. Don't loop retries.
322
+
318
323
  <!-- common: prove-tail -->
319
324
 
320
325
  <!-- topic: publish -->
@@ -456,6 +461,7 @@ or omit `interceptorOptions`). If that fixes the provider, keep interception
456
461
  off and replace intercepted claims with `window.Reclaim.requestClaim`. Full
457
462
  checklist and remedies: topic `interception`.
458
463
 
459
- **Proof failures / attestor errors** → topic `prove`.
464
+ **Proof failures / attestor errors** → topic `prove`. For a network error,
465
+ read `run_proof`'s `diagnosis`, then `check_attestor` or `run_proof_via_pod`.
460
466
  **Browser won't launch or attach** → topic `browser`.
461
467
  **Missing signing key** → topic `credentials`.
@@ -112,13 +112,18 @@ Ask plainly:
112
112
 
113
113
  If they say yes:
114
114
 
115
- 1. Call `share_browser_view` with no arguments. It returns the viewer `port` and
116
- the `command` (`cloudflared tunnel --url http://localhost:<port>`). The MCP
117
- server does not run cloudflared.
118
- 2. Run `command` yourself in the background. If `cloudflared` is not installed,
119
- ask the developer to install it; do not install it without their approval.
120
- 3. Read its output for the `https://<words>.trycloudflare.com` URL. Never guess
121
- or reconstruct the hostname.
115
+ 1. Call `share_browser_view` with no arguments. It returns the viewer `port`
116
+ and the `command` (`cloudflared tunnel --url http://localhost:<port>`). The
117
+ MCP server does not run cloudflared.
118
+ 2. Ask the developer to choose, and start no tunnel until they answer:
119
+ - **They run it (recommended).** They run `! <command>` in this session and
120
+ paste the `https://<words>.trycloudflare.com` URL. The tunnel then lives
121
+ in their shell, and they decide when it closes.
122
+ - **You run it.** They approve you running `command` in the background. If
123
+ `cloudflared` is not installed, ask them to install it; do not install it
124
+ without their approval.
125
+ 3. When you run it, read its output for the URL. Never guess or reconstruct the
126
+ hostname.
122
127
  4. Call `share_browser_view({ publicUrl })` with that URL. **Relay the returned
123
128
  `url` verbatim, on its own line.** It includes `liveview.html?magnify=1`.
124
129
 
@@ -138,8 +143,8 @@ If they say yes:
138
143
  - **Opening it on a phone lays the page out for that phone**, with touch,
139
144
  pinch-zoom and the soft keyboard. That is the reason to reach for `container`
140
145
  when a site only works on mobile.
141
- - **Stop the cloudflared process you started as soon as they're done.** The
142
- browser keeps running. `dispose_browser` and `reset_session` stop the
146
+ - **Stop a cloudflared process you started as soon as they're done.** If they
147
+ started it, tell them to stop it. The browser keeps running. `dispose_browser` and `reset_session` stop the
143
148
  container itself, but not your cloudflared process.
144
149
  - **Expose only the viewer port** that `share_browser_view` returns, never the
145
150
  browser's CDP port.
@@ -381,6 +386,13 @@ supplies are context params.
381
386
  `result.attestorLogs` and dumps `~/.reclaim/proof-runs/<timestamp>.json`
382
387
  (`runLogPath`). Off by default — suggest it for a recurring failure.
383
388
 
389
+ **Network errors** ("connection closed", `ERROR_NETWORK_ERROR`, timeouts):
390
+ read the result's `diagnosis`. It says whether the attestor is reachable
391
+ from this machine. If it is, the target site likely blocks the attestor's
392
+ network; in builder mode, run `run_proof_via_pod`. If it is not, call
393
+ `check_attestor` after the developer fixes their network. Do not loop
394
+ retries; one `run_proof` per change.
395
+
384
396
  **Do not auto-retry with a different `attestorUrl`.** There is one live
385
397
  public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
386
398
  exist**. `attestorUrl` is only for a developer-supplied private/staging URL.
@@ -42,19 +42,17 @@ or `run_proof` against it unless the developer asks.
42
42
 
43
43
  ### Try a provider without an app
44
44
 
45
- Use the production API the app will use. Session creation returns 409 while the
46
- org has no callback subscription, and results are stored only for subscriptions
47
- to the event the session ends with.
45
+ Session creation returns 409 while the org has no callback subscription.
46
+ `read_callback_events` creates the org's pull listener, which satisfies that
47
+ check and holds results for you to read. No tunnel or public URL is needed.
48
48
 
49
- 1. If the org has none, register a temporary subscription for all five ending
50
- events with `create_org_callback_subscription` (for example, a tunnel to a
51
- local listener that saves the body it receives).
49
+ 1. Call `read_callback_events`.
52
50
  2. `create_verification_session`, then give the developer `verificationUrl`.
53
- 3. Poll `get_verification_session` until it is terminal. Report the outcome
54
- from the listener's body, or with `get_verification_result` (works only
55
- until the callback is delivered).
56
- 4. `delete_org_callback_subscription` only after that. Deleting it also
57
- deletes its stored results.
51
+ 3. Poll `get_verification_session` until it is terminal.
52
+ 4. Call `read_callback_events({ sessionId })` for the verified result.
53
+
54
+ When you set up a new provider build, call `check_attestor` once. If it fails,
55
+ tell the developer before they spend time on the build.
58
56
 
59
57
  ## Secrets
60
58
 
@@ -77,11 +75,18 @@ TEE binding. Edit the app with your file tools. Use the HTTP API
77
75
  `@reclaimprotocol/client`. The only exception is `results.receive` on the
78
76
  callback, or when the developer asks for the client package.
79
77
 
78
+ For local development, call `proxy_callback_events({ port, path })` to forward
79
+ events to the app's callback route on localhost. The local listener works only
80
+ while this MCP server runs. Production needs a webhook:
81
+ `create_org_callback_subscription` with the deployed app's public URL. Without
82
+ one, session creation returns 409.
83
+
80
84
  Management tools (`list_orgs`, `create_org_callback_subscription`,
81
85
  `issue_org_token`) use the developer's sign-in. Session tools
82
86
  (`create_verification_session`, `get_verification_session`,
83
87
  `list_verification_events`, `list_verification_deliveries`,
84
- `retry_verification_callback`) are the production API and use the org key.
88
+ `retry_verification_callback`, `read_callback_events`,
89
+ `proxy_callback_events`) are the production API and use the org key.
85
90
 
86
91
  **Existing `@reclaimprotocol/js-sdk` projects.** If `package.json` depends on
87
92
  `@reclaimprotocol/js-sdk`, ask first:
@@ -12,11 +12,38 @@ Follow this plan end to end. Mark each step done as you go. Do not ask the devel
12
12
  - [ ] Call `list_orgs`. Use the only organization, or ask the developer which one when there are several.
13
13
  <!-- endif -->
14
14
 
15
- ## 2. Get a provider
15
+ ## 2. Set up the app
16
+
17
+ Setup does not need the provider, so it runs in parallel with step 3 or before it.
18
+
19
+ **Delegation.** If you can run subagents, hand this step to one subagent and start step 3 now: step 3 needs the developer at the browser, step 2 does not. Give the subagent a self-contained brief:
20
+
21
+ ```
22
+ Goal: let this app's users prove "{{goal}}" with Reclaim.
23
+ Repository: <absolute path>. Organization: <org ID>.
24
+ Task: do step 2 of the plan below and report the file paths you changed, the
25
+ callback route URL, and the env variable that holds the provider ID.
26
+ Do not build the provider. Never print or commit the org key.
27
+ <paste the step 2 checklist>
28
+ ```
29
+
30
+ Without subagents, do step 2 first.
31
+
32
+ - [ ] Detect the app's framework and package manager from the repository, and use them. Read `how_it_works({ topic: "integration" })`.
33
+ - [ ] Call `issue_org_token` with the organization, this project's absolute path, and the server env file. It writes the key to that file and never returns it. Never print or commit the key. Confirm the env file is in `.gitignore`.
34
+ - [ ] Add a backend route that creates a verification session (`POST /verifications/sessions`) and returns `verificationUrl`. Read the provider ID from server config (an env variable such as `RECLAIM_PROVIDER_ID`), so the route works before the provider exists.
35
+ - [ ] Add a callback route that verifies each result with `results.receive` from `@reclaimprotocol/client`, then stores only the requested data points.
36
+ - [ ] Add a frontend Verify button that opens `verificationUrl`, and shows the stored result when it arrives.
37
+ - [ ] Start the app. Call `proxy_callback_events({ port, path })` with the callback route's port and path. It forwards the org's verification events to that route on localhost, with no public URL.
38
+ <!-- ifnot:providerIds -->
39
+ - [ ] Call `check_attestor` once. If it fails, tell the developer before they spend time on the provider build: this machine cannot reach the attestor.
40
+ <!-- endif -->
41
+
42
+ ## 3. Get the provider
16
43
 
17
44
  <!-- if:providerIds -->
18
45
  - [ ] Use these existing providers: {{providerIds}}. Read each with `get_provider` and keep only the data points "{{goal}}" needs. Show the developer and use only those; ignore the rest.
19
- - [ ] If a provider needs any change (a missing data point, inputs the developer didn't ask for, a different value), make your own copy as the server instructions describe ("Choose the provider"). Otherwise skip to step 3.
46
+ - [ ] If a provider needs any change (a missing data point, inputs the developer didn't ask for, a different value), make your own copy as the server instructions describe ("Choose the provider"). Otherwise skip to step 4.
20
47
  <!-- endif -->
21
48
  <!-- ifnot:providerIds -->
22
49
  Build a new organization-private provider for exactly "{{goal}}". Do not search published providers first.
@@ -37,13 +64,11 @@ Build a new organization-private provider for exactly "{{goal}}". Do not search
37
64
  - [ ] Call `dispose_browser` after asking the developer.
38
65
  <!-- endif -->
39
66
 
40
- ## 3. Integrate into this codebase
67
+ ## 4. Wire the provider and test end to end
41
68
 
42
- - [ ] Read `how_it_works({ topic: "integration" })`.
43
- - [ ] Detect the app's framework and package manager from the repository, and use them.
44
- - [ ] Call `issue_org_token` with the organization, this project's absolute path, and the server env file. It writes the key to that file and never returns it. Never print or commit the key. Confirm the env file is in `.gitignore`.
45
- - [ ] Add a backend route that creates a verification session (`POST /verifications/sessions`) for the provider and returns `verificationUrl`.
46
- - [ ] Add a callback route that verifies each result with `results.receive` from `@reclaimprotocol/client`, then stores only the requested data points.
47
- - [ ] Call `create_org_callback_subscription` for the result events, pointing at the callback route. Use a tunnel for local development.
48
- - [ ] Add a frontend Verify button that opens `verificationUrl`, and shows the stored result when it arrives.
49
- - [ ] Run the app, then tell the developer exactly how to test end to end: open the app, click Verify, sign in, and see the stored result.
69
+ - [ ] Set the provider ID in the app's server config.
70
+ - [ ] Confirm the callback route is forwarding: `proxy_callback_events` with no `stop` returns its status.
71
+ - [ ] Tell the developer exactly how to test: open the app, click Verify, sign in, and see the stored result.
72
+ - [ ] After they click Verify, read the delivered events from `proxy_callback_events` status (`recent`) or `read_callback_events`. If the result did not arrive, call `list_verification_deliveries` and fix the cause. Repeat until the app shows the stored result.
73
+ - [ ] Ask the developer to set up the production callback. The local listener works only while this coding agent's MCP server runs; once it stops, the deployed app receives no results. Call `create_org_callback_subscription` with the deployed app's public callback route URL for all five session-ending events: `verification_success`, `verification_rejected`, `verification_error`, `verification_cancelled`, and `session_expired`.
74
+ - [ ] If they have no deployed URL yet, tell them: "When you deploy, ask me to run `create_org_callback_subscription` with your public callback URL, or results will not reach the app."
@@ -21,12 +21,16 @@ server), or migrate to the session flow below. When migrating, remove
21
21
  1. `get_devtools_mode` confirms Builder mode. `get_me` or `authenticate` signs
22
22
  in the developer. Find the organization with `list_orgs`; call `create_org`
23
23
  only when they want a new one.
24
- 2. Register a callback URL owned by the app's backend with
25
- `create_org_callback_subscription`. Include terminal success, rejection,
26
- error, cancellation, and expiry events. If the app needs encrypted results,
27
- register its public key with `set_org_keypair`; keep the private key only in
28
- the backend. `set_org_keypair` refuses to replace an existing key unless you
29
- pass `replace: true`; pass it only when the developer asks.
24
+ 2. Receive callbacks. Local development: call
25
+ `proxy_callback_events({ port, path })` to forward the org's events to the
26
+ app's callback route on localhost; no public URL. Deployed app (required for
27
+ production): register a
28
+ public callback URL with `create_org_callback_subscription`, including
29
+ all five session-ending events. If the
30
+ app needs encrypted results, register its public key with `set_org_keypair`;
31
+ keep the private key only in the backend. `set_org_keypair` refuses to
32
+ replace an existing key unless you pass `replace: true`; pass it only when
33
+ the developer asks.
30
34
  3. Org key: reuse the `RECLAIM_ORG_SECRET` the app already has. Otherwise an
31
35
  organization owner calls `issue_org_token`. It writes the key to the
32
36
  project's env file (default `.env`, variable `RECLAIM_ORG_SECRET`) and
@@ -75,10 +79,12 @@ Use the API base URL the developer's Builder runs at (production:
75
79
  `https://build.reclaimprotocol.org`). Every call authenticates with
76
80
  `authorization: Bearer $RECLAIM_ORG_SECRET`.
77
81
 
78
- Subscribe a callback once (or use `create_org_callback_subscription`). Session
79
- creation returns 409 while the org has none, and a result is only delivered to
82
+ Deployed apps subscribe a callback once (or use
83
+ `create_org_callback_subscription`); local development uses
84
+ `proxy_callback_events` instead. Session creation returns 409 while the org has
85
+ no subscription, and a result is only delivered to
80
86
  subscriptions that include the event the session ends with, so subscribe to all
81
- five:
87
+ five session-ending events:
82
88
 
83
89
  ```bash
84
90
  curl -X POST https://build.reclaimprotocol.org/verifications/callback-subscriptions \