@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.
- package/README.md +287 -0
- package/lib/cdp/capture.d.ts +22 -0
- package/lib/cdp/capture.js +159 -0
- package/lib/cdp/client.d.ts +9 -0
- package/lib/cdp/client.js +11 -0
- package/lib/cdp/eviction.d.ts +16 -0
- package/lib/cdp/eviction.js +38 -0
- package/lib/cdp/launcher.d.ts +14 -0
- package/lib/cdp/launcher.js +102 -0
- package/lib/cdp/tabs.d.ts +6 -0
- package/lib/cdp/tabs.js +10 -0
- package/lib/cli/args.d.ts +14 -0
- package/lib/cli/args.js +19 -0
- package/lib/cli/commands.d.ts +1 -0
- package/lib/cli/commands.js +19 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +7 -0
- package/lib/config/store.d.ts +45 -0
- package/lib/config/store.js +46 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +7 -0
- package/lib/logger.d.ts +2 -0
- package/lib/logger.js +7 -0
- package/lib/mcp/api/operations.gen.d.ts +152 -0
- package/lib/mcp/api/operations.gen.js +2300 -0
- package/lib/mcp/api/tools.d.ts +3 -0
- package/lib/mcp/api/tools.js +36 -0
- package/lib/mcp/remap_errors.d.ts +2 -0
- package/lib/mcp/remap_errors.js +27 -0
- package/lib/mcp/server.d.ts +38 -0
- package/lib/mcp/server.js +76 -0
- package/lib/mcp/start.d.ts +1 -0
- package/lib/mcp/start.js +63 -0
- package/lib/mcp/tools/agent/analyze.d.ts +3 -0
- package/lib/mcp/tools/agent/analyze.js +26 -0
- package/lib/mcp/tools/agent/attach.d.ts +11 -0
- package/lib/mcp/tools/agent/attach.js +72 -0
- package/lib/mcp/tools/agent/build.d.ts +33 -0
- package/lib/mcp/tools/agent/build.js +74 -0
- package/lib/mcp/tools/agent/capture.d.ts +10 -0
- package/lib/mcp/tools/agent/capture.js +142 -0
- package/lib/mcp/tools/agent/eval.d.ts +5 -0
- package/lib/mcp/tools/agent/eval.js +127 -0
- package/lib/mcp/tools/agent/find.d.ts +3 -0
- package/lib/mcp/tools/agent/find.js +60 -0
- package/lib/mcp/tools/agent/proof.d.ts +3 -0
- package/lib/mcp/tools/agent/proof.js +214 -0
- package/lib/mcp/tools/agent/propose.d.ts +9 -0
- package/lib/mcp/tools/agent/propose.js +75 -0
- package/lib/mcp/tools/agent/publish.d.ts +3 -0
- package/lib/mcp/tools/agent/publish.js +122 -0
- package/lib/mcp/tools/agent/replay.d.ts +3 -0
- package/lib/mcp/tools/agent/replay.js +46 -0
- package/lib/mcp/tools/agent/session.d.ts +10 -0
- package/lib/mcp/tools/agent/session.js +29 -0
- package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
- package/lib/mcp/tools/credentials/authenticate.js +108 -0
- package/lib/mcp/tools/credentials/build.d.ts +16 -0
- package/lib/mcp/tools/credentials/build.js +33 -0
- package/lib/mcp/tools/credentials/cache.d.ts +70 -0
- package/lib/mcp/tools/credentials/cache.js +114 -0
- package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
- package/lib/mcp/tools/credentials/env-file.js +71 -0
- package/lib/mcp/tools/credentials/import.d.ts +2 -0
- package/lib/mcp/tools/credentials/import.js +52 -0
- package/lib/mcp/tools/credentials/issue.d.ts +2 -0
- package/lib/mcp/tools/credentials/issue.js +84 -0
- package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
- package/lib/mcp/tools/credentials/list-cached.js +55 -0
- package/lib/mcp/tools/credentials/register.d.ts +18 -0
- package/lib/mcp/tools/credentials/register.js +30 -0
- package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
- package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
- package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
- package/lib/mcp/tools/credentials/session-cache.js +19 -0
- package/lib/mcp/tools/credentials/status.d.ts +3 -0
- package/lib/mcp/tools/credentials/status.js +62 -0
- package/lib/mcp-bin.d.ts +1 -0
- package/lib/mcp-bin.js +7 -0
- package/lib/old/client.d.ts +67 -0
- package/lib/old/client.js +146 -0
- package/lib/old/flag.d.ts +11 -0
- package/lib/old/flag.js +13 -0
- package/lib/old/identity.d.ts +22 -0
- package/lib/old/identity.js +47 -0
- package/lib/old/instructions.d.ts +7 -0
- package/lib/old/instructions.js +29 -0
- package/lib/old/login-cdp.d.ts +23 -0
- package/lib/old/login-cdp.js +125 -0
- package/lib/old/to-register.d.ts +47 -0
- package/lib/old/to-register.js +133 -0
- package/lib/old/tools/authenticate.d.ts +18 -0
- package/lib/old/tools/authenticate.js +127 -0
- package/lib/old/tools/build.d.ts +9 -0
- package/lib/old/tools/build.js +16 -0
- package/lib/old/tools/me-providers.d.ts +10 -0
- package/lib/old/tools/me-providers.js +32 -0
- package/lib/old/tools/publish.d.ts +16 -0
- package/lib/old/tools/publish.js +97 -0
- package/lib/paths.d.ts +11 -0
- package/lib/paths.js +32 -0
- package/lib/pkg.d.ts +3 -0
- package/lib/pkg.js +13 -0
- package/lib/proof/attestor.d.ts +39 -0
- package/lib/proof/attestor.js +207 -0
- package/lib/proof/replay.d.ts +8 -0
- package/lib/proof/replay.js +73 -0
- package/lib/provider/constraints.d.ts +2 -0
- package/lib/provider/constraints.js +122 -0
- package/lib/provider/draft.d.ts +6 -0
- package/lib/provider/draft.js +68 -0
- package/lib/provider/graphql.d.ts +2 -0
- package/lib/provider/graphql.js +35 -0
- package/lib/provider/matcher.d.ts +36 -0
- package/lib/provider/matcher.js +436 -0
- package/lib/provider/normalize.d.ts +4 -0
- package/lib/provider/normalize.js +51 -0
- package/lib/provider/schema.d.ts +76 -0
- package/lib/provider/schema.js +1 -0
- package/lib/provider/secrets.d.ts +5 -0
- package/lib/provider/secrets.js +22 -0
- package/lib/provider/to-version.d.ts +22 -0
- package/lib/provider/to-version.js +68 -0
- package/lib/types/openapi.d.ts +4 -0
- package/lib/types/openapi.js +1 -0
- package/package.json +88 -0
- package/scripts/download-zk-files.ts +42 -0
- package/scripts/prepend-shebang.ts +17 -0
- package/skill/SKILL.md +248 -0
- package/src/cdp/capture.ts +215 -0
- package/src/cdp/client.ts +20 -0
- package/src/cdp/eviction.ts +52 -0
- package/src/cdp/launcher.ts +133 -0
- package/src/cdp/tabs.ts +22 -0
- package/src/cli/args.ts +20 -0
- package/src/cli/commands.ts +27 -0
- package/src/cli.ts +7 -0
- package/src/config/store.ts +86 -0
- package/src/index.ts +12 -0
- package/src/logger.ts +8 -0
- package/src/mcp/api/operations.gen.ts +2397 -0
- package/src/mcp/api/tools.ts +45 -0
- package/src/mcp/remap_errors.ts +35 -0
- package/src/mcp/server.ts +119 -0
- package/src/mcp/start.ts +78 -0
- package/src/mcp/tools/agent/analyze.ts +34 -0
- package/src/mcp/tools/agent/attach.ts +95 -0
- package/src/mcp/tools/agent/build.ts +99 -0
- package/src/mcp/tools/agent/capture.ts +202 -0
- package/src/mcp/tools/agent/eval.ts +161 -0
- package/src/mcp/tools/agent/find.ts +73 -0
- package/src/mcp/tools/agent/proof.ts +272 -0
- package/src/mcp/tools/agent/propose.ts +111 -0
- package/src/mcp/tools/agent/publish.ts +155 -0
- package/src/mcp/tools/agent/replay.ts +58 -0
- package/src/mcp/tools/agent/session.ts +46 -0
- package/src/mcp/tools/credentials/authenticate.ts +135 -0
- package/src/mcp/tools/credentials/build.ts +37 -0
- package/src/mcp/tools/credentials/cache.ts +161 -0
- package/src/mcp/tools/credentials/env-file.ts +101 -0
- package/src/mcp/tools/credentials/import.ts +72 -0
- package/src/mcp/tools/credentials/issue.ts +107 -0
- package/src/mcp/tools/credentials/list-cached.ts +66 -0
- package/src/mcp/tools/credentials/register.ts +49 -0
- package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
- package/src/mcp/tools/credentials/session-cache.ts +31 -0
- package/src/mcp/tools/credentials/status.ts +82 -0
- package/src/mcp-bin.ts +7 -0
- package/src/old/client.ts +236 -0
- package/src/old/flag.ts +13 -0
- package/src/old/identity.ts +56 -0
- package/src/old/instructions.ts +30 -0
- package/src/old/login-cdp.ts +153 -0
- package/src/old/to-register.ts +234 -0
- package/src/old/tools/authenticate.ts +152 -0
- package/src/old/tools/build.ts +21 -0
- package/src/old/tools/me-providers.ts +42 -0
- package/src/old/tools/publish.ts +135 -0
- package/src/paths.ts +37 -0
- package/src/pkg.ts +24 -0
- package/src/proof/attestor.ts +286 -0
- package/src/proof/replay.ts +102 -0
- package/src/provider/constraints.ts +150 -0
- package/src/provider/draft.ts +105 -0
- package/src/provider/graphql.ts +42 -0
- package/src/provider/matcher.ts +616 -0
- package/src/provider/normalize.ts +65 -0
- package/src/provider/schema.ts +92 -0
- package/src/provider/secrets.ts +29 -0
- package/src/provider/to-version.ts +90 -0
- 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 || ''
|