@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.
- package/lib/mcp/api/operations.gen.d.ts +9 -0
- package/lib/mcp/api/operations.gen.js +89 -12
- package/lib/mcp/api/tools.d.ts +10 -2
- package/lib/mcp/api/tools.js +42 -4
- package/lib/mcp/start.js +6 -1
- package/lib/mcp/tools/verification/result.js +49 -12
- package/package.json +2 -2
- package/skill/SKILL.builder.md +2 -1
- package/skill/builder-mode.md +24 -6
- package/skill/verification-client-integration.md +111 -14
- package/src/mcp/api/operations.gen.ts +89 -12
- package/src/mcp/api/tools.ts +57 -5
- package/src/mcp/start.ts +6 -1
- package/src/mcp/tools/verification/result.ts +55 -13
|
@@ -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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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`,
|
|
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:
|
|
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`.
|
|
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.
|
|
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`.
|
|
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
|
|
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": "
|
|
2167
|
-
"
|
|
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
|
|
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.",
|
package/src/mcp/api/tools.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { unauthorized } from '@hapi/boom'
|
|
1
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
-
import
|
|
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
|
-
|
|
26
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
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
|
-
+ '
|
|
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
|
|
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
|
-
|
|
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
|
|