@reclaimprotocol/agent 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/authoring/backend.d.ts +0 -24
- package/lib/authoring/find.d.ts +3 -0
- package/lib/authoring/find.js +54 -0
- package/lib/authoring/index.d.ts +3 -3
- package/lib/authoring/index.js +4 -3
- package/lib/authoring/propose.d.ts +3 -0
- package/lib/authoring/propose.js +109 -0
- package/lib/authoring/replay.d.ts +10 -0
- package/lib/authoring/replay.js +34 -0
- package/lib/authoring/tools.d.ts +13 -6
- package/lib/authoring/tools.js +108 -293
- package/lib/authoring/util.d.ts +27 -0
- package/lib/authoring/util.js +21 -0
- package/lib/consts.d.ts +6 -1
- package/lib/consts.js +9 -2
- package/lib/mcp/server.d.ts +2 -41
- package/lib/mcp/server.js +6 -34
- package/lib/mcp/start.js +4 -2
- package/lib/mcp/tools/agent/analyze.js +3 -3
- package/lib/mcp/tools/agent/build.d.ts +4 -4
- package/lib/mcp/tools/agent/build.js +13 -15
- package/lib/mcp/tools/agent/capture.d.ts +13 -1
- package/lib/mcp/tools/agent/capture.js +18 -85
- package/lib/mcp/tools/agent/eval.d.ts +5 -2
- package/lib/mcp/tools/agent/eval.js +11 -121
- package/lib/mcp/tools/agent/find.d.ts +10 -3
- package/lib/mcp/tools/agent/find.js +25 -31
- package/lib/mcp/tools/agent/proof.d.ts +13 -3
- package/lib/mcp/tools/agent/proof.js +81 -132
- package/lib/mcp/tools/agent/propose.d.ts +10 -9
- package/lib/mcp/tools/agent/propose.js +87 -64
- package/lib/mcp/tools/agent/replay.d.ts +10 -3
- package/lib/mcp/tools/agent/replay.js +29 -36
- package/lib/mcp/tools/agent/session.d.ts +1 -3
- package/lib/mcp/tools/agent/session.js +5 -5
- package/lib/mcp/tools/agent/shared.d.ts +5 -3
- package/lib/mcp/tools/agent/shared.js +29 -19
- package/lib/old/client.d.ts +39 -0
- package/lib/old/client.js +18 -0
- package/lib/old/login-cdp.d.ts +24 -5
- package/lib/old/login-cdp.js +29 -15
- package/lib/old/sdk-client.d.ts +39 -0
- package/lib/old/sdk-client.js +85 -0
- package/lib/old/to-register.d.ts +69 -0
- package/lib/old/to-register.js +55 -2
- package/lib/old/tools/authenticate.js +3 -7
- package/lib/old/tools/build.d.ts +6 -1
- package/lib/old/tools/build.js +11 -1
- package/lib/old/tools/get-provider-info.d.ts +16 -0
- package/lib/old/tools/get-provider-info.js +73 -0
- package/lib/old/tools/get-version-config.d.ts +17 -0
- package/lib/old/tools/get-version-config.js +47 -0
- package/lib/old/tools/publish.js +208 -2
- package/lib/old/tools/update-provider-metadata.d.ts +16 -0
- package/lib/old/tools/update-provider-metadata.js +61 -0
- package/lib/proof/attestor.d.ts +6 -3
- package/lib/proof/attestor.js +10 -1
- package/lib/proof/debug-response-dump.d.ts +4 -0
- package/lib/proof/debug-response-dump.js +55 -0
- package/lib/proof/patch-domhandler.d.ts +1 -0
- package/lib/proof/patch-domhandler.js +59 -0
- package/lib/proof/zk-files.js +7 -6
- package/lib/provider/dom-xpath.d.ts +17 -0
- package/lib/provider/dom-xpath.js +187 -0
- package/lib/provider/draft.d.ts +30 -2
- package/lib/provider/draft.js +117 -28
- package/lib/provider/index.d.ts +4 -0
- package/lib/provider/index.js +7 -0
- package/lib/provider/injection-doc.js +6 -0
- package/lib/provider/matcher.d.ts +17 -20
- package/lib/provider/matcher.js +394 -280
- package/lib/provider/schema.d.ts +7 -5
- package/lib/tools/registry.d.ts +14 -0
- package/lib/tools/registry.js +32 -0
- package/package.json +9 -1
- package/skill/SKILL.builder.md +42 -23
- package/skill/SKILL.old.md +6 -2
- package/skill/builder-mode.md +1 -1
- package/skill/injection-api.md +117 -0
- package/src/authoring/backend.ts +98 -0
- package/src/authoring/index.ts +20 -0
- package/src/authoring/tools.ts +273 -0
- package/src/authoring/util.ts +42 -0
- package/src/consts.ts +15 -2
- package/src/mcp/server.ts +11 -78
- package/src/mcp/start.ts +4 -1
- package/src/mcp/tools/agent/analyze.ts +3 -3
- package/src/mcp/tools/agent/build.ts +13 -21
- package/src/mcp/tools/agent/capture.ts +31 -115
- package/src/mcp/tools/agent/eval.ts +19 -155
- package/src/mcp/tools/agent/find.ts +33 -36
- package/src/mcp/tools/agent/proof.ts +87 -160
- package/src/mcp/tools/agent/propose.ts +93 -85
- package/src/mcp/tools/agent/replay.ts +32 -42
- package/src/mcp/tools/agent/session.ts +4 -5
- package/src/mcp/tools/agent/shared.ts +152 -0
- package/src/old/client.ts +60 -0
- package/src/old/login-cdp.ts +29 -23
- package/src/old/sdk-client.ts +105 -0
- package/src/old/to-register.ts +102 -6
- package/src/old/tools/authenticate.ts +3 -11
- package/src/old/tools/build.ts +12 -0
- package/src/old/tools/get-provider-info.ts +91 -0
- package/src/old/tools/get-version-config.ts +56 -0
- package/src/old/tools/publish.ts +264 -1
- package/src/old/tools/update-provider-metadata.ts +72 -0
- package/src/proof/attestor.ts +16 -4
- package/src/proof/debug-response-dump.ts +70 -0
- package/src/proof/patch-domhandler.ts +68 -0
- package/src/proof/zk-files.ts +7 -6
- package/src/provider/dom-xpath.ts +225 -0
- package/src/provider/draft.ts +168 -35
- package/src/provider/index.ts +8 -0
- package/src/provider/injection-doc.ts +6 -0
- package/src/provider/matcher.ts +477 -357
- package/src/provider/schema.ts +7 -5
- package/src/tools/registry.ts +92 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authoring tools, authored ONCE — each directly with the shared
|
|
3
|
+
* `defineTool` primitive (no parallel tool spec). `authoringTools(resolve)`
|
|
4
|
+
* returns them as plain {@link RegisteredTool}s; the MCP (local Chrome +
|
|
5
|
+
* attestor) and the cloud loop (Popcorn + TEE) each pass their own backend
|
|
6
|
+
* resolver, so navigate/inspect/propose/replay/prove logic lives in one place.
|
|
7
|
+
*
|
|
8
|
+
* The resolver hands back the backend per call — the MCP keys it by `captureId`
|
|
9
|
+
* (multi-session), the cloud returns its one run backend. A host that needs
|
|
10
|
+
* extra input schema (the MCP's `captureId` + rich `run_proof` contract) MAPS
|
|
11
|
+
* these tools through `extendTool` at registration; the tools stay plain.
|
|
12
|
+
*
|
|
13
|
+
* Secret guarantee: captured cookie/auth values never enter a tool result. The
|
|
14
|
+
* draft (recipe + secrets) is held server-side; the model only sees an opaque
|
|
15
|
+
* `draftId` and the secret-free provider.
|
|
16
|
+
*/
|
|
17
|
+
import { findTool } from '../mcp/tools/agent/find.ts'
|
|
18
|
+
import { proposeTool } from '../mcp/tools/agent/propose.ts'
|
|
19
|
+
import { replayTool } from '../mcp/tools/agent/replay.ts'
|
|
20
|
+
import type { CapturedRequest } from '../provider/schema.ts'
|
|
21
|
+
import { defineTool, type RegisteredTool } from '../tools/registry.ts'
|
|
22
|
+
import type { AuthoringBackend } from './backend.ts'
|
|
23
|
+
import { objectSchema, publicRequest, type ResolveBackend } from './util.ts'
|
|
24
|
+
|
|
25
|
+
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
|
|
26
|
+
|
|
27
|
+
interface RequestFilter {
|
|
28
|
+
method?: string
|
|
29
|
+
urlGlob?: string
|
|
30
|
+
statusMin?: number
|
|
31
|
+
statusMax?: number
|
|
32
|
+
contentType?: string
|
|
33
|
+
graphqlOp?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function matchesFilter(r: CapturedRequest, f: RequestFilter): boolean {
|
|
37
|
+
if(f.method && r.method !== f.method.toUpperCase()) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if(f.urlGlob && !globMatch(f.urlGlob, r.url)) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if(f.statusMin !== undefined && r.status < f.statusMin) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if(f.statusMax !== undefined && r.status > f.statusMax) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if(f.contentType && !r.contentType.includes(f.contentType)) {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if(f.graphqlOp && r.graphqlOp !== f.graphqlOp) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function globMatch(pattern: string, str: string): boolean {
|
|
65
|
+
const re = new RegExp(
|
|
66
|
+
'^'
|
|
67
|
+
+ pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')
|
|
68
|
+
+ '$',
|
|
69
|
+
)
|
|
70
|
+
return re.test(str)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface EvalResult {
|
|
74
|
+
result?: { value?: unknown }
|
|
75
|
+
exceptionDetails?: { exception?: { description?: string }, text?: string }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function evalInPage(
|
|
79
|
+
backend: AuthoringBackend,
|
|
80
|
+
expression: string,
|
|
81
|
+
): Promise<{ value?: unknown, error?: string }> {
|
|
82
|
+
const res = (await backend.sendCdp('Runtime.evaluate', {
|
|
83
|
+
expression, returnByValue: true, awaitPromise: true,
|
|
84
|
+
})) as EvalResult
|
|
85
|
+
if(res.exceptionDetails) {
|
|
86
|
+
return {
|
|
87
|
+
error: res.exceptionDetails.exception?.description
|
|
88
|
+
?? res.exceptionDetails.text
|
|
89
|
+
?? 'evaluation error',
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { value: res.result?.value }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The authoring tools — navigate/inspect authored inline here; find/propose/
|
|
98
|
+
* replay live in their own files ({@link findTool}/{@link proposeTool}/
|
|
99
|
+
* {@link replayTool}) and are composed in below. Each is a plain `defineTool`
|
|
100
|
+
* primitive, so there is one canonical `RegisteredTool` per tool and no
|
|
101
|
+
* parallel tool shape. Every handler resolves its session backend via
|
|
102
|
+
* `resolveBackend(args)`: the cloud passes `() => backend` (one backend per
|
|
103
|
+
* run); the MCP passes an `args.captureId`-keyed resolver. Hosts that need
|
|
104
|
+
* extra input schema (the MCP's `captureId` session handle + the
|
|
105
|
+
* credential-aware `run_proof` contract) MAP these tools through `extendTool`
|
|
106
|
+
* (see mcp/tools/agent/shared.ts) — the tools themselves stay plain, and args
|
|
107
|
+
* parse loosely so the extra fields still reach the resolver/handler.
|
|
108
|
+
*/
|
|
109
|
+
export function authoringTools(
|
|
110
|
+
resolveBackend: ResolveBackend,
|
|
111
|
+
): RegisteredTool[] {
|
|
112
|
+
return [
|
|
113
|
+
defineTool<Record<string, unknown>>(
|
|
114
|
+
{
|
|
115
|
+
name: 'navigate',
|
|
116
|
+
description: 'Navigate the browser tab to a URL, waiting for load.',
|
|
117
|
+
inputSchema: objectSchema(
|
|
118
|
+
{ url: { type: 'string' }, timeoutMs: { type: 'number' } },
|
|
119
|
+
['url'],
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
async(args) => {
|
|
123
|
+
const backend = resolveBackend(args)
|
|
124
|
+
const timeoutMs = Number(args.timeoutMs) || 30_000
|
|
125
|
+
await backend.sendCdp('Page.enable')
|
|
126
|
+
// Start listening BEFORE navigating so the load event isn't missed.
|
|
127
|
+
const load = backend.waitForEvent('Page.loadEventFired', timeoutMs)
|
|
128
|
+
const nav = (await backend.sendCdp(
|
|
129
|
+
'Page.navigate', { url: String(args.url) },
|
|
130
|
+
)) as { errorText?: string }
|
|
131
|
+
if(nav.errorText) {
|
|
132
|
+
return { ok: false, error: nav.errorText }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const loadStatus = await load
|
|
136
|
+
const final = await evalInPage(backend, 'location.href')
|
|
137
|
+
return { ok: true, loadStatus, finalUrl: final.value }
|
|
138
|
+
},
|
|
139
|
+
),
|
|
140
|
+
defineTool<Record<string, unknown>>(
|
|
141
|
+
{
|
|
142
|
+
name: 'eval_in_page',
|
|
143
|
+
description:
|
|
144
|
+
'Evaluate a JavaScript expression in the page and return its value '
|
|
145
|
+
+ '(use to read the displayed value, check login state, etc.).',
|
|
146
|
+
inputSchema: objectSchema(
|
|
147
|
+
{ expression: { type: 'string' } }, ['expression'],
|
|
148
|
+
),
|
|
149
|
+
},
|
|
150
|
+
(args) => evalInPage(resolveBackend(args), String(args.expression)),
|
|
151
|
+
),
|
|
152
|
+
defineTool<Record<string, unknown>>(
|
|
153
|
+
{
|
|
154
|
+
name: 'wait_for_page',
|
|
155
|
+
description:
|
|
156
|
+
'Poll a JavaScript expression until it is truthy or the timeout '
|
|
157
|
+
+ 'elapses (e.g. wait for the user to finish logging in).',
|
|
158
|
+
inputSchema: objectSchema(
|
|
159
|
+
{ expression: { type: 'string' }, timeoutMs: { type: 'number' } },
|
|
160
|
+
['expression'],
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
async(args) => {
|
|
164
|
+
const backend = resolveBackend(args)
|
|
165
|
+
const expr = String(args.expression)
|
|
166
|
+
const deadline = Date.now() + (Number(args.timeoutMs) || 180_000)
|
|
167
|
+
while(Date.now() < deadline) {
|
|
168
|
+
const { value } = await evalInPage(backend, expr)
|
|
169
|
+
if(value) {
|
|
170
|
+
return { ready: true }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await sleep(1000)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ready: false }
|
|
177
|
+
},
|
|
178
|
+
),
|
|
179
|
+
defineTool<Record<string, unknown>>(
|
|
180
|
+
{
|
|
181
|
+
name: 'get_cookies',
|
|
182
|
+
description:
|
|
183
|
+
'List the NAMES of cookies currently set for the page (values are '
|
|
184
|
+
+ 'NEVER returned). The most reliable, language- and DOM-independent '
|
|
185
|
+
+ 'login signal: after the user logs in, an auth/session cookie '
|
|
186
|
+
+ 'appears (e.g. session/sid/token/auth/jwt/_user/logged_in). '
|
|
187
|
+
+ 'Includes httpOnly cookies the page JS cannot see.',
|
|
188
|
+
inputSchema: objectSchema({}),
|
|
189
|
+
},
|
|
190
|
+
async(args) => {
|
|
191
|
+
const backend = resolveBackend(args)
|
|
192
|
+
const res = (await backend.sendCdp('Network.getCookies')) as {
|
|
193
|
+
cookies?: {
|
|
194
|
+
name: string
|
|
195
|
+
domain?: string
|
|
196
|
+
session?: boolean
|
|
197
|
+
httpOnly?: boolean
|
|
198
|
+
}[]
|
|
199
|
+
}
|
|
200
|
+
return (res.cookies ?? []).map((c) => ({
|
|
201
|
+
name: c.name,
|
|
202
|
+
...(c.domain ? { domain: c.domain } : {}),
|
|
203
|
+
...(c.session ? { session: true } : {}),
|
|
204
|
+
...(c.httpOnly ? { httpOnly: true } : {}),
|
|
205
|
+
}))
|
|
206
|
+
},
|
|
207
|
+
),
|
|
208
|
+
defineTool<Record<string, unknown>>(
|
|
209
|
+
{
|
|
210
|
+
name: 'list_requests',
|
|
211
|
+
description:
|
|
212
|
+
'List captured network requests (method/url/status/size), newest '
|
|
213
|
+
+ 'last. Optionally filter by method / urlGlob / status range / '
|
|
214
|
+
+ 'contentType / graphqlOp. Metadata only — secrets never included.',
|
|
215
|
+
inputSchema: objectSchema({
|
|
216
|
+
filter: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
method: { type: 'string' },
|
|
220
|
+
urlGlob: { type: 'string' },
|
|
221
|
+
statusMin: { type: 'number' },
|
|
222
|
+
statusMax: { type: 'number' },
|
|
223
|
+
contentType: { type: 'string' },
|
|
224
|
+
graphqlOp: { type: 'string' },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
},
|
|
229
|
+
async(args) => {
|
|
230
|
+
const backend = resolveBackend(args)
|
|
231
|
+
const filter = (args.filter ?? {}) as RequestFilter
|
|
232
|
+
return [...backend.capture.requests.values()]
|
|
233
|
+
.filter((r) => matchesFilter(r, filter))
|
|
234
|
+
.map(publicRequest)
|
|
235
|
+
},
|
|
236
|
+
),
|
|
237
|
+
findTool(resolveBackend),
|
|
238
|
+
proposeTool(resolveBackend),
|
|
239
|
+
replayTool(resolveBackend),
|
|
240
|
+
defineTool<Record<string, unknown>>(
|
|
241
|
+
{
|
|
242
|
+
name: 'run_proof',
|
|
243
|
+
description:
|
|
244
|
+
'Prove the drafted recipe (zkTLS). Only call after replay_request '
|
|
245
|
+
+ 'looks right. Returns the proof result.',
|
|
246
|
+
inputSchema: objectSchema({ draftId: { type: 'string' } }, ['draftId']),
|
|
247
|
+
},
|
|
248
|
+
// The backend shapes its own result: the cloud returns a lean,
|
|
249
|
+
// secret-free summary (and records the proven recipe for publish); the
|
|
250
|
+
// MCP persists the claim + returns the rich developer view. The MCP adds
|
|
251
|
+
// `ownerAddress`/`attestorUrl` to the schema via `extendTool`; the
|
|
252
|
+
// handler reads them from args (loose parse keeps them).
|
|
253
|
+
async(args) => {
|
|
254
|
+
const backend = resolveBackend(args)
|
|
255
|
+
const draft = backend.drafts.get(String(args.draftId))
|
|
256
|
+
if(!draft) {
|
|
257
|
+
throw new Error(`no draft ${String(args.draftId)}`)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const opts: { ownerAddress?: string, attestorUrl?: string } = {}
|
|
261
|
+
if(typeof args.ownerAddress === 'string') {
|
|
262
|
+
opts.ownerAddress = args.ownerAddress
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if(typeof args.attestorUrl === 'string') {
|
|
266
|
+
opts.attestorUrl = args.attestorUrl
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return backend.prove(draft, opts)
|
|
270
|
+
},
|
|
271
|
+
),
|
|
272
|
+
]
|
|
273
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared helpers for the authoring tools (see tools.ts). Kept separate so
|
|
3
|
+
* the per-tool files (find.ts, propose.ts, replay.ts) and the inline tools in
|
|
4
|
+
* tools.ts share one copy without a circular import.
|
|
5
|
+
*/
|
|
6
|
+
import type { JSONSchema } from 'zod/v4/core'
|
|
7
|
+
import type { CapturedRequest } from '../provider/schema.ts'
|
|
8
|
+
import type { AuthoringBackend } from './backend.ts'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the session backend for a tool call. The MCP keys it by
|
|
12
|
+
* `args.captureId` (multi-session); the cloud loop returns its one run backend.
|
|
13
|
+
* Each authoring tool closes over this so the same tool works for both hosts.
|
|
14
|
+
*/
|
|
15
|
+
export type ResolveBackend =
|
|
16
|
+
(args: Record<string, unknown>) => AuthoringBackend
|
|
17
|
+
|
|
18
|
+
/** Build an `object` JSON Schema for a tool input — a small literal shortener,
|
|
19
|
+
* not a tool abstraction. */
|
|
20
|
+
export function objectSchema(
|
|
21
|
+
properties: Record<string, unknown>,
|
|
22
|
+
required?: string[],
|
|
23
|
+
): JSONSchema.ObjectSchema {
|
|
24
|
+
return {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties,
|
|
27
|
+
...(required?.length ? { required } : {}),
|
|
28
|
+
} as unknown as JSONSchema.ObjectSchema
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Secret-free projection of a captured request for tool output. */
|
|
32
|
+
export function publicRequest(r: CapturedRequest) {
|
|
33
|
+
return {
|
|
34
|
+
requestId: r.requestId,
|
|
35
|
+
method: r.method,
|
|
36
|
+
url: r.url,
|
|
37
|
+
status: r.status,
|
|
38
|
+
contentType: r.contentType,
|
|
39
|
+
size: r.size,
|
|
40
|
+
...(r.graphqlOp ? { graphqlOp: r.graphqlOp } : {}),
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/consts.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { DEFAULT_BASE_URL } from '@reclaimprotocol/client'
|
|
|
19
19
|
import { homedir } from 'node:os'
|
|
20
20
|
import { join } from 'node:path'
|
|
21
21
|
import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from './old/client.ts'
|
|
22
|
+
import { DEFAULT_SDK_API_URL } from './old/sdk-client.ts'
|
|
22
23
|
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Default / fallback literals (used only as env fallbacks).
|
|
@@ -30,9 +31,14 @@ export const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws'
|
|
|
30
31
|
/** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
|
|
31
32
|
export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org'
|
|
32
33
|
|
|
33
|
-
// Re-export the
|
|
34
|
+
// Re-export the non-env-fallback defaults so callers can keep importing
|
|
34
35
|
// their URL defaults from a single place if they wish.
|
|
35
|
-
export {
|
|
36
|
+
export {
|
|
37
|
+
DEFAULT_BASE_URL,
|
|
38
|
+
DEFAULT_OLD_API_URL,
|
|
39
|
+
DEFAULT_OLD_LOGS_URL,
|
|
40
|
+
DEFAULT_SDK_API_URL,
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
// ---------------------------------------------------------------------------
|
|
38
44
|
// Logging.
|
|
@@ -101,6 +107,13 @@ export function oldLogsUrl(): string {
|
|
|
101
107
|
return process.env.RECLAIM_OLD_LOGS_URL || DEFAULT_OLD_LOGS_URL
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
/** Public production SDK-backend base URL — the service the verification
|
|
111
|
+
* SDK/attestor itself reads a provider's recipe from at runtime. A
|
|
112
|
+
* DIFFERENT host from the old-devtools dashboard backend (`oldApiUrl`). */
|
|
113
|
+
export function sdkApiUrl(): string {
|
|
114
|
+
return process.env.RECLAIM_SDK_API_URL || DEFAULT_SDK_API_URL
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
/** Builder backend base URL. Shared by mcp/start and the verification tool. */
|
|
105
118
|
export function apiUrl(): string {
|
|
106
119
|
return process.env.RECLAIM_API_URL || DEFAULT_BASE_URL
|
package/src/mcp/server.ts
CHANGED
|
@@ -1,65 +1,16 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
-
import type
|
|
2
|
+
import { type RegisteredTool } from '../tools/registry.ts'
|
|
3
3
|
import { toZod } from './api/to-zod.ts'
|
|
4
4
|
import { remapErrorAsResponse } from './remap_errors.ts'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* human-in-the-loop tools (e.g. a browser login flow that waits for someone
|
|
15
|
-
* to sign in) should call `reportProgress` every few seconds while waiting.
|
|
16
|
-
* MCP clients that attach a progress token to their request (most desktop
|
|
17
|
-
* clients do) treat an incoming `notifications/progress` as proof the call
|
|
18
|
-
* is still alive and reset their own request timeout instead of giving up —
|
|
19
|
-
* the SDK's client-side default is 60s, far shorter than a human can be
|
|
20
|
-
* expected to take to log in. A no-op when the client didn't request
|
|
21
|
-
* progress tracking for this call.
|
|
22
|
-
*/
|
|
23
|
-
export interface ToolContext {
|
|
24
|
-
reportProgress: (message: string) => Promise<void>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const NOOP_CONTEXT: ToolContext = { reportProgress: async() => {} }
|
|
28
|
-
|
|
29
|
-
export interface RegisteredTool {
|
|
30
|
-
definition: ToolDefinition
|
|
31
|
-
/**
|
|
32
|
-
* @param args Tool arguments
|
|
33
|
-
* @param ctx Progress-reporting context (see {@link ToolContext}).
|
|
34
|
-
* Optional so direct/test callers can omit it.
|
|
35
|
-
* @returns A JSON stringifiable content to be returned to the llm
|
|
36
|
-
* @throws Any error encountered during tool execution will be caught and
|
|
37
|
-
* returned as a response using
|
|
38
|
-
* [remapErrorAsResponse](./src/mcp/remap_errors.ts).
|
|
39
|
-
*/
|
|
40
|
-
handler: (args: unknown, ctx?: ToolContext) => Promise<unknown>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Define a tool with a single inputSchema (JSON Schema) used as the
|
|
45
|
-
* source of truth. The handler receives args already parsed and
|
|
46
|
-
* type-narrowed via a Zod schema derived from that same JSON Schema —
|
|
47
|
-
* so the description sent to the MCP client and the validation applied
|
|
48
|
-
* to inbound args cannot drift.
|
|
49
|
-
*/
|
|
50
|
-
export function defineTool<TArgs>(
|
|
51
|
-
definition: ToolDefinition,
|
|
52
|
-
handler: (args: TArgs, ctx: ToolContext) => Promise<unknown>,
|
|
53
|
-
): RegisteredTool {
|
|
54
|
-
const schema = toZod(definition.inputSchema)
|
|
55
|
-
return {
|
|
56
|
-
definition,
|
|
57
|
-
handler: async(raw, ctx = NOOP_CONTEXT) => {
|
|
58
|
-
const args = schema.parse(raw) as TArgs
|
|
59
|
-
return await handler(args, ctx)
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
}
|
|
6
|
+
// The tool primitive lives in `../tools/registry.ts` (SDK-free, so the cloud
|
|
7
|
+
// loop can use it too). Re-exported here so the MCP-side tool files can keep
|
|
8
|
+
// importing `defineTool`/`RegisteredTool` from the server module.
|
|
9
|
+
export {
|
|
10
|
+
defineTool,
|
|
11
|
+
type RegisteredTool,
|
|
12
|
+
type ToolDefinition,
|
|
13
|
+
} from '../tools/registry.ts'
|
|
63
14
|
|
|
64
15
|
/**
|
|
65
16
|
* Registers a list of RegisteredTool entries on a high-level McpServer.
|
|
@@ -74,31 +25,13 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
|
|
|
74
25
|
description: tool.definition.description,
|
|
75
26
|
inputSchema: toZod(tool.definition.inputSchema),
|
|
76
27
|
},
|
|
77
|
-
async(args
|
|
28
|
+
async(args) => {
|
|
78
29
|
try {
|
|
79
|
-
const progressToken = extra?._meta?.progressToken
|
|
80
|
-
let progress = 0
|
|
81
|
-
const ctx: ToolContext = {
|
|
82
|
-
reportProgress: async(message) => {
|
|
83
|
-
if(progressToken === undefined) {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
progress += 1
|
|
88
|
-
await extra.sendNotification({
|
|
89
|
-
method: 'notifications/progress',
|
|
90
|
-
params: { progressToken, progress, message },
|
|
91
|
-
}).catch(() => {})
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
|
|
95
30
|
return {
|
|
96
31
|
content: [
|
|
97
32
|
{
|
|
98
33
|
type: 'text' as const,
|
|
99
|
-
text: JSON.stringify(
|
|
100
|
-
await tool.handler(args, ctx), null, 2,
|
|
101
|
-
),
|
|
34
|
+
text: JSON.stringify(await tool.handler(args), null, 2),
|
|
102
35
|
},
|
|
103
36
|
],
|
|
104
37
|
}
|
package/src/mcp/start.ts
CHANGED
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
apiUrl,
|
|
8
8
|
oldApiUrl,
|
|
9
9
|
oldLogsUrl,
|
|
10
|
+
sdkApiUrl,
|
|
10
11
|
useOldDevtools,
|
|
11
12
|
} from '../consts.ts'
|
|
12
13
|
import { ReclaimOldClient } from '../old/client.ts'
|
|
13
14
|
import { resolveInitialIdentity } from '../old/identity.ts'
|
|
14
15
|
import { OLD_MODE_INSTRUCTIONS } from '../old/instructions.ts'
|
|
16
|
+
import { ReclaimSdkClient } from '../old/sdk-client.ts'
|
|
15
17
|
import { buildOldBackendTools } from '../old/tools/build.ts'
|
|
16
18
|
import { NAME, VERSION } from '../pkg.ts'
|
|
17
19
|
import { registerTools } from './api/tools.ts'
|
|
@@ -53,9 +55,10 @@ export async function startMcpServer() {
|
|
|
53
55
|
logsBaseUrl: oldLogsUrl(),
|
|
54
56
|
identity: resolveInitialIdentity(),
|
|
55
57
|
})
|
|
58
|
+
const sdkClient = new ReclaimSdkClient({ baseUrl: sdkApiUrl() })
|
|
56
59
|
|
|
57
60
|
registerToolList(server, buildLocalCredentialTools())
|
|
58
|
-
registerToolList(server, buildOldBackendTools(oldClient))
|
|
61
|
+
registerToolList(server, buildOldBackendTools(oldClient, sdkClient))
|
|
59
62
|
if(agentToolsEnabled) {
|
|
60
63
|
// No builder client → no builder publish tool (old mode has its own).
|
|
61
64
|
registerToolList(server, buildAgentTools())
|
|
@@ -18,12 +18,12 @@ export function analyzeTool(captures: CaptureStore): RegisteredTool {
|
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
async({ captureId, requestId }) => {
|
|
21
|
-
const
|
|
22
|
-
if(!
|
|
21
|
+
const entry = captures.sessions.get(captureId)
|
|
22
|
+
if(!entry) {
|
|
23
23
|
throw new Error(`no capture ${captureId}`)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const r = session.requests.get(requestId)
|
|
26
|
+
const r = entry.session.requests.get(requestId)
|
|
27
27
|
if(!r) {
|
|
28
28
|
throw new Error(`no request ${requestId} in capture ${captureId}`)
|
|
29
29
|
}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* `@modelcontextprotocol/sdk` `McpServer`. Folds the agent's tools
|
|
5
5
|
* alongside the auto-generated API tools.
|
|
6
6
|
*
|
|
7
|
-
* Shared state — Chrome attach state
|
|
8
|
-
*
|
|
7
|
+
* Shared state — Chrome attach state + capture store — is built once per
|
|
8
|
+
* server and threaded through every tool factory. Per-capture drafts (and the
|
|
9
|
+
* secrets they hold) live inside each capture entry, so a capture IS the
|
|
10
|
+
* session for the shared authoring tools.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import type { ReclaimClient } from '@reclaimprotocol/client'
|
|
@@ -15,30 +17,21 @@ import { type RegisteredTool } from '../../server.ts'
|
|
|
15
17
|
import { analyzeTool } from './analyze.ts'
|
|
16
18
|
import { type AttachState, attachTool } from './attach.ts'
|
|
17
19
|
import { type CaptureStore, captureTools } from './capture.ts'
|
|
18
|
-
import {
|
|
19
|
-
import { findTool } from './find.ts'
|
|
20
|
+
import { listTabsTool } from './eval.ts'
|
|
20
21
|
import { howItWorksTool } from './how-it-works.ts'
|
|
21
|
-
import { proofTool } from './proof.ts'
|
|
22
|
-
import {
|
|
23
|
-
makeSecretStore,
|
|
24
|
-
proposeTool,
|
|
25
|
-
type SecretStore,
|
|
26
|
-
} from './propose.ts'
|
|
27
22
|
import { createVersionFromCaptureTool } from './publish.ts'
|
|
28
|
-
import { replayTool } from './replay.ts'
|
|
29
23
|
import { sessionTools } from './session.ts'
|
|
24
|
+
import { sharedAuthoringTools } from './shared.ts'
|
|
30
25
|
|
|
31
26
|
export interface AgentState {
|
|
32
27
|
attach: { current?: AttachState, config: ConfigStore }
|
|
33
28
|
captures: CaptureStore
|
|
34
|
-
secrets: SecretStore
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
function buildAgentState(): AgentState {
|
|
38
32
|
return {
|
|
39
33
|
attach: { config: new ConfigStore() },
|
|
40
34
|
captures: { sessions: new Map() },
|
|
41
|
-
secrets: makeSecretStore(),
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
37
|
|
|
@@ -57,24 +50,23 @@ export function buildAgentTools(
|
|
|
57
50
|
// client. No `client` ⇒ old-devtools mode (see `mcp/start.ts`).
|
|
58
51
|
tools.push(howItWorksTool(!client))
|
|
59
52
|
tools.push(attachTool(state.attach))
|
|
53
|
+
tools.push(listTabsTool(state.attach))
|
|
60
54
|
for(const t of captureTools(state.attach, state.captures)) {
|
|
61
55
|
tools.push(t)
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
for(const t of browserTools(state.attach)) {
|
|
58
|
+
for(const t of sessionTools(state.attach, state.captures)) {
|
|
66
59
|
tools.push(t)
|
|
67
60
|
}
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
tools.push(analyzeTool(state.captures))
|
|
63
|
+
// navigate / eval_in_page / wait_for_page / get_cookies / list_requests /
|
|
64
|
+
// find_requests_containing / propose_provider / replay_request / run_proof —
|
|
65
|
+
// shared with the cloud loop, bound to each capture's local backend.
|
|
66
|
+
for(const t of sharedAuthoringTools(state.captures)) {
|
|
70
67
|
tools.push(t)
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
tools.push(analyzeTool(state.captures))
|
|
74
|
-
tools.push(proposeTool(state.captures, state.secrets))
|
|
75
|
-
tools.push(replayTool(state.secrets))
|
|
76
|
-
tools.push(proofTool(state.secrets))
|
|
77
|
-
|
|
78
70
|
if(client) {
|
|
79
71
|
tools.push(createVersionFromCaptureTool(client))
|
|
80
72
|
}
|