@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,234 @@
1
+ import type { ReclaimProvider } from '../provider/schema.ts'
2
+ import type {
3
+ RegisterProviderBody,
4
+ UpdateProviderConfigBody,
5
+ } from './client.ts'
6
+
7
+ /**
8
+ * Translate a drafted `ReclaimProvider` (from `propose_provider`) into the
9
+ * OLD devtools backend shapes. Deliberately a SEPARATE module from
10
+ * `provider/to-version.ts` (the builder translator) — the two backends have
11
+ * structurally different bodies:
12
+ *
13
+ * - builder NESTS each redaction into its match's `extract`;
14
+ * - old devtools keeps `responseMatches` and `responseRedactions` as
15
+ * PARALLEL arrays on each `requestData` entry.
16
+ *
17
+ * Old-devtools limit we enforce here (a clear error beats a confusing
18
+ * server-side 400): only `GET` / `POST` methods. OPRF redactions are passed
19
+ * through unchanged — the backend's `hash` field accepts them.
20
+ */
21
+
22
+ export interface OldProviderOpts {
23
+ initialUrl: string
24
+ description?: string
25
+ geoLocation?: string
26
+ providerType?: 'PRIVATE' | 'PUBLIC'
27
+ }
28
+
29
+ interface OldResponseMatch {
30
+ value: string
31
+ type: string
32
+ isOptional: boolean
33
+ order: number
34
+ }
35
+
36
+ interface OldResponseRedaction {
37
+ xPath?: string
38
+ jsonPath?: string
39
+ regex?: string
40
+ /** OPRF mode (`oprf` / `oprf-raw` / `oprf-mpc`) — the backend stores it as
41
+ * a free-form string and the attestor interprets it at verification. */
42
+ hash?: string
43
+ order: number
44
+ }
45
+
46
+ interface OldRequestData {
47
+ url: string
48
+ urlType: 'CONSTANT' | 'TEMPLATE'
49
+ method: 'GET' | 'POST'
50
+ responseMatches: OldResponseMatch[]
51
+ responseRedactions: OldResponseRedaction[]
52
+ credentials: 'include'
53
+ bodySniff?: { enabled: boolean, template: string }
54
+ }
55
+
56
+ /** Build the single `requestData` entry shared by register + update. */
57
+ function requestDataFromProvider(provider: ReclaimProvider): OldRequestData {
58
+ const method = String(provider.method).toUpperCase()
59
+ if(method !== 'GET' && method !== 'POST') {
60
+ throw new Error(
61
+ 'Old devtools supports only GET/POST requests; got ' +
62
+ `"${provider.method}". Re-capture against a GET/POST endpoint.`,
63
+ )
64
+ }
65
+
66
+ const responseMatches: OldResponseMatch[] = provider.responseMatches.map(
67
+ (m, i) => ({
68
+ value: m.value,
69
+ type: m.type ?? 'contains',
70
+ isOptional: false,
71
+ order: i,
72
+ }),
73
+ )
74
+
75
+ const responseRedactions: OldResponseRedaction[] = (
76
+ provider.responseRedactions ?? []
77
+ ).map((r, i) => {
78
+ const out: OldResponseRedaction = { order: i }
79
+ if(r.xPath !== undefined) {
80
+ out.xPath = r.xPath
81
+ }
82
+
83
+ if(r.jsonPath !== undefined) {
84
+ out.jsonPath = r.jsonPath
85
+ }
86
+
87
+ if(r.regex !== undefined) {
88
+ out.regex = r.regex
89
+ }
90
+
91
+ // OPRF redactions are supported — pass the mode through unchanged.
92
+ if(r.hash !== undefined) {
93
+ out.hash = r.hash
94
+ }
95
+
96
+ return out
97
+ })
98
+
99
+ const requestData: OldRequestData = {
100
+ url: provider.url,
101
+ // `{{param}}` placeholders → TEMPLATE so the backend templates them;
102
+ // otherwise a fixed CONSTANT url. (REGEX is never drafted.)
103
+ urlType: provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT',
104
+ method: method,
105
+ responseMatches,
106
+ responseRedactions,
107
+ // The verification client re-supplies cookies/auth at runtime; secrets
108
+ // are never persisted (propose_provider already stripped them).
109
+ credentials: 'include',
110
+ }
111
+ if(provider.body !== undefined) {
112
+ requestData.bodySniff = { enabled: true, template: provider.body }
113
+ }
114
+
115
+ // NOTE: `responseVariables` is intentionally omitted — the old backend
116
+ // auto-extracts it from `{{var}}` placeholders in responseMatches. Sending
117
+ // it ourselves would risk drifting from the server's own extraction.
118
+ return requestData
119
+ }
120
+
121
+ /** Body for `POST /api/providers/register` — new provider + v1.0.0. */
122
+ export function providerToRegisterBody(
123
+ provider: ReclaimProvider,
124
+ opts: OldProviderOpts,
125
+ ): RegisterProviderBody {
126
+ const body: RegisterProviderBody = {
127
+ name: provider.name,
128
+ loginUrl: opts.initialUrl,
129
+ providerType: opts.providerType ?? 'PRIVATE',
130
+ requestData: [requestDataFromProvider(provider)],
131
+ }
132
+ if(opts.description !== undefined) {
133
+ body.description = opts.description
134
+ }
135
+
136
+ if(opts.geoLocation !== undefined) {
137
+ body.geoLocation = opts.geoLocation
138
+ }
139
+
140
+ return body
141
+ }
142
+
143
+ /** Body for `POST /api/providers/:providerId/config` — adds a new version to
144
+ * an EXISTING provider. `version` must not already exist; `versionInfo` is
145
+ * required and must be ≥10 chars (enforced by the backend). */
146
+ export function providerToConfigBody(
147
+ provider: ReclaimProvider,
148
+ opts: OldProviderOpts,
149
+ version: string,
150
+ versionInfo: string,
151
+ ): UpdateProviderConfigBody {
152
+ const providerConfig: Record<string, unknown> = {
153
+ loginUrl: opts.initialUrl,
154
+ requestData: [requestDataFromProvider(provider)],
155
+ }
156
+ if(opts.geoLocation !== undefined) {
157
+ providerConfig.geoLocation = opts.geoLocation
158
+ }
159
+
160
+ return { providerConfig, version, versionInfo }
161
+ }
162
+
163
+ /** Minimal version triplet as the old backend stores it. */
164
+ export interface SemverTriplet {
165
+ major: number
166
+ minor: number
167
+ patch: number
168
+ }
169
+
170
+ /**
171
+ * Pull `{major,minor,patch}` triplets out of a `/versions` response, tolerant
172
+ * of the exact envelope (array, `{ providerVersions: [...] }`, `{ items }`,
173
+ * etc.). Each entry's `version` is the triplet.
174
+ */
175
+ export function extractVersions(resp: unknown): SemverTriplet[] {
176
+ const list = pickArray(resp)
177
+ const out: SemverTriplet[] = []
178
+ for(const item of list) {
179
+ const v = (item as { version?: unknown }).version ?? item
180
+ if(
181
+ v && typeof v === 'object' &&
182
+ typeof (v as SemverTriplet).major === 'number' &&
183
+ typeof (v as SemverTriplet).minor === 'number' &&
184
+ typeof (v as SemverTriplet).patch === 'number'
185
+ ) {
186
+ const t = v as SemverTriplet
187
+ out.push({ major: t.major, minor: t.minor, patch: t.patch })
188
+ }
189
+ }
190
+
191
+ return out
192
+ }
193
+
194
+ /**
195
+ * Next version string for an update: the highest existing triplet with its
196
+ * patch bumped, as `"major.minor.patch"`. Falls back to `"1.0.1"` when no
197
+ * versions are found (a provider being updated always has ≥1, but stay safe
198
+ * rather than collide with the guaranteed 1.0.0).
199
+ */
200
+ export function bumpPatchString(versions: SemverTriplet[]): string {
201
+ if(versions.length === 0) {
202
+ return '1.0.1'
203
+ }
204
+
205
+ let hi = versions[0]
206
+ for(const v of versions) {
207
+ if(
208
+ v.major > hi.major ||
209
+ (v.major === hi.major && v.minor > hi.minor) ||
210
+ (v.major === hi.major && v.minor === hi.minor && v.patch > hi.patch)
211
+ ) {
212
+ hi = v
213
+ }
214
+ }
215
+
216
+ return `${hi.major}.${hi.minor}.${hi.patch + 1}`
217
+ }
218
+
219
+ function pickArray(resp: unknown): unknown[] {
220
+ if(Array.isArray(resp)) {
221
+ return resp
222
+ }
223
+
224
+ if(resp && typeof resp === 'object') {
225
+ const obj = resp as Record<string, unknown>
226
+ for(const key of ['providerVersions', 'versions', 'items', 'data']) {
227
+ if(Array.isArray(obj[key])) {
228
+ return obj[key] as unknown[]
229
+ }
230
+ }
231
+ }
232
+
233
+ return []
234
+ }
@@ -0,0 +1,152 @@
1
+ import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
2
+ import type { OldIdentity, ReclaimOldClient } from '../client.ts'
3
+ import { persistIdentity, toEthUid } from '../identity.ts'
4
+ import { captureFirebaseTokenViaCdp } from '../login-cdp.ts'
5
+
6
+ /**
7
+ * OLD-devtools authenticate. The default, zero-arg flow drives Chrome via CDP:
8
+ * it opens the devtools dashboard, waits for the developer to sign in, reads
9
+ * the Firebase ID token straight from page storage, then closes the tab (or
10
+ * the browser, if it launched one just for this). Two manual overrides remain:
11
+ *
12
+ * - `token`: paste a Firebase ID token yourself (skips the browser).
13
+ * - `ethAddress`: an `eth:<addr>`/`0x…` uid for an already-provisioned eth
14
+ * user (header carries no signature, so the uid must already exist).
15
+ *
16
+ * The chosen identity is attached to the shared client and persisted to
17
+ * `~/.reclaim/config.json` so later calls and future restarts reuse it. A
18
+ * zero-arg call reuses a still-valid stored identity before falling back to
19
+ * the browser flow.
20
+ */
21
+ export function authenticateTool(client: ReclaimOldClient): RegisteredTool {
22
+ /** Validate an identity against the backend (throws on failure). */
23
+ const validate = async(identity: OldIdentity): Promise<void> => {
24
+ client.setIdentity(identity)
25
+ if(identity.kind === 'bearer') {
26
+ await client.loginWithToken()
27
+ } else {
28
+ await client.getMyProviders({ pageSize: 1 })
29
+ }
30
+ }
31
+
32
+ return defineTool<{
33
+ token?: string
34
+ ethAddress?: string
35
+ loginUrl?: string
36
+ timeoutMs?: number
37
+ useBrowser?: boolean
38
+ }>(
39
+ {
40
+ name: 'reclaim_authenticate',
41
+ description:
42
+ 'Authenticate against the OLD Reclaim devtools backend. \n\n' +
43
+ 'Default (no args): opens the devtools dashboard in Chrome via CDP, ' +
44
+ 'waits for you to sign in, grabs the Firebase token automatically, ' +
45
+ 'and closes the tab. A previously stored identity that is still ' +
46
+ 'valid is reused first (pass `useBrowser: true` to force a fresh ' +
47
+ 'login). \n\n' +
48
+ 'Manual overrides:\n' +
49
+ '- `token`: paste a Firebase ID token (skips the browser).\n' +
50
+ '- `ethAddress`: an `eth:0x…` / `0x…` uid for an already-provisioned ' +
51
+ 'eth user (returns 401 "ETH user not found" if unknown).\n\n' +
52
+ 'The identity is cached locally for subsequent publish / ' +
53
+ 'get_me_providers calls. Firebase tokens are short-lived (~1h) — ' +
54
+ 're-run this when calls start returning 401.',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ token: {
59
+ type: 'string',
60
+ description: 'Firebase ID token (skips the browser flow).',
61
+ },
62
+ ethAddress: {
63
+ type: 'string',
64
+ description: 'eth uid (`eth:0x…`) or bare `0x…` address.',
65
+ },
66
+ loginUrl: {
67
+ type: 'string',
68
+ description:
69
+ 'Dashboard URL to open for the browser flow ' +
70
+ '(default https://dev.reclaimprotocol.org).',
71
+ },
72
+ timeoutMs: {
73
+ type: 'number',
74
+ description:
75
+ 'How long to wait for sign-in, in ms (default 180000).',
76
+ },
77
+ useBrowser: {
78
+ type: 'boolean',
79
+ description:
80
+ 'Force the browser login even if a stored identity exists.',
81
+ },
82
+ },
83
+ },
84
+ },
85
+ async({ token, ethAddress, loginUrl, timeoutMs, useBrowser }) => {
86
+ if(token && ethAddress) {
87
+ throw new Error(
88
+ 'Provide only ONE of `token` or `ethAddress`, not both.',
89
+ )
90
+ }
91
+
92
+ // Explicit credential paths skip the browser entirely.
93
+ if(token || ethAddress) {
94
+ const identity: OldIdentity = token
95
+ ? { kind: 'bearer', token: token.trim() }
96
+ : toEthUid(ethAddress!)
97
+ await validate(identity)
98
+ persistIdentity(identity)
99
+ return {
100
+ authenticated: true,
101
+ method: identity.kind === 'bearer' ? 'token' : 'eth',
102
+ identity:
103
+ identity.kind === 'bearer'
104
+ ? { kind: 'bearer' }
105
+ : { kind: 'eth', uid: identity.uid },
106
+ }
107
+ }
108
+
109
+ // Reuse a still-valid stored/env identity before opening a browser.
110
+ if(!useBrowser) {
111
+ const existing = client.getIdentity()
112
+ if(existing) {
113
+ try {
114
+ await validate(existing)
115
+ persistIdentity(existing)
116
+ return {
117
+ authenticated: true,
118
+ method: 'reused',
119
+ identity:
120
+ existing.kind === 'bearer'
121
+ ? { kind: 'bearer' }
122
+ : { kind: 'eth', uid: existing.uid },
123
+ }
124
+ } catch{
125
+ // Stale/expired — fall through to the browser flow.
126
+ }
127
+ }
128
+ }
129
+
130
+ // CDP browser login (the default headline flow).
131
+ if(process.env.RECLAIM_AGENT_DISABLED === '1') {
132
+ throw new Error(
133
+ 'Browser login is unavailable (RECLAIM_AGENT_DISABLED=1). Pass a ' +
134
+ '`token` (Firebase ID token) or `ethAddress` instead.',
135
+ )
136
+ }
137
+
138
+ const result = await captureFirebaseTokenViaCdp({ loginUrl, timeoutMs })
139
+ const identity: OldIdentity = { kind: 'bearer', token: result.token }
140
+ await validate(identity)
141
+ persistIdentity(identity)
142
+ return {
143
+ authenticated: true,
144
+ method: 'browser',
145
+ closedBrowser: result.closedBrowser,
146
+ note:
147
+ 'Signed in via the dashboard; Firebase token captured and cached. ' +
148
+ 'Tokens expire (~1h) — re-run reclaim_authenticate on 401.',
149
+ }
150
+ },
151
+ )
152
+ }
@@ -0,0 +1,21 @@
1
+ import type { RegisteredTool } from '../../mcp/server.ts'
2
+ import type { ReclaimOldClient } from '../client.ts'
3
+ import { authenticateTool } from './authenticate.ts'
4
+ import { getMeProvidersTool } from './me-providers.ts'
5
+ import { createVersionFromCaptureTool } from './publish.ts'
6
+
7
+ /**
8
+ * The backend-facing tools for OLD-devtools mode. Mirrors
9
+ * `buildCredentialTools` / `buildAgentTools`: returns a flat `RegisteredTool`
10
+ * list for `registerToolList`. These three replace the builder credential +
11
+ * publish tools and the (disabled) generated provider tools.
12
+ */
13
+ export function buildOldBackendTools(
14
+ client: ReclaimOldClient,
15
+ ): RegisteredTool[] {
16
+ return [
17
+ authenticateTool(client),
18
+ createVersionFromCaptureTool(client),
19
+ getMeProvidersTool(client),
20
+ ]
21
+ }
@@ -0,0 +1,42 @@
1
+ import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
2
+ import type { ReclaimOldClient } from '../client.ts'
3
+
4
+ /**
5
+ * List the authenticated user's OWN providers on the old devtools backend
6
+ * (`GET /api/providers/user/paginated`, filtered server-side to the caller's
7
+ * uid). This exists ONLY in old mode: the generated provider-listing tools
8
+ * are disabled there, so this is the way to discover a `providerId` to pass
9
+ * back into `create_provider_version_from_capture` for a version update.
10
+ */
11
+ export function getMeProvidersTool(client: ReclaimOldClient): RegisteredTool {
12
+ return defineTool<{
13
+ pageKey?: number
14
+ pageSize?: number
15
+ searchQuery?: string
16
+ }>(
17
+ {
18
+ name: 'get_me_providers',
19
+ description:
20
+ 'List the providers YOU own on the old devtools backend (newest ' +
21
+ 'first). Returns provider metadata including each `providerId` — ' +
22
+ 'pass one back to create_provider_version_from_capture to publish a ' +
23
+ 'new version of that provider instead of creating a duplicate. ' +
24
+ 'Requires reclaim_authenticate first.',
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ pageKey: { type: 'number', description: 'Page index (default 0).' },
29
+ pageSize: {
30
+ type: 'number',
31
+ description: 'Page size (default 20).',
32
+ },
33
+ searchQuery: {
34
+ type: 'string',
35
+ description: 'Filter by name or providerId.',
36
+ },
37
+ },
38
+ },
39
+ },
40
+ async(args) => client.getMyProviders(args),
41
+ )
42
+ }
@@ -0,0 +1,135 @@
1
+ import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
2
+ import type { ReclaimProvider } from '../../provider/schema.ts'
3
+ import type { ReclaimOldClient } from '../client.ts'
4
+ import {
5
+ bumpPatchString,
6
+ extractVersions,
7
+ type OldProviderOpts,
8
+ providerToConfigBody,
9
+ providerToRegisterBody,
10
+ } from '../to-register.ts'
11
+
12
+ const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture'
13
+
14
+ /**
15
+ * Publish a drafted provider to the OLD devtools backend. Keeps the builder
16
+ * tool NAME (`create_provider_version_from_capture`) so the skill flow is
17
+ * unchanged, but the behaviour differs:
18
+ *
19
+ * - WITHOUT `providerId` → `POST /api/providers/register`: creates a
20
+ * brand-new provider + version 1.0.0. Re-running creates a DUPLICATE.
21
+ * - WITH `providerId` → `POST /api/providers/:providerId/config`: adds a
22
+ * new version to an existing provider (auto-bumps the patch above the
23
+ * highest existing version).
24
+ *
25
+ * Get a `providerId` from `get_me_providers`.
26
+ */
27
+ export function createVersionFromCaptureTool(
28
+ client: ReclaimOldClient,
29
+ ): RegisteredTool {
30
+ return defineTool<{
31
+ provider: ReclaimProvider
32
+ initialUrl: string
33
+ providerId?: string
34
+ description?: string
35
+ geoLocation?: string
36
+ providerType?: 'PRIVATE' | 'PUBLIC'
37
+ notes?: string
38
+ }>(
39
+ {
40
+ name: 'create_provider_version_from_capture',
41
+ description:
42
+ 'Publish a drafted ReclaimProvider to the OLD devtools backend. ' +
43
+ 'Pass the `provider` object from propose_provider VERBATIM and the ' +
44
+ '`initialUrl` the user landed on (the login/landing page, NOT the ' +
45
+ 'API url). \n\n' +
46
+ 'Omit `providerId` to REGISTER A NEW provider (creates it + version ' +
47
+ '1.0.0; re-running creates a duplicate). Pass `providerId` (from ' +
48
+ 'get_me_providers) to add a NEW VERSION to that existing provider ' +
49
+ '(the patch is auto-bumped). \n\n' +
50
+ 'Captured requests must be GET/POST. OPRF redactions are supported.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ provider: {
55
+ type: 'object',
56
+ description:
57
+ 'The `provider` object from propose_provider, verbatim.',
58
+ properties: {
59
+ name: { type: 'string' },
60
+ url: { type: 'string' },
61
+ method: { type: 'string' },
62
+ headers: { type: 'object' },
63
+ body: { type: 'string' },
64
+ responseMatches: { type: 'array' },
65
+ responseRedactions: { type: 'array' },
66
+ paramValues: { type: 'object' },
67
+ },
68
+ required: ['name', 'url', 'method', 'responseMatches'],
69
+ },
70
+ initialUrl: {
71
+ type: 'string',
72
+ description:
73
+ 'The website URL the user landed on to trigger the request ' +
74
+ '(maps to the provider `loginUrl`).',
75
+ },
76
+ providerId: {
77
+ type: 'string',
78
+ description:
79
+ 'Existing provider to add a new version to. Omit to register ' +
80
+ 'a brand-new provider.',
81
+ },
82
+ description: {
83
+ type: 'string',
84
+ description: 'Provider description (new-provider register only).',
85
+ },
86
+ geoLocation: { type: 'string' },
87
+ providerType: { type: 'string', enum: ['PRIVATE', 'PUBLIC'] },
88
+ notes: {
89
+ type: 'string',
90
+ description:
91
+ 'Change note for the new version (update only; must be ≥10 ' +
92
+ 'chars or a default is used).',
93
+ },
94
+ },
95
+ required: ['provider', 'initialUrl'],
96
+ },
97
+ },
98
+ async(args) => {
99
+ const opts: OldProviderOpts = { initialUrl: args.initialUrl }
100
+ if(args.description !== undefined) {
101
+ opts.description = args.description
102
+ }
103
+
104
+ if(args.geoLocation !== undefined) {
105
+ opts.geoLocation = args.geoLocation
106
+ }
107
+
108
+ if(args.providerType !== undefined) {
109
+ opts.providerType = args.providerType
110
+ }
111
+
112
+ // New provider.
113
+ if(!args.providerId) {
114
+ const body = providerToRegisterBody(args.provider, opts)
115
+ return client.registerProvider(body)
116
+ }
117
+
118
+ // New version of an existing provider — pick the next patch above
119
+ // the highest existing version.
120
+ const versionsResp = await client.listVersions(args.providerId)
121
+ const version = bumpPatchString(extractVersions(versionsResp))
122
+ const versionInfo =
123
+ args.notes && args.notes.trim().length >= 10
124
+ ? args.notes.trim()
125
+ : DEFAULT_VERSION_INFO
126
+ const body = providerToConfigBody(
127
+ args.provider,
128
+ opts,
129
+ version,
130
+ versionInfo,
131
+ )
132
+ return client.updateProviderConfig(args.providerId, body)
133
+ },
134
+ )
135
+ }
package/src/paths.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { homedir } from 'node:os'
3
+ import { dirname, join, resolve } from 'node:path'
4
+
5
+ /**
6
+ * Resolve the Reclaim cache directory (`~/.reclaim`). Honours the
7
+ * `RECLAIM_HOME` env var as an override so tests can sandbox writes.
8
+ */
9
+ export function reclaimHomeDirPath() {
10
+ return process.env.RECLAIM_HOME || join(homedir(), '.reclaim')
11
+ }
12
+
13
+ /**
14
+ * Walk up from `start` until we find a directory containing one of
15
+ * the project-root markers, then return that directory. Falls back
16
+ * to `start` if no marker is found within 8 levels.
17
+ */
18
+ export function findProjectRoot(start: string = process.cwd()) {
19
+ const markers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml']
20
+ let dir = resolve(start)
21
+ for(let i = 0; i < 8; i++) {
22
+ for(const m of markers) {
23
+ if(existsSync(join(dir, m))) {
24
+ return dir
25
+ }
26
+ }
27
+
28
+ const parent = dirname(dir)
29
+ if(parent === dir) {
30
+ break
31
+ }
32
+
33
+ dir = parent
34
+ }
35
+
36
+ return start
37
+ }
package/src/pkg.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ // package.json lives outside `rootDir: src`, so we can't `import` it
6
+ // directly without widening rootDir and corrupting the build output
7
+ // tree. Read it at runtime instead — relative to this module's URL so
8
+ // the path resolves identically in dev (src/pkg.ts) and after build
9
+ // (build/pkg.js), both one level below the package root.
10
+ const PKG_PATH = join(
11
+ dirname(fileURLToPath(import.meta.url)),
12
+ '..',
13
+ 'package.json',
14
+ )
15
+
16
+ const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8')) as {
17
+ name: string
18
+ version: string
19
+ description?: string
20
+ }
21
+
22
+ export const NAME = pkg.name
23
+ export const VERSION = pkg.version
24
+ export const DESCRIPTION = pkg.description || ''