@reclaimprotocol/agent 0.1.5 → 0.1.6

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 (117) hide show
  1. package/lib/authoring/backend.d.ts +0 -24
  2. package/lib/authoring/find.d.ts +3 -0
  3. package/lib/authoring/find.js +54 -0
  4. package/lib/authoring/index.d.ts +3 -3
  5. package/lib/authoring/index.js +4 -3
  6. package/lib/authoring/propose.d.ts +3 -0
  7. package/lib/authoring/propose.js +109 -0
  8. package/lib/authoring/replay.d.ts +10 -0
  9. package/lib/authoring/replay.js +34 -0
  10. package/lib/authoring/tools.d.ts +13 -6
  11. package/lib/authoring/tools.js +108 -293
  12. package/lib/authoring/util.d.ts +27 -0
  13. package/lib/authoring/util.js +21 -0
  14. package/lib/consts.d.ts +6 -1
  15. package/lib/consts.js +9 -2
  16. package/lib/mcp/server.d.ts +2 -41
  17. package/lib/mcp/server.js +6 -34
  18. package/lib/mcp/start.js +4 -2
  19. package/lib/mcp/tools/agent/analyze.js +3 -3
  20. package/lib/mcp/tools/agent/build.d.ts +4 -4
  21. package/lib/mcp/tools/agent/build.js +13 -15
  22. package/lib/mcp/tools/agent/capture.d.ts +13 -1
  23. package/lib/mcp/tools/agent/capture.js +18 -85
  24. package/lib/mcp/tools/agent/eval.d.ts +5 -2
  25. package/lib/mcp/tools/agent/eval.js +11 -121
  26. package/lib/mcp/tools/agent/find.d.ts +10 -3
  27. package/lib/mcp/tools/agent/find.js +25 -31
  28. package/lib/mcp/tools/agent/proof.d.ts +13 -3
  29. package/lib/mcp/tools/agent/proof.js +81 -132
  30. package/lib/mcp/tools/agent/propose.d.ts +10 -9
  31. package/lib/mcp/tools/agent/propose.js +87 -64
  32. package/lib/mcp/tools/agent/replay.d.ts +10 -3
  33. package/lib/mcp/tools/agent/replay.js +29 -36
  34. package/lib/mcp/tools/agent/session.d.ts +1 -3
  35. package/lib/mcp/tools/agent/session.js +5 -5
  36. package/lib/mcp/tools/agent/shared.d.ts +5 -3
  37. package/lib/mcp/tools/agent/shared.js +29 -19
  38. package/lib/old/client.d.ts +35 -0
  39. package/lib/old/client.js +18 -0
  40. package/lib/old/login-cdp.d.ts +24 -5
  41. package/lib/old/login-cdp.js +29 -15
  42. package/lib/old/sdk-client.d.ts +39 -0
  43. package/lib/old/sdk-client.js +85 -0
  44. package/lib/old/to-register.d.ts +69 -0
  45. package/lib/old/to-register.js +52 -2
  46. package/lib/old/tools/authenticate.js +3 -7
  47. package/lib/old/tools/build.d.ts +6 -1
  48. package/lib/old/tools/build.js +11 -1
  49. package/lib/old/tools/get-provider-info.d.ts +16 -0
  50. package/lib/old/tools/get-provider-info.js +73 -0
  51. package/lib/old/tools/get-version-config.d.ts +17 -0
  52. package/lib/old/tools/get-version-config.js +47 -0
  53. package/lib/old/tools/publish.js +189 -2
  54. package/lib/old/tools/update-provider-metadata.d.ts +16 -0
  55. package/lib/old/tools/update-provider-metadata.js +61 -0
  56. package/lib/proof/attestor.d.ts +6 -3
  57. package/lib/proof/attestor.js +10 -1
  58. package/lib/proof/debug-response-dump.d.ts +4 -0
  59. package/lib/proof/debug-response-dump.js +55 -0
  60. package/lib/proof/patch-domhandler.d.ts +1 -0
  61. package/lib/proof/patch-domhandler.js +59 -0
  62. package/lib/proof/zk-files.js +7 -6
  63. package/lib/provider/dom-xpath.d.ts +17 -0
  64. package/lib/provider/dom-xpath.js +187 -0
  65. package/lib/provider/draft.d.ts +30 -2
  66. package/lib/provider/draft.js +117 -28
  67. package/lib/provider/index.d.ts +4 -0
  68. package/lib/provider/index.js +7 -0
  69. package/lib/provider/injection-doc.js +6 -0
  70. package/lib/provider/matcher.d.ts +17 -20
  71. package/lib/provider/matcher.js +394 -280
  72. package/lib/provider/schema.d.ts +7 -5
  73. package/lib/tools/registry.d.ts +14 -0
  74. package/lib/tools/registry.js +32 -0
  75. package/package.json +9 -1
  76. package/skill/SKILL.builder.md +42 -23
  77. package/skill/SKILL.old.md +6 -2
  78. package/skill/builder-mode.md +1 -1
  79. package/skill/injection-api.md +117 -0
  80. package/src/authoring/backend.ts +98 -0
  81. package/src/authoring/index.ts +20 -0
  82. package/src/authoring/tools.ts +273 -0
  83. package/src/authoring/util.ts +42 -0
  84. package/src/consts.ts +15 -2
  85. package/src/mcp/server.ts +11 -78
  86. package/src/mcp/start.ts +4 -1
  87. package/src/mcp/tools/agent/analyze.ts +3 -3
  88. package/src/mcp/tools/agent/build.ts +13 -21
  89. package/src/mcp/tools/agent/capture.ts +31 -115
  90. package/src/mcp/tools/agent/eval.ts +19 -155
  91. package/src/mcp/tools/agent/find.ts +33 -36
  92. package/src/mcp/tools/agent/proof.ts +87 -160
  93. package/src/mcp/tools/agent/propose.ts +93 -85
  94. package/src/mcp/tools/agent/replay.ts +32 -42
  95. package/src/mcp/tools/agent/session.ts +4 -5
  96. package/src/mcp/tools/agent/shared.ts +152 -0
  97. package/src/old/client.ts +56 -0
  98. package/src/old/login-cdp.ts +29 -23
  99. package/src/old/sdk-client.ts +105 -0
  100. package/src/old/to-register.ts +98 -6
  101. package/src/old/tools/authenticate.ts +3 -11
  102. package/src/old/tools/build.ts +12 -0
  103. package/src/old/tools/get-provider-info.ts +91 -0
  104. package/src/old/tools/get-version-config.ts +56 -0
  105. package/src/old/tools/publish.ts +244 -1
  106. package/src/old/tools/update-provider-metadata.ts +72 -0
  107. package/src/proof/attestor.ts +16 -4
  108. package/src/proof/debug-response-dump.ts +70 -0
  109. package/src/proof/patch-domhandler.ts +68 -0
  110. package/src/proof/zk-files.ts +7 -6
  111. package/src/provider/dom-xpath.ts +225 -0
  112. package/src/provider/draft.ts +168 -35
  113. package/src/provider/index.ts +8 -0
  114. package/src/provider/injection-doc.ts +6 -0
  115. package/src/provider/matcher.ts +477 -357
  116. package/src/provider/schema.ts +7 -5
  117. package/src/tools/registry.ts +92 -0
@@ -1,21 +1,25 @@
1
- import { draftProvider } from "../provider/draft.js";
2
- import { OPRF_MAX_BYTES } from "../provider/matcher.js";
3
- import { normalizeCandidates } from "../provider/normalize.js";
4
- import { verificationWarnings } from "../provider/verification-checks.js";
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.js";
18
+ import { proposeTool } from "../mcp/tools/agent/propose.js";
19
+ import { replayTool } from "../mcp/tools/agent/replay.js";
5
20
  import { defineTool } from "../tools/registry.js";
21
+ import { objectSchema, publicRequest } from "./util.js";
6
22
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
7
- /** Secret-free projection of a captured request for tool output. */
8
- function publicRequest(r) {
9
- return {
10
- requestId: r.requestId,
11
- method: r.method,
12
- url: r.url,
13
- status: r.status,
14
- contentType: r.contentType,
15
- size: r.size,
16
- ...(r.graphqlOp ? { graphqlOp: r.graphqlOp } : {}),
17
- };
18
- }
19
23
  function matchesFilter(r, f) {
20
24
  if (f.method && r.method !== f.method.toUpperCase()) {
21
25
  return false;
@@ -57,111 +61,86 @@ async function evalInPage(backend, expression) {
57
61
  return { value: res.result?.value };
58
62
  }
59
63
  /**
60
- * Build the authoring tools for a host as plain {@link RegisteredTool}s — the
61
- * same primitive the MCP-only tools use. Each handler resolves the backend via
62
- * `host.resolveBackend(args)`; the schema merges the host's `sessionSchema`
63
- * (e.g. `captureId`) and any per-tool `toolExtras`.
64
+ * The authoring tools — navigate/inspect authored inline here; find/propose/
65
+ * replay live in their own files ({@link findTool}/{@link proposeTool}/
66
+ * {@link replayTool}) and are composed in below. Each is a plain `defineTool`
67
+ * primitive, so there is one canonical `RegisteredTool` per tool and no
68
+ * parallel tool shape. Every handler resolves its session backend via
69
+ * `resolveBackend(args)`: the cloud passes `() => backend` (one backend per
70
+ * run); the MCP passes an `args.captureId`-keyed resolver. Hosts that need
71
+ * extra input schema (the MCP's `captureId` session handle + the
72
+ * credential-aware `run_proof` contract) MAP these tools through `extendTool`
73
+ * (see mcp/tools/agent/shared.ts) — the tools themselves stay plain, and args
74
+ * parse loosely so the extra fields still reach the resolver/handler.
64
75
  */
65
- export function buildAuthoringTools(host) {
66
- const mk = (spec) => {
67
- const extra = host.toolExtras?.[spec.name];
68
- const required = [
69
- ...(host.sessionSchema?.required ?? []),
70
- ...(spec.required ?? []),
71
- ...(extra?.required ?? []),
72
- ];
73
- const inputSchema = {
74
- type: 'object',
75
- properties: {
76
- ...(host.sessionSchema?.properties ?? {}),
77
- ...spec.properties,
78
- ...(extra?.properties ?? {}),
79
- },
80
- ...(required.length ? { required } : {}),
81
- };
82
- return defineTool({
83
- name: spec.name,
84
- description: extra?.description ?? spec.description,
85
- inputSchema,
86
- }, (args) => spec.run(host.resolveBackend(args), args));
87
- };
76
+ export function authoringTools(resolveBackend) {
88
77
  return [
89
- mk({
78
+ defineTool({
90
79
  name: 'navigate',
91
80
  description: 'Navigate the browser tab to a URL, waiting for load.',
92
- properties: {
93
- url: { type: 'string' },
94
- timeoutMs: { type: 'number' },
95
- },
96
- required: ['url'],
97
- run: async (backend, args) => {
98
- const timeoutMs = Number(args.timeoutMs) || 30_000;
99
- await backend.sendCdp('Page.enable');
100
- // Start listening BEFORE navigating so the load event isn't missed.
101
- const load = backend.waitForEvent('Page.loadEventFired', timeoutMs);
102
- const nav = (await backend.sendCdp('Page.navigate', { url: String(args.url) }));
103
- if (nav.errorText) {
104
- return { ok: false, error: nav.errorText };
105
- }
106
- const loadStatus = await load;
107
- const final = await evalInPage(backend, 'location.href');
108
- return { ok: true, loadStatus, finalUrl: final.value };
109
- },
81
+ inputSchema: objectSchema({ url: { type: 'string' }, timeoutMs: { type: 'number' } }, ['url']),
82
+ }, async (args) => {
83
+ const backend = resolveBackend(args);
84
+ const timeoutMs = Number(args.timeoutMs) || 30_000;
85
+ await backend.sendCdp('Page.enable');
86
+ // Start listening BEFORE navigating so the load event isn't missed.
87
+ const load = backend.waitForEvent('Page.loadEventFired', timeoutMs);
88
+ const nav = (await backend.sendCdp('Page.navigate', { url: String(args.url) }));
89
+ if (nav.errorText) {
90
+ return { ok: false, error: nav.errorText };
91
+ }
92
+ const loadStatus = await load;
93
+ const final = await evalInPage(backend, 'location.href');
94
+ return { ok: true, loadStatus, finalUrl: final.value };
110
95
  }),
111
- mk({
96
+ defineTool({
112
97
  name: 'eval_in_page',
113
98
  description: 'Evaluate a JavaScript expression in the page and return its value '
114
99
  + '(use to read the displayed value, check login state, etc.).',
115
- properties: { expression: { type: 'string' } },
116
- required: ['expression'],
117
- run: (backend, args) => evalInPage(backend, String(args.expression)),
118
- }),
119
- mk({
100
+ inputSchema: objectSchema({ expression: { type: 'string' } }, ['expression']),
101
+ }, (args) => evalInPage(resolveBackend(args), String(args.expression))),
102
+ defineTool({
120
103
  name: 'wait_for_page',
121
104
  description: 'Poll a JavaScript expression until it is truthy or the timeout '
122
105
  + 'elapses (e.g. wait for the user to finish logging in).',
123
- properties: {
124
- expression: { type: 'string' },
125
- timeoutMs: { type: 'number' },
126
- },
127
- required: ['expression'],
128
- run: async (backend, args) => {
129
- const expr = String(args.expression);
130
- const deadline = Date.now() + (Number(args.timeoutMs) || 180_000);
131
- while (Date.now() < deadline) {
132
- const { value } = await evalInPage(backend, expr);
133
- if (value) {
134
- return { ready: true };
135
- }
136
- await sleep(1000);
106
+ inputSchema: objectSchema({ expression: { type: 'string' }, timeoutMs: { type: 'number' } }, ['expression']),
107
+ }, async (args) => {
108
+ const backend = resolveBackend(args);
109
+ const expr = String(args.expression);
110
+ const deadline = Date.now() + (Number(args.timeoutMs) || 180_000);
111
+ while (Date.now() < deadline) {
112
+ const { value } = await evalInPage(backend, expr);
113
+ if (value) {
114
+ return { ready: true };
137
115
  }
138
- return { ready: false };
139
- },
116
+ await sleep(1000);
117
+ }
118
+ return { ready: false };
140
119
  }),
141
- mk({
120
+ defineTool({
142
121
  name: 'get_cookies',
143
122
  description: 'List the NAMES of cookies currently set for the page (values are '
144
123
  + 'NEVER returned). The most reliable, language- and DOM-independent '
145
124
  + 'login signal: after the user logs in, an auth/session cookie '
146
125
  + 'appears (e.g. session/sid/token/auth/jwt/_user/logged_in). '
147
126
  + 'Includes httpOnly cookies the page JS cannot see.',
148
- properties: {},
149
- run: async (backend) => {
150
- const res = (await backend.sendCdp('Network.getCookies'));
151
- return (res.cookies ?? []).map((c) => ({
152
- name: c.name,
153
- ...(c.domain ? { domain: c.domain } : {}),
154
- ...(c.session ? { session: true } : {}),
155
- ...(c.httpOnly ? { httpOnly: true } : {}),
156
- }));
157
- },
127
+ inputSchema: objectSchema({}),
128
+ }, async (args) => {
129
+ const backend = resolveBackend(args);
130
+ const res = (await backend.sendCdp('Network.getCookies'));
131
+ return (res.cookies ?? []).map((c) => ({
132
+ name: c.name,
133
+ ...(c.domain ? { domain: c.domain } : {}),
134
+ ...(c.session ? { session: true } : {}),
135
+ ...(c.httpOnly ? { httpOnly: true } : {}),
136
+ }));
158
137
  }),
159
- mk({
138
+ defineTool({
160
139
  name: 'list_requests',
161
140
  description: 'List captured network requests (method/url/status/size), newest '
162
141
  + 'last. Optionally filter by method / urlGlob / status range / '
163
142
  + 'contentType / graphqlOp. Metadata only — secrets never included.',
164
- properties: {
143
+ inputSchema: objectSchema({
165
144
  filter: {
166
145
  type: 'object',
167
146
  properties: {
@@ -173,206 +152,42 @@ export function buildAuthoringTools(host) {
173
152
  graphqlOp: { type: 'string' },
174
153
  },
175
154
  },
176
- },
177
- run: async (backend, args) => {
178
- const filter = (args.filter ?? {});
179
- return [...backend.capture.requests.values()]
180
- .filter((r) => matchesFilter(r, filter))
181
- .map(publicRequest);
182
- },
155
+ }),
156
+ }, async (args) => {
157
+ const backend = resolveBackend(args);
158
+ const filter = (args.filter ?? {});
159
+ return [...backend.capture.requests.values()]
160
+ .filter((r) => matchesFilter(r, filter))
161
+ .map(publicRequest);
183
162
  }),
184
- mk({
185
- name: 'find_requests_containing',
186
- description: 'Search captured responses for any encoding of `value` (raw, '
187
- + 'URL/JSON/HTML-escaped, …). Returns a ranked candidate list with '
188
- + 'the matched variant, score, and a short snippet — used to locate '
189
- + 'the request that returns a target. Server-side; bodies stay here.',
190
- properties: {
191
- value: { type: 'string' },
192
- limit: { type: 'number' },
193
- },
194
- required: ['value'],
195
- run: async (backend, args) => {
196
- const variants = normalizeCandidates(String(args.value));
197
- const ranked = [];
198
- for (const r of backend.capture.requests.values()) {
199
- if (!r.responseBody) {
200
- continue;
201
- }
202
- let hit;
203
- for (const v of variants) {
204
- const idx = r.responseBody.indexOf(v);
205
- if (idx >= 0) {
206
- const start = Math.max(0, idx - 40);
207
- const end = Math.min(r.responseBody.length, idx + v.length + 40);
208
- hit = {
209
- snippet: r.responseBody.slice(start, end), variant: v,
210
- };
211
- break;
212
- }
213
- }
214
- if (!hit) {
215
- continue;
216
- }
217
- // Prefer JSON responses + smaller bodies (likely API endpoints).
218
- const score = (r.contentType.includes('json') ? 10 : 0)
219
- + Math.max(0, 5 - Math.log10(Math.max(r.size, 1)));
220
- ranked.push({
221
- ...publicRequest(r),
222
- matchedVariant: hit.variant,
223
- snippet: hit.snippet,
224
- score,
225
- });
226
- }
227
- ranked.sort((a, b) => b.score - a.score);
228
- const limit = Number(args.limit) || 10;
229
- return { candidates: ranked.slice(0, limit) };
230
- },
231
- }),
232
- mk({
233
- name: 'propose_provider',
234
- description: 'Draft a provider from ONE captured request. Pass `targets` (an '
235
- + 'array): one entry per value to extract, each with its `value` '
236
- + '(verbatim) and a distinct `name` (its param). The provider gets '
237
- + 'one match + redaction per target. Returns a draftId + recipe '
238
- + '(never secrets). Prefer a SINGLE request holding ALL values. '
239
- + 'Single-value shorthand: `target` + `name`. Per-target `hash` '
240
- + 'OPRF-hashes that value.',
241
- properties: {
242
- requestId: { type: 'string' },
243
- name: { type: 'string' },
244
- targets: {
245
- type: 'array',
246
- items: {
247
- type: 'object',
248
- properties: {
249
- value: { type: 'string' },
250
- name: { type: 'string' },
251
- hash: {
252
- type: 'string',
253
- enum: ['oprf-raw', 'oprf', 'oprf-mpc'],
254
- },
255
- },
256
- required: ['value', 'name'],
257
- },
258
- },
259
- target: { type: 'string' },
260
- hash: { type: 'string', enum: ['oprf-raw', 'oprf', 'oprf-mpc'] },
261
- },
262
- required: ['requestId', 'name'],
263
- run: async (backend, args) => {
264
- const requestId = String(args.requestId);
265
- const providerName = String(args.name);
266
- const defaultHash = args.hash;
267
- // Explicit multi-value `targets`, else the single `target` shorthand.
268
- const raw = Array.isArray(args.targets) ? args.targets : [];
269
- const targets = raw.length
270
- ? raw.map((t) => {
271
- const o = t;
272
- return {
273
- value: String(o.value),
274
- name: String(o.name),
275
- ...(typeof o.hash === 'string'
276
- ? { hash: o.hash }
277
- : {}),
278
- };
279
- })
280
- : typeof args.target === 'string'
281
- ? [{
282
- value: args.target,
283
- name: providerName,
284
- ...(defaultHash ? { hash: defaultHash } : {}),
285
- }]
286
- : [];
287
- if (targets.length === 0) {
288
- throw new Error('propose_provider needs a `target` value or a non-empty '
289
- + '`targets` array');
290
- }
291
- for (const t of targets) {
292
- if ((t.hash ?? defaultHash) !== undefined
293
- && t.value.length > OPRF_MAX_BYTES) {
294
- throw new Error(`OPRF caps hashed values at ${OPRF_MAX_BYTES} bytes; got `
295
- + `${t.value.length}-byte "${t.value}". Use a tighter `
296
- + 'target or drop `hash`.');
297
- }
298
- }
299
- const captured = backend.capture.requests.get(requestId);
300
- if (!captured) {
301
- throw new Error(`no captured request ${requestId}`);
302
- }
303
- if (!captured.responseBody) {
304
- throw new Error(`request body unavailable for ${requestId}`);
305
- }
306
- const { provider, secretRefs, extractions } = draftProvider(captured, targets, providerName, defaultHash);
307
- // Materialize the real secret header values for later replay/prove;
308
- // they stay in the server-side draft, never in this result.
309
- const secrets = {};
310
- for (const ref of secretRefs) {
311
- const real = captured.requestHeaders[ref.name];
312
- if (real !== undefined) {
313
- secrets[ref.name] = real;
314
- }
315
- }
316
- const draftId = backend.drafts.put({
317
- provider, secretRefs, secrets, requestId, extractions,
318
- });
319
- const warnings = verificationWarnings(provider);
320
- return {
321
- draftId,
322
- provider,
323
- secretRefs,
324
- ...(warnings.length ? { verificationWarnings: warnings } : {}),
325
- };
326
- },
327
- }),
328
- mk({
329
- name: 'replay_request',
330
- description: 'Validate the drafted recipe before proving — confirm its selectors '
331
- + 'extract the target value. Returns diagnostics; run before '
332
- + 'proving. Set `withoutSecrets: true` for the AUTH-BOUND check: it '
333
- + 're-issues the request with NO secrets/cookies (an anonymous '
334
- + 'visitor). If the values still extract (valid:true), the endpoint '
335
- + 'is PUBLIC and the proof would not bind to the user — reject it and '
336
- + 'find an auth-bound request instead.',
337
- properties: {
338
- draftId: { type: 'string' },
339
- withoutSecrets: { type: 'boolean' },
340
- },
341
- required: ['draftId'],
342
- run: async (backend, args) => {
343
- const draft = backend.drafts.get(String(args.draftId));
344
- if (!draft) {
345
- throw new Error(`no draft ${String(args.draftId)}`);
346
- }
347
- return backend.replay(draft, {
348
- withoutSecrets: Boolean(args.withoutSecrets),
349
- });
350
- },
351
- }),
352
- mk({
163
+ findTool(resolveBackend),
164
+ proposeTool(resolveBackend),
165
+ replayTool(resolveBackend),
166
+ defineTool({
353
167
  name: 'run_proof',
354
168
  description: 'Prove the drafted recipe (zkTLS). Only call after replay_request '
355
169
  + 'looks right. Returns the proof result.',
356
- properties: { draftId: { type: 'string' } },
357
- required: ['draftId'],
358
- // The backend shapes its own result: the cloud returns a lean,
359
- // secret-free summary (and records the proven recipe for publish); the
360
- // MCP persists the claim + returns the rich developer view. The MCP adds
361
- // `ownerAddress`/`attestorUrl` to the schema via the host's toolExtras.
362
- run: async (backend, args) => {
363
- const draft = backend.drafts.get(String(args.draftId));
364
- if (!draft) {
365
- throw new Error(`no draft ${String(args.draftId)}`);
366
- }
367
- const opts = {};
368
- if (typeof args.ownerAddress === 'string') {
369
- opts.ownerAddress = args.ownerAddress;
370
- }
371
- if (typeof args.attestorUrl === 'string') {
372
- opts.attestorUrl = args.attestorUrl;
373
- }
374
- return backend.prove(draft, opts);
375
- },
170
+ inputSchema: objectSchema({ draftId: { type: 'string' } }, ['draftId']),
171
+ },
172
+ // The backend shapes its own result: the cloud returns a lean,
173
+ // secret-free summary (and records the proven recipe for publish); the
174
+ // MCP persists the claim + returns the rich developer view. The MCP adds
175
+ // `ownerAddress`/`attestorUrl` to the schema via `extendTool`; the
176
+ // handler reads them from args (loose parse keeps them).
177
+ async (args) => {
178
+ const backend = resolveBackend(args);
179
+ const draft = backend.drafts.get(String(args.draftId));
180
+ if (!draft) {
181
+ throw new Error(`no draft ${String(args.draftId)}`);
182
+ }
183
+ const opts = {};
184
+ if (typeof args.ownerAddress === 'string') {
185
+ opts.ownerAddress = args.ownerAddress;
186
+ }
187
+ if (typeof args.attestorUrl === 'string') {
188
+ opts.attestorUrl = args.attestorUrl;
189
+ }
190
+ return backend.prove(draft, opts);
376
191
  }),
377
192
  ];
378
193
  }
@@ -0,0 +1,27 @@
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
+ * Resolves the session backend for a tool call. The MCP keys it by
11
+ * `args.captureId` (multi-session); the cloud loop returns its one run backend.
12
+ * Each authoring tool closes over this so the same tool works for both hosts.
13
+ */
14
+ export type ResolveBackend = (args: Record<string, unknown>) => AuthoringBackend;
15
+ /** Build an `object` JSON Schema for a tool input — a small literal shortener,
16
+ * not a tool abstraction. */
17
+ export declare function objectSchema(properties: Record<string, unknown>, required?: string[]): JSONSchema.ObjectSchema;
18
+ /** Secret-free projection of a captured request for tool output. */
19
+ export declare function publicRequest(r: CapturedRequest): {
20
+ graphqlOp?: string | undefined;
21
+ requestId: string;
22
+ method: string;
23
+ url: string;
24
+ status: number;
25
+ contentType: string;
26
+ size: number;
27
+ };
@@ -0,0 +1,21 @@
1
+ /** Build an `object` JSON Schema for a tool input — a small literal shortener,
2
+ * not a tool abstraction. */
3
+ export function objectSchema(properties, required) {
4
+ return {
5
+ type: 'object',
6
+ properties,
7
+ ...(required?.length ? { required } : {}),
8
+ };
9
+ }
10
+ /** Secret-free projection of a captured request for tool output. */
11
+ export function publicRequest(r) {
12
+ return {
13
+ requestId: r.requestId,
14
+ method: r.method,
15
+ url: r.url,
16
+ status: r.status,
17
+ contentType: r.contentType,
18
+ size: r.size,
19
+ ...(r.graphqlOp ? { graphqlOp: r.graphqlOp } : {}),
20
+ };
21
+ }
package/lib/consts.d.ts CHANGED
@@ -17,11 +17,12 @@
17
17
  */
18
18
  import { DEFAULT_BASE_URL } from '@reclaimprotocol/client';
19
19
  import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from './old/client.ts';
20
+ import { DEFAULT_SDK_API_URL } from './old/sdk-client.ts';
20
21
  /** Public attestor websocket URL — fallback for `RECLAIM_ATTESTOR_URL`. */
21
22
  export declare const FALLBACK_ATTESTOR_URL = "wss://attestor.reclaimprotocol.org:444/ws";
22
23
  /** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
23
24
  export declare const DEFAULT_LOGIN_URL = "https://dev.reclaimprotocol.org";
24
- export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL };
25
+ export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL, DEFAULT_SDK_API_URL, };
25
26
  /** Pino log level. Read once at logger construction, so an eager const is
26
27
  * safe (no test mutates LOG_LEVEL at runtime). */
27
28
  export declare const LOG_LEVEL: string;
@@ -47,6 +48,10 @@ export declare function agentDisabled(): boolean;
47
48
  export declare function oldApiUrl(): string;
48
49
  /** Old-devtools analytics-logs service base URL (separate host). */
49
50
  export declare function oldLogsUrl(): string;
51
+ /** Public production SDK-backend base URL — the service the verification
52
+ * SDK/attestor itself reads a provider's recipe from at runtime. A
53
+ * DIFFERENT host from the old-devtools dashboard backend (`oldApiUrl`). */
54
+ export declare function sdkApiUrl(): string;
50
55
  /** Builder backend base URL. Shared by mcp/start and the verification tool. */
51
56
  export declare function apiUrl(): string;
52
57
  /** Explicit builder API token (overrides the cached session token). */
package/lib/consts.js 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.js";
22
+ import { DEFAULT_SDK_API_URL } from "./old/sdk-client.js";
22
23
  // ---------------------------------------------------------------------------
23
24
  // Default / fallback literals (used only as env fallbacks).
24
25
  // ---------------------------------------------------------------------------
@@ -26,9 +27,9 @@ import { DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL } from "./old/client.js";
26
27
  export const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws';
27
28
  /** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
28
29
  export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org';
29
- // Re-export the two non-env-fallback defaults so callers can keep importing
30
+ // Re-export the non-env-fallback defaults so callers can keep importing
30
31
  // their URL defaults from a single place if they wish.
31
- export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL };
32
+ export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL, DEFAULT_OLD_LOGS_URL, DEFAULT_SDK_API_URL, };
32
33
  // ---------------------------------------------------------------------------
33
34
  // Logging.
34
35
  // ---------------------------------------------------------------------------
@@ -83,6 +84,12 @@ export function oldApiUrl() {
83
84
  export function oldLogsUrl() {
84
85
  return process.env.RECLAIM_OLD_LOGS_URL || DEFAULT_OLD_LOGS_URL;
85
86
  }
87
+ /** Public production SDK-backend base URL — the service the verification
88
+ * SDK/attestor itself reads a provider's recipe from at runtime. A
89
+ * DIFFERENT host from the old-devtools dashboard backend (`oldApiUrl`). */
90
+ export function sdkApiUrl() {
91
+ return process.env.RECLAIM_SDK_API_URL || DEFAULT_SDK_API_URL;
92
+ }
86
93
  /** Builder backend base URL. Shared by mcp/start and the verification tool. */
87
94
  export function apiUrl() {
88
95
  return process.env.RECLAIM_API_URL || DEFAULT_BASE_URL;
@@ -1,45 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { JSONSchema } from 'zod/v4/core';
3
- export interface ToolDefinition {
4
- name: string;
5
- description: string;
6
- inputSchema: JSONSchema.ObjectSchema;
7
- }
8
- /**
9
- * Passed to every tool handler alongside its parsed args. Long-running,
10
- * human-in-the-loop tools (e.g. a browser login flow that waits for someone
11
- * to sign in) should call `reportProgress` every few seconds while waiting.
12
- * MCP clients that attach a progress token to their request (most desktop
13
- * clients do) treat an incoming `notifications/progress` as proof the call
14
- * is still alive and reset their own request timeout instead of giving up —
15
- * the SDK's client-side default is 60s, far shorter than a human can be
16
- * expected to take to log in. A no-op when the client didn't request
17
- * progress tracking for this call.
18
- */
19
- export interface ToolContext {
20
- reportProgress: (message: string) => Promise<void>;
21
- }
22
- export interface RegisteredTool {
23
- definition: ToolDefinition;
24
- /**
25
- * @param args Tool arguments
26
- * @param ctx Progress-reporting context (see {@link ToolContext}).
27
- * Optional so direct/test callers can omit it.
28
- * @returns A JSON stringifiable content to be returned to the llm
29
- * @throws Any error encountered during tool execution will be caught and
30
- * returned as a response using
31
- * [remapErrorAsResponse](./src/mcp/remap_errors.ts).
32
- */
33
- handler: (args: unknown, ctx?: ToolContext) => Promise<unknown>;
34
- }
35
- /**
36
- * Define a tool with a single inputSchema (JSON Schema) used as the
37
- * source of truth. The handler receives args already parsed and
38
- * type-narrowed via a Zod schema derived from that same JSON Schema —
39
- * so the description sent to the MCP client and the validation applied
40
- * to inbound args cannot drift.
41
- */
42
- export declare function defineTool<TArgs>(definition: ToolDefinition, handler: (args: TArgs, ctx: ToolContext) => Promise<unknown>): RegisteredTool;
2
+ import { type RegisteredTool } from '../tools/registry.ts';
3
+ export { defineTool, type RegisteredTool, type ToolDefinition, } from '../tools/registry.ts';
43
4
  /**
44
5
  * Registers a list of RegisteredTool entries on a high-level McpServer.
45
6
  * Converts JSON Schema definitions to Zod and provides consistent