@reclaimprotocol/agent 0.1.32 → 0.1.33

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.
@@ -21,19 +21,21 @@ migrating, remove `@reclaimprotocol/js-sdk` and replace its
21
21
  error, cancellation, and expiry events. If the app needs encrypted results,
22
22
  register its public key with `set_org_keypair`; retain the private key only
23
23
  in the backend.
24
- 3. An organization owner can call `issue_org_token`. Its `orgSecret` is shown
25
- once. Put it in the app backend's secret configuration as
26
- `RECLAIM_ORG_SECRET`; keep it out of frontend bundles, logs, source control,
27
- and the final response. Reuse an already configured secret when available;
28
- issue a new token when setup needs one.
29
- 4. Prefer plain HTTP from the backend. Create sessions with
30
- `POST /verifications/sessions` (`Authorization: Bearer <orgSecret>`), save
31
- each returned session ID alongside the app's own user or order, and return
32
- only `verificationUrl` to the frontend. The frontend opens that URL and asks
33
- its backend for the final status (`GET /verifications/sessions/{sessionId}`);
34
- a browser redirect alone is not proof of success. Use
35
- `@reclaimprotocol/client` (`ReclaimVerification.create({ orgSecret })`,
36
- `sessions.create`) only when the developer asks for it.
24
+ 3. Reuse a `RECLAIM_ORG_SECRET` the app already has. Otherwise an organization
25
+ owner calls `issue_org_token`; its `orgSecret` is shown once. Write it with
26
+ your file tools into the backend's env file (for example
27
+ `RECLAIM_ORG_SECRET=rorg_…` in `.env`) and make sure that file is
28
+ gitignored. Keep it out of frontend bundles, logs, source control, and the
29
+ final response.
30
+ 4. Prefer plain HTTP from the backend (see [Code to write](#code-to-write)).
31
+ Create sessions with `POST /verifications/sessions`
32
+ (`Authorization: Bearer $RECLAIM_ORG_SECRET`), save each returned session ID
33
+ alongside the app's own user or order, and return only `verificationUrl` to
34
+ the frontend. The frontend opens that URL and asks its backend for the final
35
+ status (`GET /verifications/sessions/{sessionId}`); a browser redirect alone
36
+ is not proof of success. Use `@reclaimprotocol/client`
37
+ (`ReclaimVerification.create({ orgSecret })`, `sessions.create`) only when
38
+ the developer asks for it.
37
39
  5. Add a backend callback endpoint. Persist each delivery idempotently and load
38
40
  the expected session ID and org ID from app storage. To verify the result on
39
41
  your callback, install `@reclaimprotocol/client` in the backend and call
@@ -51,6 +53,100 @@ consumer backend uses the separate organization token. The SDK's API-only
51
53
  entry point, `@reclaimprotocol/client/api`, is available when an application
52
54
  needs typed Builder operations without the full verification workflow.
53
55
 
56
+ The MCP session tools (`create_verification_session`,
57
+ `get_verification_session`, `list_verification_events`,
58
+ `list_verification_deliveries`, `retry_verification_callback`,
59
+ `create_callback_subscription`, `list_callback_subscriptions`,
60
+ `delete_callback_subscription`, `bind_verification_session_tee_nonce`, and
61
+ `get_verification_result`) call these same production endpoints with the org
62
+ key: pass it as `orgToken`, or let them fall back to the MCP server's
63
+ `RECLAIM_ORG_SECRET`. Use them, or `curl` with the key from `.env`, to test the
64
+ integration end to end.
65
+
66
+ ## Code to write
67
+
68
+ Use the API base URL the developer's Builder runs at (production:
69
+ `https://build.reclaimprotocol.org`). Every call authenticates with
70
+ `authorization: Bearer $RECLAIM_ORG_SECRET`.
71
+
72
+ Subscribe a callback once (or use `create_org_callback_subscription`). Session
73
+ creation returns 409 while the org has none, and a result is only delivered to
74
+ subscriptions that include the event the session ends with, so subscribe to all
75
+ five:
76
+
77
+ ```bash
78
+ curl -X POST https://build.reclaimprotocol.org/verifications/callback-subscriptions \
79
+ -H "authorization: Bearer $RECLAIM_ORG_SECRET" \
80
+ -H "content-type: application/json" \
81
+ -d '{
82
+ "callbackUrl": "https://your.app/reclaim/callback",
83
+ "events": [
84
+ "verification_success",
85
+ "verification_rejected",
86
+ "verification_error",
87
+ "verification_cancelled",
88
+ "session_expired"
89
+ ]
90
+ }'
91
+ ```
92
+
93
+ Create a session per claimant, from the backend; store `id` and hand
94
+ `verificationUrl` to the frontend:
95
+
96
+ ```bash
97
+ curl -X POST https://build.reclaimprotocol.org/verifications/sessions \
98
+ -H "authorization: Bearer $RECLAIM_ORG_SECRET" \
99
+ -H "content-type: application/json" \
100
+ -d '{
101
+ "providers": [
102
+ {
103
+ "providerId": "<your-provider-id>"
104
+ }
105
+ ],
106
+ "context": {
107
+ "userId": "abc"
108
+ }
109
+ }'
110
+ ```
111
+
112
+ Receive and verify the result on the callback route. This is the one place the
113
+ client package is needed:
114
+
115
+ ```js
116
+ import { ReclaimVerification } from '@reclaimprotocol/client'
117
+
118
+ const reclaim = ReclaimVerification.create({
119
+ orgSecret: process.env.RECLAIM_ORG_SECRET,
120
+ })
121
+
122
+ app.post('/reclaim/callback', async(req, res) => {
123
+ const outcome = await reclaim.results.receive(req.body, {
124
+ reclaimSessionId: sessionId, // the id you stored
125
+ })
126
+ if(outcome.kind === 'result') {
127
+ console.log(outcome.result.payload) // verified
128
+ }
129
+
130
+ res.sendStatus(200)
131
+ })
132
+ ```
133
+
134
+ Check or investigate a session, from the backend (optional, e.g. a status
135
+ endpoint the frontend polls) or by hand with `curl`. All take the org key:
136
+
137
+ | Route | Use |
138
+ | --- | --- |
139
+ | `GET /verifications/sessions/{sessionId}` | Status and outcome. |
140
+ | `GET /verifications/sessions/{sessionId}/events` | The claimant's event timeline, to see where a flow stopped. |
141
+ | `GET /verifications/sessions/{sessionId}/deliveries` | Callback delivery attempts and their responses. |
142
+ | `GET /verifications/sessions/{sessionId}/result` | Recovery only: a signed result Builder hasn't delivered yet. Builder clears it once the callback succeeds, or after 7 days undelivered (`payload: null`). Verify with `verifyResultFull`. |
143
+ | `POST /verifications/sessions/{sessionId}/retry-callback` | Redeliver an undelivered result (within the 7 days). |
144
+
145
+ ```bash
146
+ curl https://build.reclaimprotocol.org/verifications/sessions/$SESSION_ID \
147
+ -H "authorization: Bearer $RECLAIM_ORG_SECRET"
148
+ ```
149
+
54
150
  ## Create and launch a session
55
151
 
56
152
  Consumers authenticate with an organization token (`rorg_…`, issued in the
@@ -112,7 +208,8 @@ that public key instead; decrypt it with `@reclaimprotocol/client`'s
112
208
  `decryptCallback`/`verifyResult` and the org's eth private key before treating
113
209
  it as a result. A session created without `canEncryptResult` never needs this
114
210
  step. Troubleshoot a stuck or failed session with `get_verification_session`,
115
- `list_verification_events`, and `list_org_verification_sessions`.
211
+ `list_verification_events`, `list_verification_deliveries` (all with
212
+ `orgToken`), and `list_org_verification_sessions`.
116
213
 
117
214
  ## Use the browser extension
118
215
 
@@ -1,5 +1,5 @@
1
1
  // AUTO-GENERATED by packages/app/scripts/mcp-tools-gen.ts
2
- // from packages/app/openapi.yaml (spec v0.1.0, source sha256:099a5bcf4fba).
2
+ // from packages/app/openapi.yaml (spec v0.1.0, source sha256:3e28ff308da3).
3
3
  // The hash catches drift the version misses (a spec can change without a
4
4
  // version bump) — do not remove it as "redundant" with the version.
5
5
  // Run `npm run openapi:gen --workspace=packages/app` to regenerate. Do not edit by hand.
@@ -9,7 +9,7 @@ import { toZod } from './to-zod.ts'
9
9
 
10
10
  export const MCP_TOOLS = {
11
11
  "AddOrgMember": {
12
- description: "Invite a member to an organization\n\nOwner only. Invites a user to the organization by email. The invitee does **not** need to be a registered user — an invite is created in either case, and an invite email with an accept link is dispatched. The new row is returned with `status: PENDING`; it flips to `ACTIVE` only when the invitee explicitly accepts via `POST /orgs/{orgId}/invite`. `PERSONAL` organizations reject this call (409).",
12
+ description: "Invite a member to an organization\n\nOwner only. Invites a user to the organization by email. The invitee does **not** need to be a registered user — an invite is created in either case, and an invite email with an accept link is dispatched. The new row is returned with `status: PENDING`; it flips to `ACTIVE` only when the invitee explicitly accepts via `POST /orgs/{orgId}/invite`.",
13
13
  inputSchema: toZod({
14
14
  "type": "object",
15
15
  "properties": {
@@ -50,7 +50,7 @@ export const MCP_TOOLS = {
50
50
  },
51
51
  "role": {
52
52
  "type": "string",
53
- "description": "Role a user holds in an organization. `OWNER` can invite, promote, demote, and remove members, and delete the org. `MEMBER` has read/write access to resources owned by the org but cannot manage membership. `PERSONAL` orgs have exactly one `OWNER` and reject all member-mutating operations.",
53
+ "description": "Role a user holds in an organization. `OWNER` can invite, promote, demote, and remove members, and delete the org. `MEMBER` has read/write access to resources owned by the org but cannot manage membership.",
54
54
  "enum": [
55
55
  "OWNER",
56
56
  "MEMBER"
@@ -425,6 +425,12 @@ export const MCP_TOOLS = {
425
425
  "maxLength": 132
426
426
  }
427
427
  }
428
+ },
429
+ "orgToken": {
430
+ "type": "string",
431
+ "pattern": "^rorg_",
432
+ "maxLength": 200,
433
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
428
434
  }
429
435
  },
430
436
  "required": [
@@ -434,6 +440,7 @@ export const MCP_TOOLS = {
434
440
  ],
435
441
  "additionalProperties": false
436
442
  }),
443
+ orgTokenOnly: true,
437
444
  },
438
445
  "CancelPublicListingRequest": {
439
446
  description: "Cancel a pending public listing review request\n\nCancels a pending public listing request by moving the version from `in_review` back to `draft`. Only org members can cancel their own review requests. This allows users to withdraw a request before admin review if they change their mind.",
@@ -581,6 +588,12 @@ export const MCP_TOOLS = {
581
588
  }
582
589
  }
583
590
  }
591
+ },
592
+ "orgToken": {
593
+ "type": "string",
594
+ "pattern": "^rorg_",
595
+ "maxLength": 200,
596
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
584
597
  }
585
598
  },
586
599
  "required": [
@@ -590,9 +603,10 @@ export const MCP_TOOLS = {
590
603
  ],
591
604
  "additionalProperties": false
592
605
  }),
606
+ orgTokenOnly: true,
593
607
  },
594
608
  "CreateOrg": {
595
- description: "Create an organization\n\nCreates a new `ORG`-kind organization with the caller as its first `OWNER`. The personal org is created automatically at user signup and is not created via this endpoint.",
609
+ description: "Create an organization\n\nCreates a new `ORG`-kind organization with the caller as its first `OWNER`. Every user gets a default organization at sign-up, and it counts toward their org-creation limit (1 active organization unless Reclaim support raises it). Past the limit this returns `409`; contact support@reclaimprotocol.org to raise the limit or archive an organization.",
596
610
  inputSchema: toZod({
597
611
  "type": "object",
598
612
  "properties": {
@@ -611,7 +625,7 @@ export const MCP_TOOLS = {
611
625
  "body": {
612
626
  "type": "object",
613
627
  "additionalProperties": false,
614
- "description": "Body for `POST /orgs` — just a name. Every org is created keyless, like auto-provisioned personal orgs; its Ethereum (secp256k1) key pair is published separately via `PUT /orgs/{orgId}/keypair`, which is also where `canEncryptResult` is set. Creation deliberately collects nothing else so it never blocks on generating and safeguarding a private key.",
628
+ "description": "Body for `POST /orgs` — just a name. Every org is created keyless, like each user's default org; its Ethereum (secp256k1) key pair is published separately via `PUT /orgs/{orgId}/keypair`, which is also where `canEncryptResult` is set. Creation deliberately collects nothing else so it never blocks on generating and safeguarding a private key.",
615
629
  "required": [
616
630
  "name"
617
631
  ],
@@ -1580,7 +1594,7 @@ export const MCP_TOOLS = {
1580
1594
  }),
1581
1595
  },
1582
1596
  "CreateVerificationSession": {
1583
- description: "Create a verification session\n\nCreates an end-user verification session. Requires an OrgToken and a callback subscription; pass providers, context, and an optional registered verificationClientUrl (omitted: the Portal). Billing refusal returns 402 or 429 with event billing_required and a terminal sessionId.",
1597
+ description: "Create a verification session\n\nCreates an end-user verification session. Requires an OrgToken and a callback subscription; pass providers, context, and an optional registered verificationClientUrl (omitted: the Portal). Send the end user to the returned verificationUrl; that is where they verify. Billing refusal returns 402 or 429 with event billing_required and a terminal sessionId.",
1584
1598
  inputSchema: toZod({
1585
1599
  "type": "object",
1586
1600
  "properties": {
@@ -1638,9 +1652,16 @@ export const MCP_TOOLS = {
1638
1652
  },
1639
1653
  "verificationClientUrl": {
1640
1654
  "type": "string",
1641
- "description": "Optional. Must exactly equal the base URL of a registered verification client (see the `verification_clients` registry / `/admin/verification-clients`). Omit to use the default (`portals`) client. The Builder resolves and stores the registered client UUID, then returns the ready-to-share `verificationUrl` with the session id and `api=2`. A URL that is not a registered client's base is rejected with 400. Consumers append any client-owned redirect parameters after receiving the URL; Builder does not manage them.",
1655
+ "description": "Optional. Must exactly equal the base URL of a registered verification client (see the `verification_clients` registry / `/admin/verification-clients`). Omit to use the default (`portals`) client. The Builder resolves and stores the registered client UUID, then returns the ready-to-share `verificationUrl` with the session id and `api=2`. A URL that is not a registered client's base is rejected with 400. Consumers append any client-owned redirect parameters after receiving the URL; Builder does not manage them. The built-in clients are `{origin}/share/{name}`, listed in the examples.",
1642
1656
  "maxLength": 2000,
1643
- "pattern": "^https?://"
1657
+ "pattern": "^https?://",
1658
+ "examples": [
1659
+ "https://build.reclaimprotocol.org/share/portals",
1660
+ "https://build.reclaimprotocol.org/share/verifier-app",
1661
+ "https://build.reclaimprotocol.org/share/reclaim-browser-extension",
1662
+ "https://build.reclaimprotocol.org/share/builder",
1663
+ "https://build.reclaimprotocol.org/share/inapp-sdk"
1664
+ ]
1644
1665
  },
1645
1666
  "themeId": {
1646
1667
  "type": "string",
@@ -1755,6 +1776,12 @@ export const MCP_TOOLS = {
1755
1776
  }
1756
1777
  }
1757
1778
  }
1779
+ },
1780
+ "orgToken": {
1781
+ "type": "string",
1782
+ "pattern": "^rorg_",
1783
+ "maxLength": 200,
1784
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
1758
1785
  }
1759
1786
  },
1760
1787
  "required": [
@@ -1764,6 +1791,7 @@ export const MCP_TOOLS = {
1764
1791
  ],
1765
1792
  "additionalProperties": false
1766
1793
  }),
1794
+ orgTokenOnly: true,
1767
1795
  },
1768
1796
  "DecideProviderVersionReview": {
1769
1797
  description: "Record a decision on a provider version review\n\nApproves or rejects a provider version awaiting review. Admin only. Approval promotes the version to the live channel; rejection moves it to the rejected branch with a reason. Use admin_reviews to find versions waiting on a decision.",
@@ -1853,6 +1881,12 @@ export const MCP_TOOLS = {
1853
1881
  },
1854
1882
  "body": {
1855
1883
  "type": "object"
1884
+ },
1885
+ "orgToken": {
1886
+ "type": "string",
1887
+ "pattern": "^rorg_",
1888
+ "maxLength": 200,
1889
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
1856
1890
  }
1857
1891
  },
1858
1892
  "required": [
@@ -1861,6 +1895,7 @@ export const MCP_TOOLS = {
1861
1895
  ],
1862
1896
  "additionalProperties": false
1863
1897
  }),
1898
+ orgTokenOnly: true,
1864
1899
  },
1865
1900
  "DeleteOrgCallbackSubscription": {
1866
1901
  description: "Delete an organization's callback subscription\n\nRemoves a callback subscription. Caller must be an active member of the owning org.",
@@ -2156,15 +2191,22 @@ export const MCP_TOOLS = {
2156
2191
  "type": "string",
2157
2192
  "enum": [
2158
2193
  "claude",
2194
+ "copilot",
2159
2195
  "cursor",
2196
+ "omp",
2160
2197
  "opencode",
2161
2198
  "codex",
2199
+ "antigravity",
2162
2200
  "others"
2163
2201
  ]
2164
2202
  },
2165
2203
  "providerId": {
2166
- "type": "string",
2167
- "format": "uuid"
2204
+ "type": "array",
2205
+ "maxItems": 20,
2206
+ "items": {
2207
+ "type": "string",
2208
+ "format": "uuid"
2209
+ }
2168
2210
  },
2169
2211
  "client": {
2170
2212
  "type": "string",
@@ -2571,6 +2613,12 @@ export const MCP_TOOLS = {
2571
2613
  },
2572
2614
  "body": {
2573
2615
  "type": "object"
2616
+ },
2617
+ "orgToken": {
2618
+ "type": "string",
2619
+ "pattern": "^rorg_",
2620
+ "maxLength": 200,
2621
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
2574
2622
  }
2575
2623
  },
2576
2624
  "required": [
@@ -2579,6 +2627,7 @@ export const MCP_TOOLS = {
2579
2627
  ],
2580
2628
  "additionalProperties": false
2581
2629
  }),
2630
+ orgTokenOnly: true,
2582
2631
  },
2583
2632
  "ImportDevtoolsProvider": {
2584
2633
  description: "Import a provider from devtools\n\nImports a public legacy provider into Builder by UUID. Requires human BearerAuth and a target orgId; preserves the provider UUID and is idempotent for seeded providers. Returns 409 if another Builder org owns the UUID.",
@@ -2706,6 +2755,12 @@ export const MCP_TOOLS = {
2706
2755
  },
2707
2756
  "body": {
2708
2757
  "type": "object"
2758
+ },
2759
+ "orgToken": {
2760
+ "type": "string",
2761
+ "pattern": "^rorg_",
2762
+ "maxLength": 200,
2763
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
2709
2764
  }
2710
2765
  },
2711
2766
  "required": [
@@ -2714,6 +2769,7 @@ export const MCP_TOOLS = {
2714
2769
  ],
2715
2770
  "additionalProperties": false
2716
2771
  }),
2772
+ orgTokenOnly: true,
2717
2773
  },
2718
2774
  "ListOrgCallbackSubscriptions": {
2719
2775
  description: "List an organization's callback subscriptions\n\nReturns every callback subscription owned by the organization. Caller must be an active member of the org.",
@@ -2751,7 +2807,7 @@ export const MCP_TOOLS = {
2751
2807
  }),
2752
2808
  },
2753
2809
  "ListOrgs": {
2754
- description: "List organizations the caller belongs to\n\nLists the organizations the caller belongs to, personal and shared. Use it first to find an organization id — most other tools take one.",
2810
+ description: "List organizations the caller belongs to\n\nLists the organizations the caller belongs to. Use it first to find an organization id — most other tools take one.",
2755
2811
  inputSchema: toZod({
2756
2812
  "type": "object",
2757
2813
  "properties": {
@@ -3018,6 +3074,12 @@ export const MCP_TOOLS = {
3018
3074
  },
3019
3075
  "body": {
3020
3076
  "type": "object"
3077
+ },
3078
+ "orgToken": {
3079
+ "type": "string",
3080
+ "pattern": "^rorg_",
3081
+ "maxLength": 200,
3082
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
3021
3083
  }
3022
3084
  },
3023
3085
  "required": [
@@ -3026,6 +3088,7 @@ export const MCP_TOOLS = {
3026
3088
  ],
3027
3089
  "additionalProperties": false
3028
3090
  }),
3091
+ orgTokenOnly: true,
3029
3092
  },
3030
3093
  "ListVerificationEvents": {
3031
3094
  description: "Get the event log for a session\n\nLists append-only lifecycle events for a session. Requires the owning organization's token. Use after get_verification_session to find the first missing or failed milestone; inspect eventData for failure details.",
@@ -3054,6 +3117,12 @@ export const MCP_TOOLS = {
3054
3117
  },
3055
3118
  "body": {
3056
3119
  "type": "object"
3120
+ },
3121
+ "orgToken": {
3122
+ "type": "string",
3123
+ "pattern": "^rorg_",
3124
+ "maxLength": 200,
3125
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
3057
3126
  }
3058
3127
  },
3059
3128
  "required": [
@@ -3062,6 +3131,7 @@ export const MCP_TOOLS = {
3062
3131
  ],
3063
3132
  "additionalProperties": false
3064
3133
  }),
3134
+ orgTokenOnly: true,
3065
3135
  },
3066
3136
  "MakePrivate": {
3067
3137
  description: "Change a PUBLIC provider back to PRIVATE\n\nChanges a PUBLIC provider's visibility to PRIVATE. Unlike the reverse (PRIVATE → PUBLIC), this does not require review since it restricts rather than expands access. Existing live versions remain but are no longer publicly listed.",
@@ -3251,7 +3321,7 @@ export const MCP_TOOLS = {
3251
3321
  }),
3252
3322
  },
3253
3323
  "RetryVerificationCallback": {
3254
- description: "Re-enqueue callback delivery for a session\n\nRe-enqueues delivery of a session's stored result to its callback subscriptions. Use this after `list_verification_deliveries` shows a `failed` or dead-lettered delivery, once the consumer's callback endpoint is reachable again and answers 2xx — it re-attempts every not-yet-delivered callback for the session. Usable by the session creator (authenticated by the consumer's `OrgToken`) to recover from a transient consumer-side outage.",
3324
+ description: "Re-enqueue callback delivery for a session\n\nRe-enqueues delivery of a session's stored result to its callback subscriptions. Use this after `list_verification_deliveries` shows a `failed` or dead-lettered delivery, once the consumer's callback endpoint is reachable again and answers 2xx — it re-attempts every not-yet-delivered callback for the session. Usable by the session creator (authenticated by the consumer's `OrgToken`) to recover from a transient consumer-side outage. A result left undelivered for 7 days is purged and can no longer be retried.",
3255
3325
  inputSchema: toZod({
3256
3326
  "type": "object",
3257
3327
  "properties": {
@@ -3277,6 +3347,12 @@ export const MCP_TOOLS = {
3277
3347
  },
3278
3348
  "body": {
3279
3349
  "type": "object"
3350
+ },
3351
+ "orgToken": {
3352
+ "type": "string",
3353
+ "pattern": "^rorg_",
3354
+ "maxLength": 200,
3355
+ "description": "The organization API key (`rorg_…`) this call authenticates with: from `issue_org_token`, or the app's RECLAIM_ORG_SECRET. Defaults to the MCP server's RECLAIM_ORG_SECRET. Never echo it back to the user."
3280
3356
  }
3281
3357
  },
3282
3358
  "required": [
@@ -3285,6 +3361,7 @@ export const MCP_TOOLS = {
3285
3361
  ],
3286
3362
  "additionalProperties": false
3287
3363
  }),
3364
+ orgTokenOnly: true,
3288
3365
  },
3289
3366
  "RevokeOrgToken": {
3290
3367
  description: "Revoke one of the organization's API keys\n\nRevokes a single API key by id (leaving the org's other keys intact). The caller must be an active **OWNER** of the organization. The key stops authenticating immediately.",
@@ -1,9 +1,12 @@
1
+ import { unauthorized } from '@hapi/boom'
1
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import type { ReclaimClient } from '@reclaimprotocol/client/api'
3
+ import { ReclaimClient } from '@reclaimprotocol/client/api'
3
4
  import type {
4
5
  IOperationArgs,
5
6
  IOperationId
6
7
  } from '@reclaimprotocol/client/openapi'
8
+ import assert from 'node:assert'
9
+ import { orgSecret } from '../../consts.ts'
7
10
  import { remapErrorAsResponse } from '../remap_errors.ts'
8
11
  import { successResponse } from '../success-response.ts'
9
12
  import { MCP_TOOLS } from './operations.gen.ts'
@@ -22,8 +25,29 @@ function toSnakeCase(name: string): string {
22
25
  .toLowerCase()
23
26
  }
24
27
 
25
- export function registerTools(server: McpServer, client: ReclaimClient) {
26
- for(const [opId, shape] of Object.entries(MCP_TOOLS)) {
28
+ /** The client for a call that presents the organization's API key. */
29
+ export type OrgClientFactory = (orgToken: string) => ReclaimClient
30
+
31
+ /**
32
+ * Register a tool per generated operation. Calls go out with the developer's
33
+ * session token, except operations only an org API key (`rorg_…`) authorizes:
34
+ * those take an `orgToken` input (else the server's RECLAIM_ORG_SECRET) and go
35
+ * out through `orgClient` with that key.
36
+ */
37
+ export function registerTools(
38
+ server: McpServer,
39
+ client: ReclaimClient,
40
+ orgClient: OrgClientFactory = (orgToken) => new ReclaimClient({
41
+ baseUrl: client.baseUrl,
42
+ token: orgToken,
43
+ }),
44
+ ) {
45
+ for(const [opId, tool] of Object.entries(MCP_TOOLS)) {
46
+ const orgTokenOnly = 'orgTokenOnly' in tool && tool.orgTokenOnly === true
47
+ const shape = {
48
+ description: tool.description,
49
+ inputSchema: tool.inputSchema,
50
+ }
27
51
  if(!isOperationId(opId)) {
28
52
  throw new Error(
29
53
  `Generated MCP operation is not in the API client: ${opId}`,
@@ -37,13 +61,21 @@ export function registerTools(server: McpServer, client: ReclaimClient) {
37
61
  server.registerTool(
38
62
  toSnakeCase(opId),
39
63
  shape,
40
- async(args: unknown) => {
64
+ async(input: unknown) => {
41
65
  try {
66
+ if(!isRecord(input)) {
67
+ throw new Error(`Invalid generated MCP arguments for ${opId}`)
68
+ }
69
+
70
+ const { orgToken, ...args } = input
42
71
  if(!isOperationArgs(opId, args)) {
43
72
  throw new Error(`Invalid generated MCP arguments for ${opId}`)
44
73
  }
45
74
 
46
- const result = await client.call(
75
+ const caller = orgTokenOnly
76
+ ? orgClient(orgTokenFor(toSnakeCase(opId), orgToken))
77
+ : client
78
+ const result = await caller.call(
47
79
  opId,
48
80
  args,
49
81
  )
@@ -79,3 +111,23 @@ function isOperationArgs<T extends IOperationId>(
79
111
  ): value is IOperationArgs<T> {
80
112
  return typeof value === 'object' && value !== null && !Array.isArray(value)
81
113
  }
114
+
115
+ function isRecord(value: unknown): value is Record<string, unknown> {
116
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
117
+ }
118
+
119
+ /** The org API key a call authenticates with: its `orgToken`, else the
120
+ * server's RECLAIM_ORG_SECRET. The session token (the developer's sign-in)
121
+ * can't stand in, since these routes accept only an org key. */
122
+ function orgTokenFor(tool: string, orgToken: unknown) {
123
+ const token = (typeof orgToken === 'string' && orgToken) || orgSecret()
124
+ assert(
125
+ token,
126
+ unauthorized(
127
+ `${tool} authenticates with the organization's API key. Pass `
128
+ + 'orgToken: one from issue_org_token, or the app\'s '
129
+ + 'RECLAIM_ORG_SECRET. Or set RECLAIM_ORG_SECRET for the MCP server.',
130
+ ),
131
+ )
132
+ return token
133
+ }
package/src/mcp/start.ts CHANGED
@@ -116,7 +116,12 @@ export async function startMcpServer() {
116
116
 
117
117
  // Add auto-generated tools with snake_case wrappers
118
118
  // for every MCP-tagged operation.
119
- registerTools(server, client)
119
+ // Org-key-only operations go out with the call's `orgToken` instead.
120
+ registerTools(server, client, (orgToken) => new ReclaimClient({
121
+ baseUrl: apiUrl(),
122
+ token: orgToken,
123
+ headers: { 'User-Agent': `${NAME}/${VERSION}` },
124
+ }))
120
125
 
121
126
  if(agentToolsEnabled) {
122
127
  agentState = buildAgentState()
@@ -17,19 +17,29 @@ const TERMINAL = new Set([
17
17
  'expired',
18
18
  ])
19
19
 
20
+ /** Events whose delivery is a signed result; the rest are plaintext event
21
+ * notifications. Mirrors `verification_result_events()`. */
22
+ const RESULT_EVENTS = new Set([
23
+ 'verification_success',
24
+ 'verification_rejected',
25
+ 'verification_error',
26
+ 'verification_cancelled',
27
+ ])
28
+
20
29
  /**
21
30
  * Resolve the org secret used to authenticate verification reads. Auth is a
22
- * single per-org bearer secret (`rorg_…`), not an eth signature — read it from
23
- * `RECLAIM_ORG_SECRET`.
31
+ * single per-org bearer secret (`rorg_…`), not an eth signature: the call's
32
+ * `orgToken`, else `RECLAIM_ORG_SECRET`.
24
33
  */
25
- function resolveOrgSecret(): string {
26
- const secret = orgSecret()?.trim()
34
+ function resolveOrgSecret(orgToken?: string): string {
35
+ const secret = orgToken?.trim() || orgSecret()?.trim()
27
36
  assert(
28
37
  secret,
29
38
  new Error(
30
- 'RECLAIM_ORG_SECRET is not set. Verification reads authenticate with ' +
31
- 'the org secret (rorg_…) — issue one from the dashboard ' +
32
- '(POST /orgs/{orgId}/token) and export it as RECLAIM_ORG_SECRET.',
39
+ 'No organization API key. Verification reads authenticate with the org '
40
+ + 'secret (rorg_…): pass orgToken (from issue_org_token, or the '
41
+ + 'app\'s RECLAIM_ORG_SECRET), or export RECLAIM_ORG_SECRET for the '
42
+ + 'MCP server.',
33
43
  ),
34
44
  )
35
45
  return secret
@@ -46,12 +56,15 @@ function resolveOrgSecret(): string {
46
56
  * Decryption, when needed, happens here.
47
57
  */
48
58
  export function verificationResultTool(): RegisteredTool {
49
- return defineTool<{ reclaimSessionId: string }>(
59
+ return defineTool<{ reclaimSessionId: string, orgToken?: string }>(
50
60
  {
51
61
  name: 'get_verification_result',
52
62
  description:
53
- 'Fetches and verifies a terminal Builder result for one session. '
54
- + 'Requires `RECLAIM_ORG_SECRET`; encrypted deliveries also require '
63
+ 'Fetches and verifies a terminal Builder result for one session, '
64
+ + 'while Builder still holds it: until its callback is delivered, or '
65
+ + '7 days undelivered. The callback is the normal way to get results. '
66
+ + 'Authenticates with the org API key: `orgToken`, else '
67
+ + '`RECLAIM_ORG_SECRET`. Encrypted deliveries also require '
55
68
  + 'the matching ETH decryption key. Returns `ready: false` until a '
56
69
  + 'payload is available. Invalid results fail verification and are not '
57
70
  + 'returned. Use `list_verification_events` to diagnose '
@@ -65,11 +78,20 @@ export function verificationResultTool(): RegisteredTool {
65
78
  description:
66
79
  'The verification session id from create_verification_session.',
67
80
  },
81
+ orgToken: {
82
+ type: 'string',
83
+ pattern: '^rorg_',
84
+ maxLength: 200,
85
+ description: 'The organization API key (`rorg_…`): from '
86
+ + '`issue_org_token`, or the app\'s RECLAIM_ORG_SECRET. '
87
+ + 'Defaults to the MCP server\'s RECLAIM_ORG_SECRET. Never echo '
88
+ + 'it back to the user.',
89
+ },
68
90
  },
69
91
  },
70
92
  },
71
93
  async(args) => {
72
- const secret = resolveOrgSecret()
94
+ const secret = resolveOrgSecret(args.orgToken)
73
95
  // Optional eth decryption key — absent ⇒ plaintext-only.
74
96
  const decryptionKey: DecryptionKey | undefined =
75
97
  await resolveDecryptionKey()
@@ -91,14 +113,34 @@ export function verificationResultTool(): RegisteredTool {
91
113
  const resultRes = await reclaim.api('GetVerificationResult', {
92
114
  params: { sessionId: args.reclaimSessionId },
93
115
  })
94
- const { items } = resultRes.data
116
+ const signed = resultRes.data.items
117
+ .filter((item) => RESULT_EVENTS.has(item.event))
118
+ if(!signed.length) {
119
+ return {
120
+ reclaimSessionId: args.reclaimSessionId,
121
+ status,
122
+ ready: false,
123
+ note: 'Session is terminal but no result is stored: none submitted '
124
+ + 'yet, or no callback subscription covers its ending event.',
125
+ }
126
+ }
95
127
 
128
+ // Builder keeps a result (which can carry proofs) only until its
129
+ // callback is delivered, or 7 days undelivered.
130
+ const items = signed.flatMap((item) => (
131
+ item.payload === null ? [] : [{ ...item, payload: item.payload }]
132
+ ))
96
133
  if(!items.length) {
97
134
  return {
98
135
  reclaimSessionId: args.reclaimSessionId,
99
136
  status,
100
137
  ready: false,
101
- note: 'Session is terminal but no result has been submitted yet.',
138
+ delivered: signed.some((item) => (
139
+ item.deliveryStatus === 'delivered'
140
+ )),
141
+ note: 'Builder no longer holds this result: it was delivered to '
142
+ + 'the callback (read it there), or went undelivered for 7 days. '
143
+ + 'See list_verification_deliveries.',
102
144
  }
103
145
  }
104
146