@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.
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 +39 -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 +55 -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 +208 -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 +60 -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 +102 -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 +264 -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
@@ -3,13 +3,10 @@ import { join } from 'node:path'
3
3
  import { agentTraceEnabled } from '../../../consts.ts'
4
4
  import { reclaimHomeDirPath } from '../../../paths.ts'
5
5
  import {
6
- runAttestorProof,
6
+ type ProofResult,
7
7
  tailLogs,
8
8
  warnsAndErrors,
9
9
  } from '../../../proof/attestor.ts'
10
- import type { ReclaimProvider } from '../../../provider/schema.ts'
11
- import { defineTool, type RegisteredTool } from '../../server.ts'
12
- import type { SecretStore } from './propose.ts'
13
10
 
14
11
  function claimsDir(): string {
15
12
  return join(reclaimHomeDirPath(), 'claims')
@@ -98,170 +95,100 @@ function isLikelyByteArray(v: unknown[]): boolean {
98
95
  return true
99
96
  }
100
97
 
101
- export function proofTool(secrets: SecretStore): RegisteredTool {
102
- return defineTool<{
103
- provider: unknown
104
- secretParamsRef?: string
105
- attestorUrl?: string
106
- ownerAddress?: string
107
- }>(
108
- {
109
- name: 'run_proof',
110
- description:
111
- 'Run a full zkTLS proof through the Reclaim attestor SDK. ' +
112
- 'Use only after replay_request ' +
113
- 'returns matchesOriginal=true — attestor calls are expensive. ' +
114
- 'Writes the full claim to ' +
115
- '~/.reclaim/claims/<timestamp>.json and ' +
116
- 'returns identifier + extractedParameters + ' +
117
- 'claimPath + a prettified `claim` field ' +
118
- '(request dropped, byte arrays as base64) suitable ' +
119
- 'for displaying directly to the dev. ' +
120
- 'When the MCP server is started with ' +
121
- '`RECLAIM_AGENT_TRACE=1`, ALSO writes ' +
122
- 'a per-call diagnostic dump (input provider, ' +
123
- 'sentParams the SDK saw post-substitution, ' +
124
- 'FULL attestor log stream, result) to ' +
125
- '~/.reclaim/proof-runs/<timestamp>.json and ' +
126
- 'returns the path as `runLogPath`. ' +
127
- '`attestorUrl` overrides the default attestor URL only when the dev ' +
128
- 'supplies a specific ' +
129
- 'alternative — do NOT invent backup URLs ' +
130
- '(there is one live public attestor right now; ' +
131
- 'surface real errors to the dev instead of guessing). ' +
132
- '`ownerAddress` (the credential to sign with) is REQUIRED — ' +
133
- 'call resolve_owner_key first to obtain it, then pass it here. ' +
134
- 'errorKind="missing-owner-key" means that address has no known ' +
135
- 'private key; re-run resolve_owner_key or issue/import a credential.',
136
- inputSchema: {
137
- type: 'object',
138
- properties: {
139
- provider: { type: 'object', properties: {} },
140
- secretParamsRef: { type: 'string' },
141
- attestorUrl: { type: 'string' },
142
- ownerAddress: {
143
- type: 'string',
144
- pattern: '^0x[0-9a-fA-F]{40}$',
145
- description:
146
- 'ETH address of the credential to sign the claim with. ' +
147
- 'Get it from resolve_owner_key first. The private key is ' +
148
- 'resolved locally (cache / env / .env) — never passed here.',
149
- },
150
- },
151
- required: ['provider', 'ownerAddress'],
152
- },
153
- },
154
- async({ provider, secretParamsRef, attestorUrl, ownerAddress }) => {
155
- let resolved: Record<string, string> = {}
156
- if(secretParamsRef !== undefined) {
157
- const r = secrets.resolve(secretParamsRef)
158
- if(!r) {
159
- throw new Error(
160
- `unknown secretParamsRef "${secretParamsRef}" — ` +
161
- 'the MCP server has restarted or reset_session was called. ' +
162
- 'Re-run propose_provider to mint a fresh ref.',
163
- )
164
- }
165
-
166
- resolved = r
167
- }
168
-
169
- const result = await runAttestorProof(
170
- provider as ReclaimProvider,
171
- resolved,
172
- { attestorUrl, ownerAddress },
173
- )
174
-
175
- // Persist the full claim to disk for the dev to inspect/share.
176
- let claimPath: string | undefined
177
- if(result.proof !== undefined && result.proof !== null) {
178
- try {
179
- mkdirSync(claimsDir(), { recursive: true })
180
- claimPath = join(claimsDir(), `${Date.now()}.json`)
181
- writeFileSync(
182
- claimPath,
183
- JSON.stringify(result.proof, null, 2),
184
- 'utf8',
185
- )
186
- } catch{
187
- // Persistence is best-effort; don't fail the proof for it.
188
- claimPath = undefined
189
- }
190
- }
98
+ /**
99
+ * The MCP's rich `run_proof` view: persist the full claim to disk, return
100
+ * identifier + extractedParameters + a prettified claim (request dropped, byte
101
+ * arrays as base64) + trimmed logs, and — under `RECLAIM_AGENT_TRACE=1` — a
102
+ * forensic run dump. This is the developer-facing shape; the cloud backend
103
+ * projects the same {@link ProofResult} to a lean summary instead.
104
+ */
105
+ export function shapeRichProofResult(
106
+ result: ProofResult,
107
+ opts: { provider: unknown, hasSecrets: boolean, attestorUrl?: string },
108
+ ): Record<string, unknown> {
109
+ // Persist the full claim to disk for the dev to inspect/share.
110
+ let claimPath: string | undefined
111
+ if(result.proof !== undefined && result.proof !== null) {
112
+ try {
113
+ mkdirSync(claimsDir(), { recursive: true })
114
+ claimPath = join(claimsDir(), `${Date.now()}.json`)
115
+ writeFileSync(claimPath, JSON.stringify(result.proof, null, 2), 'utf8')
116
+ } catch{
117
+ // Persistence is best-effort; don't fail the proof for it.
118
+ claimPath = undefined
119
+ }
120
+ }
191
121
 
192
- const out: Record<string, unknown> = {
193
- verified: result.verified,
194
- extractedValue: result.extractedValue,
195
- extractedParameters: result.extractedParameters,
196
- }
197
- if(result.identifier !== undefined) {
198
- out.identifier = result.identifier
199
- }
122
+ const out: Record<string, unknown> = {
123
+ verified: result.verified,
124
+ extractedValue: result.extractedValue,
125
+ extractedParameters: result.extractedParameters,
126
+ }
127
+ if(result.identifier !== undefined) {
128
+ out.identifier = result.identifier
129
+ }
200
130
 
201
- if(result.owner !== undefined) {
202
- out.owner = result.owner
203
- }
131
+ if(result.owner !== undefined) {
132
+ out.owner = result.owner
133
+ }
204
134
 
205
- if(claimPath !== undefined) {
206
- out.claimPath = claimPath
207
- }
135
+ if(claimPath !== undefined) {
136
+ out.claimPath = claimPath
137
+ }
208
138
 
209
- if(result.proof !== undefined && result.proof !== null) {
210
- out.claim = prettifyClaim(result.proof)
211
- }
139
+ if(result.proof !== undefined && result.proof !== null) {
140
+ out.claim = prettifyClaim(result.proof)
141
+ }
212
142
 
213
- if(result.error !== undefined) {
214
- out.error = result.error
215
- }
143
+ if(result.error !== undefined) {
144
+ out.error = result.error
145
+ }
216
146
 
217
- if(result.errorKind !== undefined) {
218
- out.errorKind = result.errorKind
219
- }
147
+ if(result.errorKind !== undefined) {
148
+ out.errorKind = result.errorKind
149
+ }
220
150
 
221
- // Trim the LLM-visible logs — full stream goes to runLogPath.
222
- if(result.attestorLogs !== undefined && result.attestorLogs.length > 0) {
223
- out.attestorLogs = result.verified
224
- ? warnsAndErrors(result.attestorLogs)
225
- : tailLogs(result.attestorLogs)
226
- if((out.attestorLogs as unknown[]).length === 0) {
227
- delete out.attestorLogs
228
- }
229
- }
151
+ // Trim the LLM-visible logs — full stream goes to runLogPath.
152
+ if(result.attestorLogs !== undefined && result.attestorLogs.length > 0) {
153
+ out.attestorLogs = result.verified
154
+ ? warnsAndErrors(result.attestorLogs)
155
+ : tailLogs(result.attestorLogs)
156
+ if((out.attestorLogs as unknown[]).length === 0) {
157
+ delete out.attestorLogs
158
+ }
159
+ }
230
160
 
231
- if(result.sentParams !== undefined) {
232
- out.sentParams = result.sentParams
233
- }
161
+ if(result.sentParams !== undefined) {
162
+ out.sentParams = result.sentParams
163
+ }
234
164
 
235
- // Persist a diagnostic dump for forensics — gated behind
236
- // RECLAIM_AGENT_TRACE=1 (matches the attestor logger capture).
237
- // Off by default since happy-path runs don't need it; turn on
238
- // when investigating proof failures and the file at runLogPath
239
- // will carry the input provider, sentParams, full attestor log
240
- // stream, and result.
241
- if(agentTraceEnabled()) {
242
- const runLogPath = writeProofRunDump({
243
- provider,
244
- hasSecretParamsRef: secretParamsRef !== undefined,
245
- attestorUrl,
246
- result: {
247
- verified: result.verified,
248
- error: result.error,
249
- errorKind: result.errorKind,
250
- extractedValue: result.extractedValue,
251
- extractedParameters: result.extractedParameters,
252
- identifier: result.identifier,
253
- owner: result.owner,
254
- sentParams: result.sentParams,
255
- attestorLogs: result.attestorLogs,
256
- claimPath,
257
- },
258
- })
259
- if(runLogPath !== undefined) {
260
- out.runLogPath = runLogPath
261
- }
262
- }
165
+ // Persist a diagnostic dump for forensics — gated behind
166
+ // RECLAIM_AGENT_TRACE=1 (matches the attestor logger capture). Off by
167
+ // default since happy-path runs don't need it; turn on when investigating
168
+ // proof failures and the file at runLogPath will carry the input provider,
169
+ // sentParams, full attestor log stream, and result.
170
+ if(agentTraceEnabled()) {
171
+ const runLogPath = writeProofRunDump({
172
+ provider: opts.provider,
173
+ hasSecretParamsRef: opts.hasSecrets,
174
+ attestorUrl: opts.attestorUrl,
175
+ result: {
176
+ verified: result.verified,
177
+ error: result.error,
178
+ errorKind: result.errorKind,
179
+ extractedValue: result.extractedValue,
180
+ extractedParameters: result.extractedParameters,
181
+ identifier: result.identifier,
182
+ owner: result.owner,
183
+ sentParams: result.sentParams,
184
+ attestorLogs: result.attestorLogs,
185
+ claimPath,
186
+ },
187
+ })
188
+ if(runLogPath !== undefined) {
189
+ out.runLogPath = runLogPath
190
+ }
191
+ }
263
192
 
264
- return out
265
- },
266
- )
193
+ return out
267
194
  }
@@ -1,120 +1,128 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { draftProvider } from '../../../provider/draft.ts'
1
+ /**
2
+ * `propose_provider` — draft a provider (recipe + captured secrets) from ONE
3
+ * captured request, one match + redaction per target value. Authored once as a
4
+ * plain {@link RegisteredTool}; hosts pass their own backend resolver. The
5
+ * draft (recipe + real secret header values) is held server-side under an
6
+ * opaque `draftId`; the result carries only the secret-free provider.
7
+ */
8
+ import { objectSchema, type ResolveBackend } from '../../../authoring/util.ts'
9
+ import { draftProvider, type DraftTarget } from '../../../provider/draft.ts'
3
10
  import { OPRF_MAX_BYTES } from '../../../provider/matcher.ts'
4
11
  import type { OprfMode } from '../../../provider/schema.ts'
5
12
  import { verificationWarnings } from '../../../provider/verification-checks.ts'
6
- import { defineTool, type RegisteredTool } from '../../server.ts'
7
- import type { CaptureStore } from './capture.ts'
13
+ import { defineTool, type RegisteredTool } from '../../../tools/registry.ts'
8
14
 
9
- export interface SecretStore {
10
- put: (values: Record<string, string>) => string
11
- resolve: (ref: string) => Record<string, string> | undefined
12
- clear: () => void
13
- }
14
-
15
- export function makeSecretStore(): SecretStore {
16
- const map = new Map<string, Record<string, string>>()
17
- return {
18
- put: (values) => {
19
- const id = `sref_${randomUUID()}`
20
- map.set(id, values)
21
- return id
22
- },
23
- resolve: (ref) => map.get(ref),
24
- clear: () => map.clear(),
25
- }
26
- }
27
-
28
- export function proposeTool(
29
- captures: CaptureStore,
30
- secrets: SecretStore,
31
- ): RegisteredTool {
32
- return defineTool<{
33
- captureId: string
34
- requestId: string
35
- target: string
36
- name: string
37
- hash?: OprfMode
38
- }>(
15
+ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
16
+ return defineTool<Record<string, unknown>>(
39
17
  {
40
18
  name: 'propose_provider',
41
19
  description:
42
- 'Draft a Reclaim provider JSON from a captured request + ' +
43
- 'a target value. ' +
44
- 'Secret header values are stored server-side and replaced with an ' +
45
- 'opaque secretParamsRef ' +
46
- 'in the response — raw secret values never appear in tool output. ' +
47
- "`hash` is opt-in: when set, the captured value is OPRF'd so " +
48
- 'the attestor never sees ' +
49
- 'cleartext. Skill should ask the dev first. `oprf-raw` ' +
50
- 'is the only variant that runs ' +
51
- 'without external deps; `oprf` needs gnark (Linux/macOS), ' +
52
- '`oprf-mpc` needs a TEE attestor. ' +
53
- 'The response may include `verificationWarnings` — advisory ' +
54
- 'author-only decisions (e.g. a user-specific URL part needing ' +
55
- 'the {{context.<name>}}-vs-bare call). Surface each to the ' +
56
- 'dev verbatim and resolve before publishing.',
57
- inputSchema: {
58
- type: 'object',
59
- properties: {
60
- captureId: { type: 'string' },
20
+ 'Draft a provider from ONE captured request. Pass `targets` (an '
21
+ + 'array): one entry per value to extract, each with its `value` '
22
+ + '(verbatim) and a distinct `name` (its param). The provider gets '
23
+ + 'one match + redaction per target. Returns a draftId + recipe '
24
+ + '(never secrets). Prefer a SINGLE request holding ALL values. '
25
+ + 'Single-value shorthand: `target` + `name`. Per-target `hash` '
26
+ + 'OPRF-hashes that value.',
27
+ inputSchema: objectSchema(
28
+ {
61
29
  requestId: { type: 'string' },
62
- target: { type: 'string' },
63
30
  name: { type: 'string' },
31
+ targets: {
32
+ type: 'array',
33
+ items: {
34
+ type: 'object',
35
+ properties: {
36
+ value: { type: 'string' },
37
+ name: { type: 'string' },
38
+ hash: {
39
+ type: 'string',
40
+ enum: ['oprf-raw', 'oprf', 'oprf-mpc'],
41
+ },
42
+ },
43
+ required: ['value', 'name'],
44
+ },
45
+ },
46
+ target: { type: 'string' },
64
47
  hash: { type: 'string', enum: ['oprf-raw', 'oprf', 'oprf-mpc'] },
65
48
  },
66
- required: ['captureId', 'requestId', 'target', 'name'],
67
- },
49
+ ['requestId', 'name'],
50
+ ),
68
51
  },
69
- async({ captureId, requestId, target, name, hash }) => {
70
- if(hash !== undefined && target.length > OPRF_MAX_BYTES) {
52
+ async(args) => {
53
+ const backend = resolveBackend(args)
54
+ const requestId = String(args.requestId)
55
+ const providerName = String(args.name)
56
+ const defaultHash = args.hash as OprfMode | undefined
57
+
58
+ // Explicit multi-value `targets`, else the single `target` shorthand.
59
+ const raw = Array.isArray(args.targets) ? args.targets : []
60
+ const targets: DraftTarget[] = raw.length
61
+ ? raw.map((t) => {
62
+ const o = t as Record<string, unknown>
63
+ return {
64
+ value: String(o.value),
65
+ name: String(o.name),
66
+ ...(typeof o.hash === 'string'
67
+ ? { hash: o.hash as OprfMode }
68
+ : {}),
69
+ }
70
+ })
71
+ : typeof args.target === 'string'
72
+ ? [{
73
+ value: args.target,
74
+ name: providerName,
75
+ ...(defaultHash ? { hash: defaultHash } : {}),
76
+ }]
77
+ : []
78
+ if(targets.length === 0) {
71
79
  throw new Error(
72
- `OPRF caps hashed values at ${OPRF_MAX_BYTES} bytes; ` +
73
- `got ${target.length}-byte target ` +
74
- `"${target}". Try a tighter target ` +
75
- '(e.g. just the relevant substring) or call ' +
76
- 'propose_provider without `hash`.',
80
+ 'propose_provider needs a `target` value or a non-empty '
81
+ + '`targets` array',
77
82
  )
78
83
  }
79
84
 
80
- const session = captures.sessions.get(captureId)
81
- if(!session) {
82
- throw new Error(`no capture ${captureId}`)
85
+ for(const t of targets) {
86
+ if((t.hash ?? defaultHash) !== undefined
87
+ && t.value.length > OPRF_MAX_BYTES) {
88
+ throw new Error(
89
+ `OPRF caps hashed values at ${OPRF_MAX_BYTES} bytes; got `
90
+ + `${t.value.length}-byte "${t.value}". Use a tighter `
91
+ + 'target or drop `hash`.',
92
+ )
93
+ }
83
94
  }
84
95
 
85
- const r = session.requests.get(requestId)
86
- if(!r) {
87
- throw new Error(`no request ${requestId}`)
96
+ const captured = backend.capture.requests.get(requestId)
97
+ if(!captured) {
98
+ throw new Error(`no captured request ${requestId}`)
88
99
  }
89
100
 
90
- const body = session.bodies.get(requestId)
91
- if(!body.available) {
92
- throw new Error(`request body unavailable: ${body.reason}`)
101
+ if(!captured.responseBody) {
102
+ throw new Error(`request body unavailable for ${requestId}`)
93
103
  }
94
104
 
95
- const reqWithBody = { ...r, responseBody: body.body }
96
- const { provider, secretRefs } = draftProvider(
97
- reqWithBody,
98
- target,
99
- name,
100
- hash,
105
+ const { provider, secretRefs, extractions } = draftProvider(
106
+ captured, targets, providerName, defaultHash,
101
107
  )
102
-
103
- // Stash real secret values for later replay/proof.
104
- const realSecrets: Record<string, string> = {}
108
+ // Materialize the real secret header values for later replay/prove;
109
+ // they stay in the server-side draft, never in this result.
110
+ const secrets: Record<string, string> = {}
105
111
  for(const ref of secretRefs) {
106
- const real = r.requestHeaders[ref.name]
112
+ const real = captured.requestHeaders[ref.name]
107
113
  if(real !== undefined) {
108
- realSecrets[ref.name] = real
114
+ secrets[ref.name] = real
109
115
  }
110
116
  }
111
117
 
112
- const secretParamsRef = secrets.put(realSecrets)
118
+ const draftId = backend.drafts.put({
119
+ provider, secretRefs, secrets, requestId, extractions,
120
+ })
113
121
  const warnings = verificationWarnings(provider)
114
122
  return {
123
+ draftId,
115
124
  provider,
116
125
  secretRefs,
117
- secretParamsRef,
118
126
  ...(warnings.length ? { verificationWarnings: warnings } : {}),
119
127
  }
120
128
  },
@@ -1,53 +1,43 @@
1
- import { replayProvider } from '../../../proof/replay.ts'
2
- import type { ReclaimProvider } from '../../../provider/schema.ts'
3
- import { defineTool, type RegisteredTool } from '../../server.ts'
4
- import type { SecretStore } from './propose.ts'
1
+ /**
2
+ * `replay_request` — validate a drafted recipe before proving: confirm its
3
+ * selectors extract the target(s), and (with `withoutSecrets`) that the
4
+ * endpoint is auth-bound. Authored once as a plain {@link RegisteredTool}; the
5
+ * backend does the actual replay (MCP re-runs locally, cloud re-issues in the
6
+ * TEE pod).
7
+ */
8
+ import { objectSchema, type ResolveBackend } from '../../../authoring/util.ts'
9
+ import { defineTool, type RegisteredTool } from '../../../tools/registry.ts'
5
10
 
6
- export function replayTool(secrets: SecretStore): RegisteredTool {
7
- return defineTool<{ provider: unknown, secretParamsRef?: string }>(
11
+ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
12
+ return defineTool<Record<string, unknown>>(
8
13
  {
9
14
  name: 'replay_request',
10
15
  description:
11
- 'Replay a drafted provider against the live endpoint ' +
12
- 'using Node fetch (no attestor). ' +
13
- 'Pre-flight gate before run_proof — ' +
14
- 'diagnoses CSRF / token-rotation / matcher-too-tight ' +
15
- 'issues with richer hints than the attestor would produce.',
16
- inputSchema: {
17
- type: 'object',
18
- properties: {
19
- provider: { type: 'object', properties: {} },
20
- secretParamsRef: { type: 'string' },
16
+ 'Validate the drafted recipe before proving — confirm its selectors '
17
+ + 'extract the target value. Returns diagnostics; run before '
18
+ + 'proving. Set `withoutSecrets: true` for the AUTH-BOUND check: it '
19
+ + 're-issues the request with NO secrets/cookies (an anonymous '
20
+ + 'visitor). If the values still extract (valid:true), the endpoint '
21
+ + 'is PUBLIC and the proof would not bind to the user — reject it '
22
+ + 'and find an auth-bound request instead.',
23
+ inputSchema: objectSchema(
24
+ {
25
+ draftId: { type: 'string' },
26
+ withoutSecrets: { type: 'boolean' },
21
27
  },
22
- required: ['provider'],
23
- },
28
+ ['draftId'],
29
+ ),
24
30
  },
25
- async({ provider, secretParamsRef }) => {
26
- let resolved: Record<string, string> = {}
27
- if(secretParamsRef !== undefined) {
28
- const r = secrets.resolve(secretParamsRef)
29
- if(!r) {
30
- throw new Error(
31
- `unknown secretParamsRef "${secretParamsRef}" — ` +
32
- 'the MCP server has restarted or reset_session was called. ' +
33
- 'Re-run propose_provider to mint a fresh ref.',
34
- )
35
- }
36
-
37
- resolved = r
38
- }
39
-
40
- const result = await replayProvider(provider as ReclaimProvider, resolved)
41
- const out: Record<string, unknown> = {
42
- status: result.status,
43
- matchesOriginal: result.matchesOriginal,
44
- bodySnippet: result.body.slice(0, 256),
45
- }
46
- if(result.hints !== undefined) {
47
- out.hints = result.hints
31
+ async(args) => {
32
+ const backend = resolveBackend(args)
33
+ const draft = backend.drafts.get(String(args.draftId))
34
+ if(!draft) {
35
+ throw new Error(`no draft ${String(args.draftId)}`)
48
36
  }
49
37
 
50
- return out
38
+ return backend.replay(draft, {
39
+ withoutSecrets: Boolean(args.withoutSecrets),
40
+ })
51
41
  },
52
42
  )
53
43
  }
@@ -6,7 +6,6 @@ import type { CaptureStore } from './capture.ts'
6
6
  export function sessionTools(
7
7
  attachRef: { current?: AttachState, config: ConfigStore },
8
8
  captures: CaptureStore,
9
- secrets: { clear: () => void },
10
9
  ): RegisteredTool[] {
11
10
  return [
12
11
  defineTool<Record<string, never>>(
@@ -28,17 +27,17 @@ export function sessionTools(
28
27
  {
29
28
  name: 'reset_session',
30
29
  description:
31
- 'Stop all captures, drop in-memory secrets, and clear capture state.',
30
+ 'Stop all captures and clear capture state — drops every '
31
+ + 'session\'s drafts and their in-memory secrets.',
32
32
  inputSchema: { type: 'object', properties: {} },
33
33
  },
34
34
  async() => {
35
- for(const s of captures.sessions.values()) {
36
- await s.stop().catch(() => {})
35
+ for(const entry of captures.sessions.values()) {
36
+ await entry.session.stop().catch(() => {})
37
37
  }
38
38
 
39
39
  captures.sessions.clear()
40
40
  delete captures.activeTab
41
- secrets.clear()
42
41
  return { reset: true }
43
42
  },
44
43
  ),