@reclaimprotocol/agent 0.1.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +287 -0
  2. package/lib/cdp/capture.d.ts +22 -0
  3. package/lib/cdp/capture.js +159 -0
  4. package/lib/cdp/client.d.ts +9 -0
  5. package/lib/cdp/client.js +11 -0
  6. package/lib/cdp/eviction.d.ts +16 -0
  7. package/lib/cdp/eviction.js +38 -0
  8. package/lib/cdp/launcher.d.ts +14 -0
  9. package/lib/cdp/launcher.js +102 -0
  10. package/lib/cdp/tabs.d.ts +6 -0
  11. package/lib/cdp/tabs.js +10 -0
  12. package/lib/cli/args.d.ts +14 -0
  13. package/lib/cli/args.js +19 -0
  14. package/lib/cli/commands.d.ts +1 -0
  15. package/lib/cli/commands.js +19 -0
  16. package/lib/cli.d.ts +1 -0
  17. package/lib/cli.js +7 -0
  18. package/lib/config/store.d.ts +45 -0
  19. package/lib/config/store.js +46 -0
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.js +7 -0
  22. package/lib/logger.d.ts +2 -0
  23. package/lib/logger.js +7 -0
  24. package/lib/mcp/api/operations.gen.d.ts +152 -0
  25. package/lib/mcp/api/operations.gen.js +2300 -0
  26. package/lib/mcp/api/tools.d.ts +3 -0
  27. package/lib/mcp/api/tools.js +36 -0
  28. package/lib/mcp/remap_errors.d.ts +2 -0
  29. package/lib/mcp/remap_errors.js +27 -0
  30. package/lib/mcp/server.d.ts +38 -0
  31. package/lib/mcp/server.js +76 -0
  32. package/lib/mcp/start.d.ts +1 -0
  33. package/lib/mcp/start.js +63 -0
  34. package/lib/mcp/tools/agent/analyze.d.ts +3 -0
  35. package/lib/mcp/tools/agent/analyze.js +26 -0
  36. package/lib/mcp/tools/agent/attach.d.ts +11 -0
  37. package/lib/mcp/tools/agent/attach.js +72 -0
  38. package/lib/mcp/tools/agent/build.d.ts +33 -0
  39. package/lib/mcp/tools/agent/build.js +74 -0
  40. package/lib/mcp/tools/agent/capture.d.ts +10 -0
  41. package/lib/mcp/tools/agent/capture.js +142 -0
  42. package/lib/mcp/tools/agent/eval.d.ts +5 -0
  43. package/lib/mcp/tools/agent/eval.js +127 -0
  44. package/lib/mcp/tools/agent/find.d.ts +3 -0
  45. package/lib/mcp/tools/agent/find.js +60 -0
  46. package/lib/mcp/tools/agent/proof.d.ts +3 -0
  47. package/lib/mcp/tools/agent/proof.js +214 -0
  48. package/lib/mcp/tools/agent/propose.d.ts +9 -0
  49. package/lib/mcp/tools/agent/propose.js +75 -0
  50. package/lib/mcp/tools/agent/publish.d.ts +3 -0
  51. package/lib/mcp/tools/agent/publish.js +122 -0
  52. package/lib/mcp/tools/agent/replay.d.ts +3 -0
  53. package/lib/mcp/tools/agent/replay.js +46 -0
  54. package/lib/mcp/tools/agent/session.d.ts +10 -0
  55. package/lib/mcp/tools/agent/session.js +29 -0
  56. package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
  57. package/lib/mcp/tools/credentials/authenticate.js +108 -0
  58. package/lib/mcp/tools/credentials/build.d.ts +16 -0
  59. package/lib/mcp/tools/credentials/build.js +33 -0
  60. package/lib/mcp/tools/credentials/cache.d.ts +70 -0
  61. package/lib/mcp/tools/credentials/cache.js +114 -0
  62. package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
  63. package/lib/mcp/tools/credentials/env-file.js +71 -0
  64. package/lib/mcp/tools/credentials/import.d.ts +2 -0
  65. package/lib/mcp/tools/credentials/import.js +52 -0
  66. package/lib/mcp/tools/credentials/issue.d.ts +2 -0
  67. package/lib/mcp/tools/credentials/issue.js +84 -0
  68. package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
  69. package/lib/mcp/tools/credentials/list-cached.js +55 -0
  70. package/lib/mcp/tools/credentials/register.d.ts +18 -0
  71. package/lib/mcp/tools/credentials/register.js +30 -0
  72. package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
  73. package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
  74. package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
  75. package/lib/mcp/tools/credentials/session-cache.js +19 -0
  76. package/lib/mcp/tools/credentials/status.d.ts +3 -0
  77. package/lib/mcp/tools/credentials/status.js +62 -0
  78. package/lib/mcp-bin.d.ts +1 -0
  79. package/lib/mcp-bin.js +7 -0
  80. package/lib/old/client.d.ts +67 -0
  81. package/lib/old/client.js +146 -0
  82. package/lib/old/flag.d.ts +11 -0
  83. package/lib/old/flag.js +13 -0
  84. package/lib/old/identity.d.ts +22 -0
  85. package/lib/old/identity.js +47 -0
  86. package/lib/old/instructions.d.ts +7 -0
  87. package/lib/old/instructions.js +29 -0
  88. package/lib/old/login-cdp.d.ts +23 -0
  89. package/lib/old/login-cdp.js +125 -0
  90. package/lib/old/to-register.d.ts +47 -0
  91. package/lib/old/to-register.js +133 -0
  92. package/lib/old/tools/authenticate.d.ts +18 -0
  93. package/lib/old/tools/authenticate.js +127 -0
  94. package/lib/old/tools/build.d.ts +9 -0
  95. package/lib/old/tools/build.js +16 -0
  96. package/lib/old/tools/me-providers.d.ts +10 -0
  97. package/lib/old/tools/me-providers.js +32 -0
  98. package/lib/old/tools/publish.d.ts +16 -0
  99. package/lib/old/tools/publish.js +97 -0
  100. package/lib/paths.d.ts +11 -0
  101. package/lib/paths.js +32 -0
  102. package/lib/pkg.d.ts +3 -0
  103. package/lib/pkg.js +13 -0
  104. package/lib/proof/attestor.d.ts +39 -0
  105. package/lib/proof/attestor.js +207 -0
  106. package/lib/proof/replay.d.ts +8 -0
  107. package/lib/proof/replay.js +73 -0
  108. package/lib/provider/constraints.d.ts +2 -0
  109. package/lib/provider/constraints.js +122 -0
  110. package/lib/provider/draft.d.ts +6 -0
  111. package/lib/provider/draft.js +68 -0
  112. package/lib/provider/graphql.d.ts +2 -0
  113. package/lib/provider/graphql.js +35 -0
  114. package/lib/provider/matcher.d.ts +36 -0
  115. package/lib/provider/matcher.js +436 -0
  116. package/lib/provider/normalize.d.ts +4 -0
  117. package/lib/provider/normalize.js +51 -0
  118. package/lib/provider/schema.d.ts +76 -0
  119. package/lib/provider/schema.js +1 -0
  120. package/lib/provider/secrets.d.ts +5 -0
  121. package/lib/provider/secrets.js +22 -0
  122. package/lib/provider/to-version.d.ts +22 -0
  123. package/lib/provider/to-version.js +68 -0
  124. package/lib/types/openapi.d.ts +4 -0
  125. package/lib/types/openapi.js +1 -0
  126. package/package.json +88 -0
  127. package/scripts/download-zk-files.ts +42 -0
  128. package/scripts/prepend-shebang.ts +17 -0
  129. package/skill/SKILL.md +248 -0
  130. package/src/cdp/capture.ts +215 -0
  131. package/src/cdp/client.ts +20 -0
  132. package/src/cdp/eviction.ts +52 -0
  133. package/src/cdp/launcher.ts +133 -0
  134. package/src/cdp/tabs.ts +22 -0
  135. package/src/cli/args.ts +20 -0
  136. package/src/cli/commands.ts +27 -0
  137. package/src/cli.ts +7 -0
  138. package/src/config/store.ts +86 -0
  139. package/src/index.ts +12 -0
  140. package/src/logger.ts +8 -0
  141. package/src/mcp/api/operations.gen.ts +2397 -0
  142. package/src/mcp/api/tools.ts +45 -0
  143. package/src/mcp/remap_errors.ts +35 -0
  144. package/src/mcp/server.ts +119 -0
  145. package/src/mcp/start.ts +78 -0
  146. package/src/mcp/tools/agent/analyze.ts +34 -0
  147. package/src/mcp/tools/agent/attach.ts +95 -0
  148. package/src/mcp/tools/agent/build.ts +99 -0
  149. package/src/mcp/tools/agent/capture.ts +202 -0
  150. package/src/mcp/tools/agent/eval.ts +161 -0
  151. package/src/mcp/tools/agent/find.ts +73 -0
  152. package/src/mcp/tools/agent/proof.ts +272 -0
  153. package/src/mcp/tools/agent/propose.ts +111 -0
  154. package/src/mcp/tools/agent/publish.ts +155 -0
  155. package/src/mcp/tools/agent/replay.ts +58 -0
  156. package/src/mcp/tools/agent/session.ts +46 -0
  157. package/src/mcp/tools/credentials/authenticate.ts +135 -0
  158. package/src/mcp/tools/credentials/build.ts +37 -0
  159. package/src/mcp/tools/credentials/cache.ts +161 -0
  160. package/src/mcp/tools/credentials/env-file.ts +101 -0
  161. package/src/mcp/tools/credentials/import.ts +72 -0
  162. package/src/mcp/tools/credentials/issue.ts +107 -0
  163. package/src/mcp/tools/credentials/list-cached.ts +66 -0
  164. package/src/mcp/tools/credentials/register.ts +49 -0
  165. package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
  166. package/src/mcp/tools/credentials/session-cache.ts +31 -0
  167. package/src/mcp/tools/credentials/status.ts +82 -0
  168. package/src/mcp-bin.ts +7 -0
  169. package/src/old/client.ts +236 -0
  170. package/src/old/flag.ts +13 -0
  171. package/src/old/identity.ts +56 -0
  172. package/src/old/instructions.ts +30 -0
  173. package/src/old/login-cdp.ts +153 -0
  174. package/src/old/to-register.ts +234 -0
  175. package/src/old/tools/authenticate.ts +152 -0
  176. package/src/old/tools/build.ts +21 -0
  177. package/src/old/tools/me-providers.ts +42 -0
  178. package/src/old/tools/publish.ts +135 -0
  179. package/src/paths.ts +37 -0
  180. package/src/pkg.ts +24 -0
  181. package/src/proof/attestor.ts +286 -0
  182. package/src/proof/replay.ts +102 -0
  183. package/src/provider/constraints.ts +150 -0
  184. package/src/provider/draft.ts +105 -0
  185. package/src/provider/graphql.ts +42 -0
  186. package/src/provider/matcher.ts +616 -0
  187. package/src/provider/normalize.ts +65 -0
  188. package/src/provider/schema.ts +92 -0
  189. package/src/provider/secrets.ts +29 -0
  190. package/src/provider/to-version.ts +90 -0
  191. package/src/types/openapi.ts +7 -0
@@ -0,0 +1,82 @@
1
+ import {
2
+ deriveFromPrivateKey,
3
+ type ReclaimClient,
4
+ } from '@reclaimprotocol/client'
5
+ import assert from 'node:assert'
6
+ import { findProjectRoot } from '../../../paths.ts'
7
+ import { defineTool, type RegisteredTool } from '../../server.ts'
8
+ import { suggestCredentialForDir } from './cache.ts'
9
+
10
+ export function statusTool(client: ReclaimClient): RegisteredTool {
11
+ return defineTool<{ address?: string }>(
12
+ {
13
+ name: 'reclaim_status',
14
+ description:
15
+ 'Report the live mode (SANDBOX | PRODUCTION | REVOKED) ' +
16
+ 'of a Reclaim app credential. Pass `address` — or omit it ' +
17
+ 'to derive the address from RECLAIM_PRIVATE_KEY, falling ' +
18
+ 'back to the best cached credential for this directory. ' +
19
+ 'Useful to confirm a credential is linked to an org ' +
20
+ 'with billing before running a production flow.',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ address: {
25
+ type: 'string',
26
+ pattern: '^0x[0-9a-fA-F]{40}$',
27
+ description:
28
+ 'Lowercase ETH address. Defaults to the address ' +
29
+ 'derived from RECLAIM_PRIVATE_KEY, then the cached ' +
30
+ 'credential suggested for the current directory.',
31
+ },
32
+ },
33
+ },
34
+ },
35
+ async(args) => {
36
+ let address = args.address?.toLowerCase()
37
+ if(!address && process.env.RECLAIM_PRIVATE_KEY) {
38
+ address = deriveFromPrivateKey(process.env.RECLAIM_PRIVATE_KEY).address
39
+ }
40
+
41
+ if(!address) {
42
+ address = suggestCredentialForDir(findProjectRoot(process.cwd()))
43
+ ?.credential.address
44
+ }
45
+
46
+ assert(
47
+ address,
48
+ new Error(
49
+ 'No address given, RECLAIM_PRIVATE_KEY is not set, and the ' +
50
+ 'cache is empty. Run issue_credentials or import_credentials ' +
51
+ 'first.',
52
+ )
53
+ )
54
+
55
+ const credResult = await client
56
+ .call('GetCredentialByAddress', { params: { address } })
57
+ const cred = credResult.data
58
+
59
+ // A registered credential always carries an `id`; its absence means
60
+ // the server had no row for this address — a local-only credential.
61
+ const localOnly = !cred.id
62
+
63
+ return {
64
+ address: cred.address,
65
+ mode: cred.mode,
66
+ orgId: cred.orgId,
67
+ label: cred.label,
68
+ revokedAt: cred.revokedAt,
69
+ lastUsedAt: cred.lastUsedAt,
70
+ ...(localOnly ? {
71
+ note:
72
+ 'Local-only credential — not registered with the server. '
73
+ + 'It operates in SANDBOX, which is all you need to publish '
74
+ + 'a verification (only an auth token is required — no '
75
+ + 'credential link). Linking it to an org with billing (via '
76
+ + 'link_credential) is only needed for the upcoming feature '
77
+ + 'that runs verifications for your own app’s users.',
78
+ } : {}),
79
+ }
80
+ },
81
+ )
82
+ }
package/src/mcp-bin.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { startMcpServer } from './mcp/start.ts'
2
+ import { LOGGER } from './logger.ts'
3
+
4
+ startMcpServer().catch((err) => {
5
+ LOGGER.error({ err }, 'mcp server failed')
6
+ process.exitCode = 1
7
+ })
@@ -0,0 +1,236 @@
1
+ import { Boom } from '@hapi/boom'
2
+ import { NAME, VERSION } from '../pkg.ts'
3
+
4
+ /** Default origin of the OLD devtools backend. The client appends the
5
+ * `/api` route prefix itself, so this is the bare origin. Override with
6
+ * `RECLAIM_OLD_API_URL`. */
7
+ export const DEFAULT_OLD_API_URL = 'https://devapi.reclaimprotocol.org'
8
+
9
+ /**
10
+ * Externally-supplied caller identity. We do NOT mint these — the dev
11
+ * provides one:
12
+ * - `bearer`: a Firebase ID token from the devtools dashboard. Attached
13
+ * as `Authorization: Bearer …`; `POST /api/users/login` provisions the
14
+ * user row on first use.
15
+ * - `eth`: an `eth:<lowercase-address>` uid for an already-provisioned
16
+ * eth user. Attached as the `x-eth-uid` header (no signature — the
17
+ * backend trusts the header for a uid that already exists).
18
+ */
19
+ export type OldIdentity =
20
+ | { kind: 'bearer', token: string }
21
+ | { kind: 'eth', uid: string }
22
+
23
+ export interface RegisterProviderBody {
24
+ name: string
25
+ description?: string
26
+ loginUrl: string
27
+ geoLocation?: string
28
+ providerType?: 'PRIVATE' | 'PUBLIC'
29
+ requestData: unknown[]
30
+ }
31
+
32
+ export interface UpdateProviderConfigBody {
33
+ providerConfig: Record<string, unknown>
34
+ version: string
35
+ versionInfo: string
36
+ }
37
+
38
+ export interface MyProvidersQuery {
39
+ pageKey?: number
40
+ pageSize?: number
41
+ searchQuery?: string
42
+ }
43
+
44
+ interface RequestOpts {
45
+ query?: Record<string, string | number | undefined>
46
+ body?: unknown
47
+ /** Attach the caller identity header. Throws if no identity is set. */
48
+ auth?: boolean
49
+ }
50
+
51
+ /**
52
+ * Minimal REST client for the OLD devtools Express backend (base `/api`).
53
+ * Deliberately NOT a clone of `@reclaimprotocol/client`'s operationId
54
+ * machinery — the old backend has no OpenAPI spec, so this exposes a few
55
+ * hand-written typed methods for exactly the flows old mode needs:
56
+ * authenticate, publish (register / update version), and list-own.
57
+ *
58
+ * Errors are thrown as `@hapi/boom` so `remapErrorAsResponse` renders them
59
+ * identically to builder-mode `ProblemError`s.
60
+ */
61
+ export class ReclaimOldClient {
62
+ readonly baseUrl: string
63
+ #identity?: OldIdentity
64
+ #fetch: typeof fetch
65
+
66
+ constructor(opts: {
67
+ baseUrl?: string
68
+ identity?: OldIdentity
69
+ fetch?: typeof fetch
70
+ } = {}) {
71
+ this.baseUrl = (opts.baseUrl || DEFAULT_OLD_API_URL).replace(/\/+$/, '')
72
+ this.#identity = opts.identity
73
+ this.#fetch = opts.fetch ?? fetch
74
+ }
75
+
76
+ setIdentity(identity: OldIdentity | undefined) {
77
+ this.#identity = identity
78
+ }
79
+
80
+ getIdentity(): OldIdentity | undefined {
81
+ return this.#identity
82
+ }
83
+
84
+ /** Validate + provision a bearer identity. The backend creates the user
85
+ * row on first login, which is what makes later authed calls work. */
86
+ loginWithToken(): Promise<unknown> {
87
+ return this.#request('POST', '/users/login', { auth: true })
88
+ }
89
+
90
+ registerProvider(body: RegisterProviderBody): Promise<unknown> {
91
+ return this.#request('POST', '/providers/register', { body, auth: true })
92
+ }
93
+
94
+ updateProviderConfig(
95
+ providerId: string,
96
+ body: UpdateProviderConfigBody,
97
+ ): Promise<unknown> {
98
+ return this.#request(
99
+ 'POST',
100
+ `/providers/${encodeURIComponent(providerId)}/config`,
101
+ { body, auth: true },
102
+ )
103
+ }
104
+
105
+ listVersions(providerId: string): Promise<unknown> {
106
+ return this.#request(
107
+ 'GET',
108
+ `/providers/${encodeURIComponent(providerId)}/versions`,
109
+ { auth: true },
110
+ )
111
+ }
112
+
113
+ getMyProviders(query: MyProvidersQuery = {}): Promise<unknown> {
114
+ // Every one of these query params is REQUIRED by the backend
115
+ // validator (missing/invalid → 400), so default them all here.
116
+ return this.#request('GET', '/providers/user/paginated', {
117
+ auth: true,
118
+ query: {
119
+ pageKey: query.pageKey ?? 0,
120
+ pageSize: query.pageSize ?? 20,
121
+ searchQuery: query.searchQuery ?? '',
122
+ sortByCreatedLatest: 'true',
123
+ providerType: 'ALL',
124
+ providerStatus: 'ALL',
125
+ providerVisibility: 'ALL',
126
+ },
127
+ })
128
+ }
129
+
130
+ async #request(
131
+ method: string,
132
+ path: string,
133
+ opts: RequestOpts = {},
134
+ ): Promise<unknown> {
135
+ let url = `${this.baseUrl}/api${path}`
136
+ if(opts.query) {
137
+ const search = new URLSearchParams()
138
+ for(const [k, v] of Object.entries(opts.query)) {
139
+ if(v === undefined) {
140
+ continue
141
+ }
142
+
143
+ search.append(k, String(v))
144
+ }
145
+
146
+ const qs = search.toString()
147
+ if(qs) {
148
+ url += `?${qs}`
149
+ }
150
+ }
151
+
152
+ const headers: Record<string, string> = {
153
+ Accept: 'application/json',
154
+ 'User-Agent': `${NAME}/${VERSION}`,
155
+ }
156
+ if(opts.auth) {
157
+ headers[this.#authHeaderName()] = this.#authHeaderValue()
158
+ }
159
+
160
+ const init: RequestInit = { method, headers }
161
+ if(method !== 'GET' && method !== 'HEAD') {
162
+ init.body = JSON.stringify(opts.body ?? {})
163
+ headers['Content-Type'] = 'application/json'
164
+ }
165
+
166
+ const res = await this.#fetch(url, init)
167
+ const json = await this.#parseBody(res)
168
+
169
+ // Old API convention: { message, ...data, isSuccess }. Treat a non-2xx
170
+ // OR an explicit isSuccess:false as an error so a 200-with-failure
171
+ // body never slips through as success.
172
+ const failed =
173
+ !res.ok ||
174
+ (json !== undefined &&
175
+ typeof json === 'object' &&
176
+ (json as { isSuccess?: unknown }).isSuccess === false)
177
+ if(failed) {
178
+ throw this.#asBoom(res, json)
179
+ }
180
+
181
+ return json
182
+ }
183
+
184
+ #authHeaderName(): string {
185
+ const id = this.#requireIdentity()
186
+ return id.kind === 'bearer' ? 'Authorization' : 'x-eth-uid'
187
+ }
188
+
189
+ #authHeaderValue(): string {
190
+ const id = this.#requireIdentity()
191
+ return id.kind === 'bearer' ? `Bearer ${id.token}` : id.uid
192
+ }
193
+
194
+ #requireIdentity(): OldIdentity {
195
+ if(!this.#identity) {
196
+ throw new Boom(
197
+ 'Not authenticated against old devtools. Call reclaim_authenticate ' +
198
+ 'with a dashboard token (or eth uid) first.',
199
+ { statusCode: 401, data: { status: 401, detail: 'no identity set' } },
200
+ )
201
+ }
202
+
203
+ return this.#identity
204
+ }
205
+
206
+ async #parseBody(res: Response): Promise<unknown> {
207
+ if(res.status === 204 || res.headers.get('content-length') === '0') {
208
+ return undefined
209
+ }
210
+
211
+ try {
212
+ return await res.json()
213
+ } catch{
214
+ return undefined
215
+ }
216
+ }
217
+
218
+ #asBoom(res: Response, json: unknown): Boom {
219
+ const message =
220
+ (json && typeof json === 'object' &&
221
+ typeof (json as { message?: unknown }).message === 'string'
222
+ ? (json as { message: string }).message
223
+ : undefined) || res.statusText || 'Old devtools request failed'
224
+ // Shape `data` like a ProblemDetails so remapErrorAsResponse can render
225
+ // `${message}: ${data.detail}` and expose the raw body as structured.
226
+ return new Boom(message, {
227
+ statusCode: res.ok ? 502 : res.status,
228
+ data: {
229
+ status: res.status,
230
+ title: message,
231
+ detail: message,
232
+ body: json,
233
+ },
234
+ })
235
+ }
236
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `USE_OLD_DEVTOOLS` — single source of truth for which backend the MCP
3
+ * server targets.
4
+ *
5
+ * Defaults to OLD devtools (the system still in production) while the new
6
+ * "builder" backend is under development. Builder mode must be opted into
7
+ * EXPLICITLY by setting `USE_OLD_DEVTOOLS=false`. Anything else — unset,
8
+ * empty, `true`, `1`, a typo — keeps old mode on, so a forgotten env var
9
+ * can never silently route a developer at the half-built builder backend.
10
+ */
11
+ export function useOldDevtools(): boolean {
12
+ return (process.env.USE_OLD_DEVTOOLS ?? '').trim().toLowerCase() !== 'false'
13
+ }
@@ -0,0 +1,56 @@
1
+ import { deriveFromPrivateKey } from '@reclaimprotocol/client'
2
+ import { ConfigStore } from '../config/store.ts'
3
+ import type { OldIdentity } from './client.ts'
4
+
5
+ /** Normalize an eth uid input to `eth:<lowercase-address>`. Accepts a bare
6
+ * `0x…` address or an already-prefixed `eth:0x…`. */
7
+ export function toEthUid(addressOrUid: string): OldIdentity {
8
+ const lower = addressOrUid.trim().toLowerCase()
9
+ const uid = lower.startsWith('eth:') ? lower : `eth:${lower}`
10
+ return { kind: 'eth', uid }
11
+ }
12
+
13
+ /**
14
+ * Resolve a caller identity from the environment, in priority order:
15
+ * 1. `RECLAIM_OLD_API_TOKEN` — a dashboard Firebase bearer token.
16
+ * 2. `RECLAIM_OLD_ETH_UID` — an `eth:<addr>` (or bare `0x…`) uid.
17
+ * 3. `RECLAIM_PRIVATE_KEY` — derive the eth uid from the local app key.
18
+ * Returns undefined when none is set.
19
+ */
20
+ export function resolveIdentityFromEnv(): OldIdentity | undefined {
21
+ const token = process.env.RECLAIM_OLD_API_TOKEN?.trim()
22
+ if(token) {
23
+ return { kind: 'bearer', token }
24
+ }
25
+
26
+ const ethUid = process.env.RECLAIM_OLD_ETH_UID?.trim()
27
+ if(ethUid) {
28
+ return toEthUid(ethUid)
29
+ }
30
+
31
+ const privateKey = process.env.RECLAIM_PRIVATE_KEY?.trim()
32
+ if(privateKey) {
33
+ return toEthUid(deriveFromPrivateKey(privateKey).address)
34
+ }
35
+
36
+ return undefined
37
+ }
38
+
39
+ /** Read the identity persisted by a prior `reclaim_authenticate` call. */
40
+ export function loadPersistedIdentity(): OldIdentity | undefined {
41
+ return new ConfigStore().read()?.old_devtools
42
+ }
43
+
44
+ /** Persist the identity so it survives an MCP-server restart. */
45
+ export function persistIdentity(identity: OldIdentity) {
46
+ new ConfigStore().write({ old_devtools: identity })
47
+ }
48
+
49
+ /**
50
+ * The identity the client should start with: an explicit env var wins
51
+ * (useful for headless / CI), otherwise fall back to whatever the dev
52
+ * authenticated with last time.
53
+ */
54
+ export function resolveInitialIdentity(): OldIdentity | undefined {
55
+ return resolveIdentityFromEnv() ?? loadPersistedIdentity()
56
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * MCP server instructions shown to the LLM when running in OLD-devtools mode
3
+ * (`USE_OLD_DEVTOOLS` unset or not `false`). Describes the REDUCED tool
4
+ * surface — there are no org/credential-link/review flows and no generated
5
+ * API tools here. Must not mention disabled tools.
6
+ */
7
+ export const OLD_MODE_INSTRUCTIONS =
8
+ 'This server is running against the LEGACY Reclaim devtools backend ' +
9
+ '(temporary, while the new builder backend is in development), so its ' +
10
+ 'capabilities are intentionally limited.\n\n' +
11
+ 'Provider-authoring flow:\n' +
12
+ '1. `reclaim_authenticate` — by default opens the dashboard in Chrome, ' +
13
+ 'waits for you to sign in, captures the token automatically, and closes ' +
14
+ 'the tab (or paste a `token` / pass an eth `ethAddress` to skip the ' +
15
+ 'browser). Do this once before publishing or listing.\n' +
16
+ '2. `attach_chrome` → `start_capture` → drive the page (`navigate`, ' +
17
+ '`wait_for_page`) → inspect (`list_requests`, `get_request`, ' +
18
+ '`find_requests_containing`).\n' +
19
+ '3. `propose_provider` to draft a provider, `replay_request` to ' +
20
+ 'pre-flight, `run_proof` to verify with the attestor. OPRF/`hash` ' +
21
+ 'redactions are optional and supported.\n' +
22
+ '4. `create_provider_version_from_capture` to publish. With NO ' +
23
+ '`providerId` it REGISTERS A NEW provider (re-running creates a ' +
24
+ 'duplicate); pass a ' +
25
+ '`providerId` from `get_me_providers` to add a new version to an existing ' +
26
+ 'provider instead.\n' +
27
+ '5. `get_me_providers` lists the providers you own (the only ' +
28
+ 'provider-listing tool available in this mode).\n\n' +
29
+ 'Only GET/POST endpoints can be captured into providers here. There is no ' +
30
+ 'organization, credential-linking, or public-review workflow in this mode.'
@@ -0,0 +1,153 @@
1
+ import { connectCdp } from '../cdp/client.ts'
2
+ import {
3
+ defaultProfileDir,
4
+ launchChrome,
5
+ waitForCdpReady,
6
+ } from '../cdp/launcher.ts'
7
+ import { ConfigStore } from '../config/store.ts'
8
+
9
+ /** The devtools dashboard login page. Override per-call or via
10
+ * `RECLAIM_OLD_LOGIN_URL`. */
11
+ export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org'
12
+
13
+ /**
14
+ * Read the Firebase ID token the dashboard persisted after login.
15
+ *
16
+ * The frontend uses `browserLocalPersistence`, which the Firebase JS SDK
17
+ * implements on `localStorage` under a `firebase:authUser:<apiKey>:[DEFAULT]`
18
+ * key whose JSON value carries `stsTokenManager.accessToken`. We scan for any
19
+ * such key (so we never hard-code the project apiKey) and fall back to the
20
+ * IndexedDB persistence layout (`firebaseLocalStorageDb` /
21
+ * `firebaseLocalStorage`) in case a build switches persistence. Returns the
22
+ * token string, or null when the user has not finished logging in yet.
23
+ *
24
+ * Evaluated in-page with `awaitPromise` so the async IndexedDB branch resolves.
25
+ */
26
+ const EXTRACT_TOKEN_JS = `(async () => {
27
+ try {
28
+ for (let i = 0; i < localStorage.length; i++) {
29
+ const k = localStorage.key(i)
30
+ if (k && k.indexOf('firebase:authUser:') === 0) {
31
+ const v = JSON.parse(localStorage.getItem(k) || 'null')
32
+ const t = v && v.stsTokenManager && v.stsTokenManager.accessToken
33
+ if (t) return t
34
+ }
35
+ }
36
+ } catch (e) {}
37
+ try {
38
+ return await new Promise((resolve) => {
39
+ const req = indexedDB.open('firebaseLocalStorageDb')
40
+ req.onerror = () => resolve(null)
41
+ req.onsuccess = () => {
42
+ try {
43
+ const db = req.result
44
+ if (!db.objectStoreNames.contains('firebaseLocalStorage')) {
45
+ resolve(null); return
46
+ }
47
+ const store = db
48
+ .transaction('firebaseLocalStorage', 'readonly')
49
+ .objectStore('firebaseLocalStorage')
50
+ const all = store.getAll()
51
+ all.onerror = () => resolve(null)
52
+ all.onsuccess = () => {
53
+ for (const row of (all.result || [])) {
54
+ const m = row && row.value && row.value.stsTokenManager
55
+ if (m && m.accessToken) { resolve(m.accessToken); return }
56
+ }
57
+ resolve(null)
58
+ }
59
+ } catch (e) { resolve(null) }
60
+ }
61
+ })
62
+ } catch (e) { return null }
63
+ })()`
64
+
65
+ export interface CdpLoginResult {
66
+ token: string
67
+ /** True when we launched (and then closed) a dedicated Chrome just for
68
+ * login; false when we attached to an already-running browser and only
69
+ * closed the tab we opened. */
70
+ closedBrowser: boolean
71
+ }
72
+
73
+ /**
74
+ * Open the dashboard login page in Chrome via CDP, wait for the developer to
75
+ * finish signing in, read the resulting Firebase ID token from page storage,
76
+ * then clean up. If we had to launch a dedicated Chrome for this, we close the
77
+ * whole browser afterwards; if we attached to a Chrome that was already up
78
+ * (e.g. the capture flow's), we only close the tab we opened so we don't
79
+ * disrupt it.
80
+ */
81
+ export async function captureFirebaseTokenViaCdp(opts: {
82
+ loginUrl?: string
83
+ timeoutMs?: number
84
+ pollMs?: number
85
+ }): Promise<CdpLoginResult> {
86
+ const loginUrl =
87
+ opts.loginUrl || process.env.RECLAIM_OLD_LOGIN_URL || DEFAULT_LOGIN_URL
88
+ const timeoutMs = opts.timeoutMs ?? 180_000
89
+ const pollMs = opts.pollMs ?? 1000
90
+
91
+ const cfg = new ConfigStore().read()?.browser_agent
92
+ const mode = cfg?.attachMode ?? 'dedicated'
93
+ const port = cfg?.port ?? 9222
94
+
95
+ // Ensure something is answering CDP. In dedicated mode we may launch it.
96
+ let launchedByUs = false
97
+ try {
98
+ await waitForCdpReady(port, 500)
99
+ } catch{
100
+ if(mode !== 'dedicated') {
101
+ throw new Error(
102
+ `attach mode: nothing answering CDP on port ${port}. Launch Chrome ` +
103
+ `with --remote-debugging-port=${port} AND a non-default ` +
104
+ '--user-data-dir, or switch to dedicated mode (attach_chrome).',
105
+ )
106
+ }
107
+
108
+ launchChrome({ port, profileDir: defaultProfileDir() })
109
+ await waitForCdpReady(port, 10_000)
110
+ launchedByUs = true
111
+ }
112
+
113
+ const handles = await connectCdp(port)
114
+ const { Target } = handles.browser
115
+ const { targetId } = await Target.createTarget({ url: loginUrl })
116
+
117
+ try {
118
+ const session = await handles.attachToTab(targetId)
119
+ const { Runtime } = session
120
+ const deadline = Date.now() + timeoutMs
121
+ while(Date.now() < deadline) {
122
+ const r = await Runtime.evaluate({
123
+ expression: EXTRACT_TOKEN_JS,
124
+ awaitPromise: true,
125
+ returnByValue: true,
126
+ })
127
+ const value = r.result?.value
128
+ if(typeof value === 'string' && value.length > 0) {
129
+ return { token: value, closedBrowser: launchedByUs }
130
+ }
131
+
132
+ await new Promise((resolve) => setTimeout(resolve, pollMs))
133
+ }
134
+
135
+ throw new Error(
136
+ 'Timed out waiting for login. Finish signing in at the opened ' +
137
+ 'page, or re-run reclaim_authenticate with a longer timeoutMs.',
138
+ )
139
+ } finally {
140
+ // Best-effort cleanup — never mask the real result/error.
141
+ try {
142
+ if(launchedByUs) {
143
+ await handles.browser.Browser.close()
144
+ } else {
145
+ await handles.browser.Target.closeTarget({ targetId })
146
+ }
147
+ } catch{
148
+ // ignore
149
+ }
150
+
151
+ await handles.close().catch(() => {})
152
+ }
153
+ }