@reclaimprotocol/agent 0.1.11 → 0.1.13
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/tools/agent/shared.js +30 -1
- package/lib/mcp/tools/authenticate/decrypt-key.js +1 -1
- package/lib/old/client.d.ts +31 -0
- package/lib/old/client.js +33 -0
- package/lib/old/tools/build.js +12 -0
- package/lib/old/tools/check-app-status.d.ts +8 -0
- package/lib/old/tools/check-app-status.js +27 -0
- package/lib/old/tools/issue-app-credentials.d.ts +11 -0
- package/lib/old/tools/issue-app-credentials.js +35 -0
- package/lib/old/tools/js-sdk-integration-guide.d.ts +2 -0
- package/lib/old/tools/js-sdk-integration-guide.js +27 -0
- package/lib/old/tools/link-app.d.ts +10 -0
- package/lib/old/tools/link-app.js +73 -0
- package/lib/old/tools/publish.js +26 -11
- package/lib/old/tools/scaffold-demo-server.d.ts +2 -0
- package/lib/old/tools/scaffold-demo-server.js +94 -0
- package/lib/old/tools/verify-sdk-setup.d.ts +9 -0
- package/lib/old/tools/verify-sdk-setup.js +132 -0
- package/lib/proof/attestor.js +2 -2
- package/lib/provider/draft.js +51 -1
- package/package.json +3 -1
- package/samples/demo-server/.env.example +12 -0
- package/samples/demo-server/README.md +24 -0
- package/samples/demo-server/package.json +13 -0
- package/samples/demo-server/server.js +156 -0
- package/skill/js-sdk-integration.md +119 -0
- package/src/cdp/launcher.ts +33 -33
- package/src/mcp/api/tools.ts +3 -4
- package/src/mcp/remap_errors.ts +1 -1
- package/src/mcp/tools/agent/shared.ts +35 -1
- package/src/mcp/tools/authenticate/authenticate.ts +1 -3
- package/src/mcp/tools/authenticate/decrypt-key.ts +1 -4
- package/src/old/client.ts +60 -0
- package/src/old/tools/build.ts +12 -0
- package/src/old/tools/check-app-status.ts +33 -0
- package/src/old/tools/issue-app-credentials.ts +44 -0
- package/src/old/tools/js-sdk-integration-guide.ts +38 -0
- package/src/old/tools/link-app.ts +93 -0
- package/src/old/tools/publish.ts +26 -12
- package/src/old/tools/scaffold-demo-server.ts +120 -0
- package/src/old/tools/verify-sdk-setup.ts +162 -0
- package/src/proof/attestor.ts +7 -3
- package/src/provider/draft.ts +59 -1
package/src/mcp/remap_errors.ts
CHANGED
|
@@ -14,10 +14,42 @@ import {
|
|
|
14
14
|
} from '../../../authoring/index.ts'
|
|
15
15
|
import { runAttestorProof } from '../../../proof/attestor.ts'
|
|
16
16
|
import { replayProvider } from '../../../proof/replay.ts'
|
|
17
|
+
import type { OprfMode, ReclaimProvider } from '../../../provider/schema.ts'
|
|
17
18
|
import type { RegisteredTool } from '../../server.ts'
|
|
18
19
|
import type { CaptureEntry, CaptureStore } from './capture.ts'
|
|
19
20
|
import { shapeRichProofResult } from './proof.ts'
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* OPRF mode this LOCAL (non-TEE) attestor can actually run. Recipes carry
|
|
24
|
+
* `oprf-mpc` — the canonical/publish mode a TEE expects — but the local dev
|
|
25
|
+
* attestor can't do MPC. gnark `oprf` (Linux/Mac, needs ZK circuits) is the
|
|
26
|
+
* strongest it supports; Windows falls back to server-side `oprf-raw` (no
|
|
27
|
+
* circuits). Proving with the wrong mode fails, which is what pushed the agent
|
|
28
|
+
* to hand-craft a plaintext workaround.
|
|
29
|
+
*/
|
|
30
|
+
const LOCAL_OPRF_MODE: OprfMode =
|
|
31
|
+
process.platform === 'win32' ? 'oprf-raw' : 'oprf'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Clone `provider` with every `oprf-mpc` redaction remapped to the mode this
|
|
35
|
+
* local attestor can run, so the PROOF validates locally. The draft itself is
|
|
36
|
+
* untouched — it keeps `oprf-mpc`, so the PUBLISHED recipe is always the
|
|
37
|
+
* TEE-canonical mode regardless of where it was proved.
|
|
38
|
+
*/
|
|
39
|
+
function withLocalOprfMode(provider: ReclaimProvider): ReclaimProvider {
|
|
40
|
+
const reds = provider.responseRedactions
|
|
41
|
+
if(!reds?.some((r) => r.hash === 'oprf-mpc')) {
|
|
42
|
+
return provider
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...provider,
|
|
47
|
+
responseRedactions: reds.map((r) => {
|
|
48
|
+
return r.hash === 'oprf-mpc' ? { ...r, hash: LOCAL_OPRF_MODE } : r
|
|
49
|
+
}),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
21
53
|
/** The local backend for one capture: drive its tab over CDP, read its
|
|
22
54
|
* captured requests, prove via the in-process attestor, replay by re-fetch. */
|
|
23
55
|
function localBackend(entry: CaptureEntry): AuthoringBackend {
|
|
@@ -64,8 +96,10 @@ function localBackend(entry: CaptureEntry): AuthoringBackend {
|
|
|
64
96
|
}
|
|
65
97
|
},
|
|
66
98
|
prove: async(draft, opts) => {
|
|
99
|
+
// Prove with the locally-runnable OPRF mode; the draft (→ published
|
|
100
|
+
// recipe) keeps oprf-mpc.
|
|
67
101
|
const result = await runAttestorProof(
|
|
68
|
-
draft.provider,
|
|
102
|
+
withLocalOprfMode(draft.provider),
|
|
69
103
|
draft.secrets,
|
|
70
104
|
{
|
|
71
105
|
...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client'
|
|
2
|
-
import type {
|
|
3
|
-
CredentialAttachSession
|
|
4
|
-
} from '@reclaimprotocol/client/openapi'
|
|
2
|
+
import type { CredentialAttachSession } from '@reclaimprotocol/client/openapi'
|
|
5
3
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
6
4
|
import { writeSession } from './session-cache.ts'
|
|
7
5
|
|
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
* The hex is loaded via the client's `loadDecryptionKey` and passed straight
|
|
12
12
|
* through as `ReclaimConfig.credential` / to `decryptCallback`.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
15
|
-
type DecryptionKey,
|
|
16
|
-
loadDecryptionKey,
|
|
17
|
-
} from '@reclaimprotocol/client'
|
|
14
|
+
import { type DecryptionKey, loadDecryptionKey } from '@reclaimprotocol/client'
|
|
18
15
|
import { existsSync, readFileSync } from 'node:fs'
|
|
19
16
|
import { join } from 'node:path'
|
|
20
17
|
import { findProjectRoot } from '../../../paths.ts'
|
package/src/old/client.ts
CHANGED
|
@@ -74,6 +74,22 @@ export interface MyProvidersQuery {
|
|
|
74
74
|
searchQuery?: string
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** `POST /api/applications/issue-credentials` response — a brand-new,
|
|
78
|
+
* unassociated (sandboxed) app. `appSecret` is the raw eth private key;
|
|
79
|
+
* it's returned once and never stored server-side in recoverable form. */
|
|
80
|
+
export interface IssuedAppCredentials {
|
|
81
|
+
appId: string
|
|
82
|
+
appSecret: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `GET /api/applications/link/nonce` response — a one-time, 5-minute-TTL
|
|
86
|
+
* challenge the caller signs with the app's private key to prove ownership
|
|
87
|
+
* in `linkAppToAccount`. */
|
|
88
|
+
export interface LinkNonce {
|
|
89
|
+
nonce: string
|
|
90
|
+
expiresInSeconds: number
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
interface RequestOpts {
|
|
78
94
|
query?: Record<string, string | number | undefined>
|
|
79
95
|
body?: unknown
|
|
@@ -187,6 +203,50 @@ export class ReclaimOldClient {
|
|
|
187
203
|
return json
|
|
188
204
|
}
|
|
189
205
|
|
|
206
|
+
/** Mint a brand-new, unassociated (sandboxed) app — no auth required. Link
|
|
207
|
+
* it to an account afterwards with `getLinkNonce` + `linkAppToAccount`, or
|
|
208
|
+
* use it as-is within the sandbox session limit. */
|
|
209
|
+
async issueCredentials(): Promise<IssuedAppCredentials> {
|
|
210
|
+
const res = await this.#request('POST', '/applications/issue-credentials')
|
|
211
|
+
return (res as { data: IssuedAppCredentials }).data
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** One-time, 5-minute challenge for `linkAppToAccount`. Requires auth —
|
|
215
|
+
* the nonce is scoped to the authenticated user's uid. */
|
|
216
|
+
async getLinkNonce(): Promise<LinkNonce> {
|
|
217
|
+
const res = await this.#request('GET', '/applications/link/nonce', {
|
|
218
|
+
auth: true,
|
|
219
|
+
})
|
|
220
|
+
return (res as { data: LinkNonce }).data
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Link an unassociated (`issueCredentials`-minted) app to the
|
|
224
|
+
* authenticated account. `signature` must be an EIP-191 signature, made
|
|
225
|
+
* with the app's private key, over the exact string
|
|
226
|
+
* `` `Link ${appId} to account ${userId} | nonce: ${nonce}` `` — see
|
|
227
|
+
* `buildLinkMessage` in the backend's `application.controller.ts`. Fails
|
|
228
|
+
* with `APP_ALREADY_LINKED` (400) if the app already belongs to someone. */
|
|
229
|
+
linkAppToAccount(
|
|
230
|
+
appId: string,
|
|
231
|
+
signature: string,
|
|
232
|
+
nonce: string,
|
|
233
|
+
): Promise<unknown> {
|
|
234
|
+
return this.#request('POST', '/applications/link', {
|
|
235
|
+
body: { appId, signature, nonce },
|
|
236
|
+
auth: true,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Public, unauthenticated app status — `isLinked`, sandbox/quota info.
|
|
241
|
+
* Works for any appId, owned or not. */
|
|
242
|
+
async getApplicationStatus(appId: string): Promise<unknown> {
|
|
243
|
+
const res = await this.#request(
|
|
244
|
+
'GET',
|
|
245
|
+
`/applications/status/${encodeURIComponent(appId)}`,
|
|
246
|
+
)
|
|
247
|
+
return (res as { data: unknown }).data
|
|
248
|
+
}
|
|
249
|
+
|
|
190
250
|
getMyProviders(query: MyProvidersQuery = {}): Promise<unknown> {
|
|
191
251
|
// Every one of these query params is REQUIRED by the backend
|
|
192
252
|
// validator (missing/invalid → 400), so default them all here.
|
package/src/old/tools/build.ts
CHANGED
|
@@ -2,11 +2,17 @@ import type { RegisteredTool } from '../../mcp/server.ts'
|
|
|
2
2
|
import type { ReclaimOldClient } from '../client.ts'
|
|
3
3
|
import type { ReclaimSdkClient } from '../sdk-client.ts'
|
|
4
4
|
import { authenticateTool } from './authenticate.ts'
|
|
5
|
+
import { checkAppStatusTool } from './check-app-status.ts'
|
|
5
6
|
import { getProviderInfoTool } from './get-provider-info.ts'
|
|
7
|
+
import { issueAppCredentialsTool } from './issue-app-credentials.ts'
|
|
8
|
+
import { getJsSdkIntegrationGuideTool } from './js-sdk-integration-guide.ts'
|
|
9
|
+
import { linkAppTool } from './link-app.ts'
|
|
6
10
|
import { getMeProvidersTool } from './me-providers.ts'
|
|
7
11
|
import { createVersionFromCaptureTool } from './publish.ts'
|
|
12
|
+
import { scaffoldDemoServerTool } from './scaffold-demo-server.ts'
|
|
8
13
|
import { sessionAnalyticsLogsTool } from './session-analytics-logs.ts'
|
|
9
14
|
import { updateProviderMetadataTool } from './update-provider-metadata.ts'
|
|
15
|
+
import { verifySdkSetupTool } from './verify-sdk-setup.ts'
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* The backend-facing tools for OLD-devtools mode. Mirrors
|
|
@@ -29,5 +35,11 @@ export function buildOldBackendTools(
|
|
|
29
35
|
getProviderInfoTool(sdkClient),
|
|
30
36
|
updateProviderMetadataTool(client),
|
|
31
37
|
sessionAnalyticsLogsTool(client),
|
|
38
|
+
issueAppCredentialsTool(client),
|
|
39
|
+
linkAppTool(client),
|
|
40
|
+
checkAppStatusTool(client),
|
|
41
|
+
getJsSdkIntegrationGuideTool(),
|
|
42
|
+
scaffoldDemoServerTool(),
|
|
43
|
+
verifySdkSetupTool(client),
|
|
32
44
|
]
|
|
33
45
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
2
|
+
import type { ReclaimOldClient } from '../client.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Public, unauthenticated app status lookup — works for any `appId`, owned
|
|
6
|
+
* or not, linked or sandboxed. Mirrors the backend's
|
|
7
|
+
* `GET /api/applications/status/:appId`.
|
|
8
|
+
*/
|
|
9
|
+
export function checkAppStatusTool(client: ReclaimOldClient): RegisteredTool {
|
|
10
|
+
return defineTool<{ appId: string }>(
|
|
11
|
+
{
|
|
12
|
+
name: 'check_app_status',
|
|
13
|
+
description:
|
|
14
|
+
'Check a Reclaim app\'s link/sandbox status by appId — no ' +
|
|
15
|
+
'authentication required. Returns whether the app is linked to a ' +
|
|
16
|
+
'dashboard account (`isLinked`), and for linked apps, remaining ' +
|
|
17
|
+
'session quota. A 404 means the appId has never been used yet — ' +
|
|
18
|
+
'it is created lazily on the first ReclaimProofRequest.init() call ' +
|
|
19
|
+
'or via issue_app_credentials.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
appId: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'The eth-address appId to check.',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['appId'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
async({ appId }) => client.getApplicationStatus(appId),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
2
|
+
import type { ReclaimOldClient } from '../client.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mint a brand-new eth-keypair APP (as opposed to authenticating as a
|
|
6
|
+
* dashboard user). `appId` is the eth address, `appSecret` the raw private
|
|
7
|
+
* key — this is the pair `@reclaimprotocol/js-sdk`'s `ReclaimProofRequest.init`
|
|
8
|
+
* expects. No auth required: the app starts unassociated (`creatorUid: null`)
|
|
9
|
+
* and sandboxed (`sandboxLimit: 100` sessions). Use `link_app_to_account` to
|
|
10
|
+
* attach it to your dashboard account, or `check_app_status` to inspect it.
|
|
11
|
+
*/
|
|
12
|
+
export function issueAppCredentialsTool(
|
|
13
|
+
client: ReclaimOldClient,
|
|
14
|
+
): RegisteredTool {
|
|
15
|
+
return defineTool<Record<string, never>>(
|
|
16
|
+
{
|
|
17
|
+
name: 'issue_app_credentials',
|
|
18
|
+
description:
|
|
19
|
+
'Mint a new Reclaim APP (eth keypair) on the old devtools backend — ' +
|
|
20
|
+
'no authentication required. Returns `{ appId, appSecret }`: ' +
|
|
21
|
+
'`appId` is the eth address, `appSecret` the raw private key. This ' +
|
|
22
|
+
'is the appId/appSecret pair for @reclaimprotocol/js-sdk ' +
|
|
23
|
+
'(`ReclaimProofRequest.init(appId, appSecret, providerId)`). The app ' +
|
|
24
|
+
'starts UNLINKED and sandboxed (100 sessions) — call ' +
|
|
25
|
+
'link_app_to_account to attach it to your dashboard account and ' +
|
|
26
|
+
'check_app_status to check its state. Store `appSecret` like a ' +
|
|
27
|
+
'password (e.g. in a gitignored .env as RECLAIM_APP_SECRET) — it ' +
|
|
28
|
+
'is returned once and cannot be recovered from the server.',
|
|
29
|
+
inputSchema: { type: 'object', properties: {} },
|
|
30
|
+
},
|
|
31
|
+
async() => {
|
|
32
|
+
const { appId, appSecret } = await client.issueCredentials()
|
|
33
|
+
return {
|
|
34
|
+
appId,
|
|
35
|
+
appSecret,
|
|
36
|
+
note:
|
|
37
|
+
'Unlinked, sandboxed app (100 session limit). Set RECLAIM_APP_ID ' +
|
|
38
|
+
'and RECLAIM_APP_SECRET in your .env (gitignored). Run ' +
|
|
39
|
+
'link_app_to_account to attach it to your dashboard account and ' +
|
|
40
|
+
'lift the sandbox limit.',
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Verified against `@reclaimprotocol/js-sdk` v5.8.1 source
|
|
7
|
+
* (`reclaim-js-sdk/src/Reclaim.ts`) — NOT copied unchecked from the archived
|
|
8
|
+
* `reclaim-mcp-server`'s guide, which predates several of these signatures.
|
|
9
|
+
* The prose lives in `skill/js-sdk-integration.md` so it can be edited as
|
|
10
|
+
* markdown without touching code — same pattern as `provider/injection-doc.ts`.
|
|
11
|
+
* The path resolves the same from `src/old/tools` (dev) and `lib/old/tools`
|
|
12
|
+
* (published): both are three levels below the package root.
|
|
13
|
+
*/
|
|
14
|
+
const GUIDE = readFileSync(
|
|
15
|
+
fileURLToPath(
|
|
16
|
+
new URL('../../../skill/js-sdk-integration.md', import.meta.url),
|
|
17
|
+
),
|
|
18
|
+
'utf8',
|
|
19
|
+
).trim()
|
|
20
|
+
|
|
21
|
+
export function getJsSdkIntegrationGuideTool(): RegisteredTool {
|
|
22
|
+
return defineTool<Record<string, never>>(
|
|
23
|
+
{
|
|
24
|
+
name: 'get_js_sdk_integration_guide',
|
|
25
|
+
description:
|
|
26
|
+
'Return a verified, copy-pasteable guide for integrating ' +
|
|
27
|
+
'@reclaimprotocol/js-sdk (backend proof request + callback ' +
|
|
28
|
+
'verification), checked against the current SDK source rather than ' +
|
|
29
|
+
'assumed from older docs. Covers testing locally without deploying: ' +
|
|
30
|
+
'the built-in "example" providerId for a quick smoke test, and using ' +
|
|
31
|
+
'ngrok to get a callback URL Reclaim\'s servers can actually reach. ' +
|
|
32
|
+
'Use scaffold_reclaim_demo_server to generate a runnable local demo ' +
|
|
33
|
+
'instead of copy-pasting by hand.',
|
|
34
|
+
inputSchema: { type: 'object', properties: {} },
|
|
35
|
+
},
|
|
36
|
+
async() => ({ guide: GUIDE }),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { signEip191 } from '@reclaimprotocol/client'
|
|
2
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
3
|
+
import type { ReclaimOldClient } from '../client.ts'
|
|
4
|
+
|
|
5
|
+
/** Byte-for-byte the same format as the backend's `buildLinkMessage` in
|
|
6
|
+
* `application.controller.ts` — MUST stay in lockstep with it. */
|
|
7
|
+
function buildLinkMessage(
|
|
8
|
+
appId: string,
|
|
9
|
+
userId: string,
|
|
10
|
+
nonce: string,
|
|
11
|
+
): string {
|
|
12
|
+
return `Link ${appId} to account ${userId} | nonce: ${nonce}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The uid the backend binds into the signed message — `req.user.uid` there.
|
|
16
|
+
* For a bearer identity this comes back from `POST /users/login`; for an eth
|
|
17
|
+
* identity, `OldIdentity.uid` (already `eth:<address>`) IS that uid. */
|
|
18
|
+
async function resolveUserId(client: ReclaimOldClient): Promise<string> {
|
|
19
|
+
const identity = client.getIdentity()
|
|
20
|
+
if(!identity) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'Not authenticated against old devtools. Call reclaim_authenticate ' +
|
|
23
|
+
'first.',
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if(identity.kind === 'eth') {
|
|
28
|
+
return identity.uid
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const res = await client.loginWithToken() as { user?: { uid?: string } }
|
|
32
|
+
const uid = res?.user?.uid
|
|
33
|
+
if(!uid) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'Could not resolve your account uid from POST /users/login — ' +
|
|
36
|
+
'unexpected response shape.',
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return uid
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Link an unassociated app (minted via `issue_app_credentials`, or any
|
|
45
|
+
* eth-keypair app you already hold the private key for) to the currently
|
|
46
|
+
* authenticated dashboard account. Proves ownership by signing a one-time
|
|
47
|
+
* challenge with the app's private key — the private key itself is only
|
|
48
|
+
* ever used locally to sign, never sent to the server.
|
|
49
|
+
*/
|
|
50
|
+
export function linkAppTool(client: ReclaimOldClient): RegisteredTool {
|
|
51
|
+
return defineTool<{ appId: string, appSecret: string }>(
|
|
52
|
+
{
|
|
53
|
+
name: 'link_app_to_account',
|
|
54
|
+
description:
|
|
55
|
+
'Link an eth-keypair app to your authenticated dashboard account — ' +
|
|
56
|
+
'use this to keep using an appId/appSecret you already have ' +
|
|
57
|
+
'(instead of minting a fresh one via issue_app_credentials). ' +
|
|
58
|
+
'Requires reclaim_authenticate first. Fetches a one-time nonce, ' +
|
|
59
|
+
'signs `Link <appId> to account <userId> | nonce: <nonce>` locally ' +
|
|
60
|
+
'with `appSecret` (EIP-191, never transmitted), and submits the ' +
|
|
61
|
+
'signature. Fails with APP_ALREADY_LINKED if the app is already ' +
|
|
62
|
+
'linked to a (possibly different) account.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
appId: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'The eth-address appId to link.',
|
|
69
|
+
},
|
|
70
|
+
appSecret: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description:
|
|
73
|
+
'The app\'s raw eth private key — used only locally to sign ' +
|
|
74
|
+
'the link challenge, never sent to the server.',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ['appId', 'appSecret'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
async({ appId, appSecret }) => {
|
|
81
|
+
const userId = await resolveUserId(client)
|
|
82
|
+
const { nonce } = await client.getLinkNonce()
|
|
83
|
+
const message = buildLinkMessage(appId, userId, nonce)
|
|
84
|
+
const signature = signEip191(message, appSecret)
|
|
85
|
+
await client.linkAppToAccount(appId, signature, nonce)
|
|
86
|
+
return {
|
|
87
|
+
linked: true,
|
|
88
|
+
appId,
|
|
89
|
+
note: 'App linked to your account. Run check_app_status to confirm.',
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
}
|
package/src/old/tools/publish.ts
CHANGED
|
@@ -147,9 +147,16 @@ export function createVersionFromCaptureTool(
|
|
|
147
147
|
description:
|
|
148
148
|
'Multiple proven drafts → one multi-request version (each ' +
|
|
149
149
|
'becomes a parallel `requestData` entry, all verified ' +
|
|
150
|
-
'together). Each item has the same shape as `provider
|
|
151
|
-
'
|
|
152
|
-
|
|
150
|
+
'together). Each item has the same shape as `provider` (if ' +
|
|
151
|
+
'both are passed, they are combined). May be omitted (or ' +
|
|
152
|
+
'empty) together with `provider` ONLY when adding a version ' +
|
|
153
|
+
'to an EXISTING provider (`providerId` set) — publishes with ' +
|
|
154
|
+
'zero `requestData` entries, for a pure customInjection/' +
|
|
155
|
+
'HAWKEYE provider that relies entirely on ' +
|
|
156
|
+
'`allowedInjectedRequestData` for validation instead of a ' +
|
|
157
|
+
'static requestData stub. Registering a brand-new provider ' +
|
|
158
|
+
'always needs at least one draft (its name comes from the ' +
|
|
159
|
+
'first one).',
|
|
153
160
|
items: {
|
|
154
161
|
type: 'object',
|
|
155
162
|
properties: {
|
|
@@ -299,20 +306,27 @@ export function createVersionFromCaptureTool(
|
|
|
299
306
|
},
|
|
300
307
|
},
|
|
301
308
|
async(args) => {
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
// `provider` / `providers` are just two spellings of the same input —
|
|
310
|
+
// fold them into one array. Zero drafts is allowed ONLY when adding a
|
|
311
|
+
// version to an EXISTING provider (`providerId` set): that publishes
|
|
312
|
+
// with zero `requestData` entries, for a pure customInjection/HAWKEYE
|
|
313
|
+
// provider whose claims are validated entirely via
|
|
314
|
+
// `allowedInjectedRequestData`. Registering a brand-new provider still
|
|
315
|
+
// needs at least one draft — its name comes from the first one.
|
|
316
|
+
const providers = [
|
|
317
|
+
...(args.provider ? [args.provider] : []),
|
|
318
|
+
...(args.providers ?? []),
|
|
319
|
+
]
|
|
320
|
+
if(!providers.length && !args.providerId) {
|
|
307
321
|
throw new Boom(
|
|
308
|
-
'Pass
|
|
309
|
-
'(
|
|
322
|
+
'Pass at least one of `provider`/`providers` when registering a ' +
|
|
323
|
+
'new provider (its name comes from the first draft). Omitting ' +
|
|
324
|
+
'both is only allowed when adding a version to an EXISTING ' +
|
|
325
|
+
'provider via `providerId`.',
|
|
310
326
|
{ statusCode: 400 },
|
|
311
327
|
)
|
|
312
328
|
}
|
|
313
329
|
|
|
314
|
-
const providers = hasMany ? args.providers! : [args.provider!]
|
|
315
|
-
|
|
316
330
|
const opts: OldProviderOpts = {
|
|
317
331
|
initialUrl: args.initialUrl,
|
|
318
332
|
description: args.description,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { isAbsolute, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
5
|
+
import { findProjectRoot } from '../../paths.ts'
|
|
6
|
+
|
|
7
|
+
/** The demo's source files live in `samples/demo-server/` so they can be
|
|
8
|
+
* edited (and even run) as plain files without touching code — same pattern
|
|
9
|
+
* as `skill/*.md`. The path resolves the same from `src/old/tools` (dev) and
|
|
10
|
+
* `lib/old/tools` (published): both are three levels below the package root.
|
|
11
|
+
*
|
|
12
|
+
* `server.js` there is corrected against `@reclaimprotocol/js-sdk` v5.8.1
|
|
13
|
+
* (`Reclaim.ts`): awaits `getRequestUrl()`, uses `setContext` (not deprecated
|
|
14
|
+
* `addContext`), JSON-parses the raw webhook body before `verifyProof`,
|
|
15
|
+
* passes the required `{ providerId, providerVersion }` config, and reads
|
|
16
|
+
* `result.isVerified` (not `is_verified`). `providerVersion` is left unpinned
|
|
17
|
+
* by default — the built-in "example" providerId isn't versioned, so pinning
|
|
18
|
+
* one is only meaningful once you swap in your own provider. */
|
|
19
|
+
const SAMPLE_DIR = fileURLToPath(
|
|
20
|
+
new URL('../../../samples/demo-server', import.meta.url),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
function readSample(name: string): string {
|
|
24
|
+
return readFileSync(join(SAMPLE_DIR, name), 'utf8')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `server.js` with its `__PROVIDER_ID__` / `__PROVIDER_VERSION__` tokens
|
|
28
|
+
* substituted with JS literals. */
|
|
29
|
+
function serverJs(
|
|
30
|
+
providerId: string,
|
|
31
|
+
providerVersion: string | undefined,
|
|
32
|
+
): string {
|
|
33
|
+
return readSample('server.js')
|
|
34
|
+
.replace('__PROVIDER_ID__', JSON.stringify(providerId))
|
|
35
|
+
.replace(
|
|
36
|
+
'__PROVIDER_VERSION__',
|
|
37
|
+
providerVersion ? JSON.stringify(providerVersion) : 'undefined',
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Scaffold a small, runnable Express demo that exercises the full
|
|
43
|
+
* @reclaimprotocol/js-sdk flow end-to-end: request a proof (backend), open it
|
|
44
|
+
* (frontend), and verify the async webhook callback (backend). Corrected
|
|
45
|
+
* against the CURRENT SDK API (see js-sdk-integration-guide) rather than
|
|
46
|
+
* older examples that predate several signature changes.
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_PROVIDER_ID = 'example'
|
|
49
|
+
|
|
50
|
+
export function scaffoldDemoServerTool(): RegisteredTool {
|
|
51
|
+
return defineTool<{
|
|
52
|
+
targetDir: string
|
|
53
|
+
providerId?: string
|
|
54
|
+
providerVersion?: string
|
|
55
|
+
}>(
|
|
56
|
+
{
|
|
57
|
+
name: 'scaffold_reclaim_demo_server',
|
|
58
|
+
description:
|
|
59
|
+
'Write a small runnable Express demo (server.js, package.json, ' +
|
|
60
|
+
'.env.example, README.md) that requests a Reclaim proof and ' +
|
|
61
|
+
'verifies the async webhook callback, using the CURRENT ' +
|
|
62
|
+
'@reclaimprotocol/js-sdk API. Defaults `providerId` to the built-in ' +
|
|
63
|
+
'"example" smoke-test provider so you can confirm the whole loop ' +
|
|
64
|
+
'without authoring one first. The webhook callback needs a URL ' +
|
|
65
|
+
'Reclaim\'s servers can reach — localhost never works — via EITHER ' +
|
|
66
|
+
'a tunnel (ngrok or similar) OR quickly deploying this demo to a ' +
|
|
67
|
+
'dev/staging server the user already has; ASK THE USER which they\'d ' +
|
|
68
|
+
'rather use before assuming ngrok, since either sets `PUBLIC_URL` in ' +
|
|
69
|
+
'.env (the generated README covers both). Use ' +
|
|
70
|
+
'get_js_sdk_integration_guide for the annotated write-up of the ' +
|
|
71
|
+
'same flow.',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
targetDir: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description:
|
|
78
|
+
'Directory to write the demo into (created if missing). ' +
|
|
79
|
+
'Relative paths resolve against the project root.',
|
|
80
|
+
},
|
|
81
|
+
providerId: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description:
|
|
84
|
+
'Provider id the demo requests a proof for (default ' +
|
|
85
|
+
'"example", a built-in smoke-test provider — good for ' +
|
|
86
|
+
'confirming the flow works before wiring in a real one).',
|
|
87
|
+
},
|
|
88
|
+
providerVersion: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description:
|
|
91
|
+
'Provider version to pin (default: unpinned — "example" ' +
|
|
92
|
+
'isn\'t versioned, so only set this once you swap in your ' +
|
|
93
|
+
'own provider).',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ['targetDir'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
async({ targetDir, providerId, providerVersion }) => {
|
|
100
|
+
const dir = isAbsolute(targetDir)
|
|
101
|
+
? targetDir
|
|
102
|
+
: join(findProjectRoot(process.cwd()), targetDir)
|
|
103
|
+
mkdirSync(dir, { recursive: true })
|
|
104
|
+
|
|
105
|
+
const id = providerId ?? DEFAULT_PROVIDER_ID
|
|
106
|
+
writeFileSync(join(dir, 'server.js'), serverJs(id, providerVersion))
|
|
107
|
+
writeFileSync(join(dir, 'package.json'), readSample('package.json'))
|
|
108
|
+
writeFileSync(join(dir, '.env.example'), readSample('.env.example'))
|
|
109
|
+
writeFileSync(join(dir, 'README.md'), readSample('README.md'))
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
dir,
|
|
113
|
+
providerId: id,
|
|
114
|
+
files: ['server.js', 'package.json', '.env.example', 'README.md'],
|
|
115
|
+
next: 'cd into the directory, npm install, copy .env.example to .env ' +
|
|
116
|
+
'and fill it in (including PUBLIC_URL from ngrok), then npm start.',
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
}
|